From a023f7e694f2cb52674fcafbf1fa7bd4483c6bc8 Mon Sep 17 00:00:00 2001 From: Benjamin Chung Date: Sun, 27 Oct 2024 14:05:55 -0700 Subject: [PATCH 1/7] Add support for initialize and finalize callbacks --- src/systems/callbacks.jl | 177 ++++++++++++++++++++++++++++++--------- 1 file changed, 138 insertions(+), 39 deletions(-) diff --git a/src/systems/callbacks.jl b/src/systems/callbacks.jl index c427ae02a1..99d001146f 100644 --- a/src/systems/callbacks.jl +++ b/src/systems/callbacks.jl @@ -114,6 +114,8 @@ initialization. """ struct SymbolicContinuousCallback eqs::Vector{Equation} + initialize::Union{Vector{Equation}, FunctionalAffect} + finalize::Union{Vector{Equation}, FunctionalAffect} affect::Union{Vector{Equation}, FunctionalAffect} affect_neg::Union{Vector{Equation}, FunctionalAffect, Nothing} rootfind::SciMLBase.RootfindOpt @@ -122,9 +124,12 @@ struct SymbolicContinuousCallback eqs::Vector{Equation}, affect = NULL_AFFECT, affect_neg = affect, + initialize = NULL_AFFECT, + finalize = NULL_AFFECT, rootfind = SciMLBase.LeftRootFind, reinitializealg = SciMLBase.CheckInit()) - new(eqs, make_affect(affect), make_affect(affect_neg), rootfind, reinitializealg) + new(eqs, initialize, finalize, make_affect(affect), + make_affect(affect_neg), rootfind, reinitializealg) end # Default affect to nothing end make_affect(affect) = affect @@ -133,17 +138,80 @@ make_affect(affect::NamedTuple) = FunctionalAffect(; affect...) function Base.:(==)(e1::SymbolicContinuousCallback, e2::SymbolicContinuousCallback) isequal(e1.eqs, e2.eqs) && isequal(e1.affect, e2.affect) && + isequal(e1.initialize, e2.initialize) && isequal(e1.finalize, e2.finalize) && isequal(e1.affect_neg, e2.affect_neg) && isequal(e1.rootfind, e2.rootfind) end Base.isempty(cb::SymbolicContinuousCallback) = isempty(cb.eqs) function Base.hash(cb::SymbolicContinuousCallback, s::UInt) + hash_affect(affect::AbstractVector, s) = foldr(hash, affect, init = s) + hash_affect(affect, s) = hash(affect, s) s = foldr(hash, cb.eqs, init = s) - s = cb.affect isa AbstractVector ? foldr(hash, cb.affect, init = s) : hash(cb.affect, s) - s = cb.affect_neg isa AbstractVector ? foldr(hash, cb.affect_neg, init = s) : - hash(cb.affect_neg, s) + s = hash_affect(cb.affect, s) + s = hash_affect(cb.affect_neg, s) + s = hash_affect(cb.initialize, s) + s = hash_affect(cb.finalize, s) + s = hash(cb.reinitializealg, s) hash(cb.rootfind, s) end +function Base.show(io::IO, cb::SymbolicContinuousCallback) + indent = get(io, :indent, 0) + iio = IOContext(io, :indent => indent + 1) + print(io, "SymbolicContinuousCallback(") + print(iio, "Equations:") + show(iio, equations(cb)) + print(iio, "; ") + if affects(cb) != NULL_AFFECT + print(iio, "Affect:") + show(iio, affects(cb)) + print(iio, ", ") + end + if affect_negs(cb) != NULL_AFFECT + print(iio, "Negative-edge affect:") + show(iio, affect_negs(cb)) + print(iio, ", ") + end + if initialize_affects(cb) != NULL_AFFECT + print(iio, "Initialization affect:") + show(iio, initialize_affects(cb)) + print(iio, ", ") + end + if finalize_affects(cb) != NULL_AFFECT + print(iio, "Finalization affect:") + show(iio, finalize_affects(cb)) + end + print(iio, ")") +end + +function Base.show(io::IO, mime::MIME"text/plain", cb::SymbolicContinuousCallback) + indent = get(io, :indent, 0) + iio = IOContext(io, :indent => indent + 1) + println(io, "SymbolicContinuousCallback:") + println(iio, "Equations:") + show(iio, mime, equations(cb)) + print(iio, "\n") + if affects(cb) != NULL_AFFECT + println(iio, "Affect:") + show(iio, mime, affects(cb)) + print(iio, "\n") + end + if affect_negs(cb) != NULL_AFFECT + println(iio, "Negative-edge affect:") + show(iio, mime, affect_negs(cb)) + print(iio, "\n") + end + if initialize_affects(cb) != NULL_AFFECT + println(iio, "Initialization affect:") + show(iio, mime, initialize_affects(cb)) + print(iio, "\n") + end + if finalize_affects(cb) != NULL_AFFECT + println(iio, "Finalization affect:") + show(iio, mime, finalize_affects(cb)) + print(iio, "\n") + end +end + to_equation_vector(eq::Equation) = [eq] to_equation_vector(eqs::Vector{Equation}) = eqs function to_equation_vector(eqs::Vector{Any}) @@ -156,15 +224,18 @@ function SymbolicContinuousCallback(args...) end # wrap eq in vector SymbolicContinuousCallback(p::Pair) = SymbolicContinuousCallback(p[1], p[2]) SymbolicContinuousCallback(cb::SymbolicContinuousCallback) = cb # passthrough -function SymbolicContinuousCallback(eqs::Equation, affect = NULL_AFFECT; - affect_neg = affect, rootfind = SciMLBase.LeftRootFind) +function SymbolicContinuousCallback(eqs::Equation, affect = NULL_AFFECT; + initialize=NULL_AFFECT, finalize=NULL_AFFECT, + affect_neg = affect, rootfind = SciMLBase.LeftRootFind) SymbolicContinuousCallback( - eqs = [eqs], affect = affect, affect_neg = affect_neg, rootfind = rootfind) + eqs = [eqs], affect = affect, affect_neg = affect_neg, + initialize=initialize, finalize=finalize, rootfind = rootfind) end function SymbolicContinuousCallback(eqs::Vector{Equation}, affect = NULL_AFFECT; - affect_neg = affect, rootfind = SciMLBase.LeftRootFind) + affect_neg = affect, initialize=NULL_AFFECT, finalize=NULL_AFFECT, + rootfind = SciMLBase.LeftRootFind) SymbolicContinuousCallback( - eqs = eqs, affect = affect, affect_neg = affect_neg, rootfind = rootfind) + eqs = eqs, affect = affect, affect_neg = affect_neg, initialize=initialize, finalize=finalize, rootfind = rootfind) end SymbolicContinuousCallbacks(cb::SymbolicContinuousCallback) = [cb] @@ -199,15 +270,28 @@ function reinitialization_algs(cbs::Vector{SymbolicContinuousCallback}) reinitialization_alg, vcat, cbs, init = SciMLBase.DAEInitializationAlgorithm[]) end +initialize_affects(cb::SymbolicContinuousCallback) = cb.initialize +function initialize_affects(cbs::Vector{SymbolicContinuousCallback}) + mapreduce(initialize_affects, vcat, cbs, init = Equation[]) +end + +finalize_affects(cb::SymbolicContinuousCallback) = cb.initialize +function finalize_affects(cbs::Vector{SymbolicContinuousCallback}) + mapreduce(finalize_affects, vcat, cbs, init = Equation[]) +end + namespace_affects(af::Vector, s) = Equation[namespace_affect(a, s) for a in af] namespace_affects(af::FunctionalAffect, s) = namespace_affect(af, s) namespace_affects(::Nothing, s) = nothing function namespace_callback(cb::SymbolicContinuousCallback, s)::SymbolicContinuousCallback - SymbolicContinuousCallback( - namespace_equation.(equations(cb), (s,)), - namespace_affects(affects(cb), s); - affect_neg = namespace_affects(affect_negs(cb), s)) + SymbolicContinuousCallback(; + eqs = namespace_equation.(equations(cb), (s,)), + affect = namespace_affects(affects(cb), s), + affect_neg = namespace_affects(affect_negs(cb), s), + initialize = namespace_affects(initialize_affects(cb), s), + finalize = namespace_affects(finalize_affects(cb), s), + rootfind = cb.rootfind) end """ @@ -589,22 +673,25 @@ function generate_single_rootfinding_callback( rf_oop(u, parameter_values(integ), t) end end - + user_initfun = (affect_function.initialize == NULL_AFFECT) ? SciMLBase.INITIALIZE_DEFAULT : + (c, u, t, i) -> affect_function.initialize(i) if has_index_cache(sys) && (ic = get_index_cache(sys)) !== nothing && (save_idxs = get(ic.callback_to_clocks, cb, nothing)) !== nothing initfn = let save_idxs = save_idxs function (cb, u, t, integrator) + user_initfun(cb, u, t, integrator) for idx in save_idxs SciMLBase.save_discretes!(integrator, idx) end end end else - initfn = SciMLBase.INITIALIZE_DEFAULT + initfn = user_initfun end return ContinuousCallback( cond, affect_function.affect, affect_function.affect_neg, rootfind = cb.rootfind, initialize = initfn, + finalize = (affect_function.finalize == NULL_AFFECT) ? SciMLBase.FINALIZE_DEFAULT : (c, u, t, i) -> affect_function.finalize(i), initializealg = reinitialization_alg(cb)) end @@ -626,13 +713,14 @@ function generate_vector_rootfinding_callback( _, rf_ip = generate_custom_function( sys, rhss, dvs, ps; expression = Val{false}, kwargs...) - affect_functions = @NamedTuple{affect::Function, affect_neg::Union{Function, Nothing}}[compile_affect_fn( - cb, - sys, - dvs, - ps, - kwargs) - for cb in cbs] + affect_functions = @NamedTuple{ + affect::Function, + affect_neg::Union{Function, Nothing}, + initialize::Union{Function, Nothing}, + finalize::Union{Function, Nothing}}[ + compile_affect_fn(cb, sys, dvs, ps, kwargs) + for cb in cbs] + cond = function (out, u, t, integ) rf_ip(out, u, parameter_values(integ), t) end @@ -658,26 +746,31 @@ function generate_vector_rootfinding_callback( affect_neg(integ) end end - if has_index_cache(sys) && (ic = get_index_cache(sys)) !== nothing - save_idxs = mapreduce( - cb -> get(ic.callback_to_clocks, cb, Int[]), vcat, cbs; init = Int[]) - initfn = if isempty(save_idxs) - SciMLBase.INITIALIZE_DEFAULT + function handle_optional_setup_fn(funs, default) + if all(isnothing, funs) + return default else - let save_idxs = save_idxs - function (cb, u, t, integrator) - for idx in save_idxs - SciMLBase.save_discretes!(integrator, idx) + return let funs = funs + function (cb, u, t, integ) + for func in funs + if isnothing(func) + continue + else + func(integ) + end end end end end - else - initfn = SciMLBase.INITIALIZE_DEFAULT end + + initialize = handle_optional_setup_fn( + map(fn -> fn.initialize, affect_functions), SciMLBase.INITIALIZE_DEFAULT) + finalize = handle_optional_setup_fn( + map(fn -> fn.finalize, affect_functions), SciMLBase.FINALIZE_DEFAULT) return VectorContinuousCallback( cond, affect, affect_neg, length(eqs), rootfind = rootfind, - initialize = initfn, initializealg = reinitialization) + initialize = initialize, finalize = finalize, initializealg = reinitialization) end """ @@ -687,15 +780,21 @@ function compile_affect_fn(cb, sys::AbstractODESystem, dvs, ps, kwargs) eq_aff = affects(cb) eq_neg_aff = affect_negs(cb) affect = compile_affect(eq_aff, cb, sys, dvs, ps; expression = Val{false}, kwargs...) + function compile_optional_affect(aff, default = nothing) + if isnothing(aff) || aff == default + return nothing + else + return compile_affect(aff, cb, sys, dvs, ps; expression = Val{false}, kwargs...) + end + end if eq_neg_aff === eq_aff affect_neg = affect - elseif isnothing(eq_neg_aff) - affect_neg = nothing else - affect_neg = compile_affect( - eq_neg_aff, cb, sys, dvs, ps; expression = Val{false}, kwargs...) + affect_neg = compile_optional_affect(eq_neg_aff) end - (affect = affect, affect_neg = affect_neg) + initialize = compile_optional_affect(initialize_affects(cb), NULL_AFFECT) + finalize = compile_optional_affect(finalize_affects(cb), NULL_AFFECT) + (affect = affect, affect_neg = affect_neg, initialize = initialize, finalize = finalize) end function generate_rootfinding_callback(cbs, sys::AbstractODESystem, dvs = unknowns(sys), From b4893a3ef5b767eafc2d34409781241397de9509 Mon Sep 17 00:00:00 2001 From: Benjamin Chung Date: Mon, 28 Oct 2024 01:59:41 -0700 Subject: [PATCH 2/7] Fix initial condition --- src/systems/callbacks.jl | 36 +++++++++++++++++++++++++++++++----- 1 file changed, 31 insertions(+), 5 deletions(-) diff --git a/src/systems/callbacks.jl b/src/systems/callbacks.jl index 99d001146f..fe2dd8c14f 100644 --- a/src/systems/callbacks.jl +++ b/src/systems/callbacks.jl @@ -673,7 +673,7 @@ function generate_single_rootfinding_callback( rf_oop(u, parameter_values(integ), t) end end - user_initfun = (affect_function.initialize == NULL_AFFECT) ? SciMLBase.INITIALIZE_DEFAULT : + user_initfun = isnothing(affect_function.initialize) ? SciMLBase.INITIALIZE_DEFAULT : (c, u, t, i) -> affect_function.initialize(i) if has_index_cache(sys) && (ic = get_index_cache(sys)) !== nothing && (save_idxs = get(ic.callback_to_clocks, cb, nothing)) !== nothing @@ -691,7 +691,7 @@ function generate_single_rootfinding_callback( return ContinuousCallback( cond, affect_function.affect, affect_function.affect_neg, rootfind = cb.rootfind, initialize = initfn, - finalize = (affect_function.finalize == NULL_AFFECT) ? SciMLBase.FINALIZE_DEFAULT : (c, u, t, i) -> affect_function.finalize(i), + finalize = isnothing(affect_function.finalize) ? SciMLBase.FINALIZE_DEFAULT : (c, u, t, i) -> affect_function.finalize(i), initializealg = reinitialization_alg(cb)) end @@ -763,9 +763,35 @@ function generate_vector_rootfinding_callback( end end end - - initialize = handle_optional_setup_fn( - map(fn -> fn.initialize, affect_functions), SciMLBase.INITIALIZE_DEFAULT) + initialize = nothing + if has_index_cache(sys) && (ic = get_index_cache(sys)) !== nothing + initialize = handle_optional_setup_fn(map((cb, fn) -> begin + if (save_idxs = get(ic.callback_to_clocks, cb, nothing)) !== nothing + let save_idxs = save_idxs + if !isnothing(fn.initialize) + (i) -> begin + for idx in save_idxs + SciMLBase.save_discretes!(i, idx) + end + fn.initialize(i) + end + else + (i) -> begin + for idx in save_idxs + SciMLBase.save_discretes!(i, idx) + end + end + end + end + else + fn.initialize + end + end, cbs, affect_functions), SciMLBase.INITIALIZE_DEFAULT) + + else + initialize = handle_optional_setup_fn(map(fn -> fn.initialize, affect_functions), SciMLBase.INITIALIZE_DEFAULT) + end + finalize = handle_optional_setup_fn( map(fn -> fn.finalize, affect_functions), SciMLBase.FINALIZE_DEFAULT) return VectorContinuousCallback( From aa0ab70d75f33b97a13a42e740a59107add84e3e Mon Sep 17 00:00:00 2001 From: Benjamin Chung Date: Mon, 28 Oct 2024 02:15:10 -0700 Subject: [PATCH 3/7] Fix finalization --- src/systems/callbacks.jl | 2 +- test/symbolic_events.jl | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/systems/callbacks.jl b/src/systems/callbacks.jl index fe2dd8c14f..1729796a14 100644 --- a/src/systems/callbacks.jl +++ b/src/systems/callbacks.jl @@ -275,7 +275,7 @@ function initialize_affects(cbs::Vector{SymbolicContinuousCallback}) mapreduce(initialize_affects, vcat, cbs, init = Equation[]) end -finalize_affects(cb::SymbolicContinuousCallback) = cb.initialize +finalize_affects(cb::SymbolicContinuousCallback) = cb.finalize function finalize_affects(cbs::Vector{SymbolicContinuousCallback}) mapreduce(finalize_affects, vcat, cbs, init = Equation[]) end diff --git a/test/symbolic_events.jl b/test/symbolic_events.jl index 26593f980c..1e9321770e 100644 --- a/test/symbolic_events.jl +++ b/test/symbolic_events.jl @@ -970,6 +970,20 @@ end @test sol[c] == [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0] end + +@testset "Initialization" begin + @variables x(t) + seen = false + f = ModelingToolkit.FunctionalAffect(f=(i,u,p,c)->seen=true, sts=[], pars=[], discretes=[]) + cb1 = ModelingToolkit.SymbolicContinuousCallback([x ~ 0], Equation[], initialize=[x~1.5], finalize=f) + @mtkbuild sys = ODESystem(D(x) ~ -1, t, [x], []; continuous_events = [cb1]) + prob = ODEProblem(sys, [x => 1.0], (0.0, 2), []) + sol = solve(prob, Tsit5(); dtmax=0.01) + @test sol[x][1] ≈ 1.0 + @test sol[x][2] ≈ 1.5 # the initialize affect has been applied + @test seen == true +end + @testset "Bump" begin @variables x(t) [irreducible = true] y(t) [irreducible = true] eqs = [x ~ y, D(x) ~ -1] From 3e5cbde5ae297a69b554334b05ce3dd92114e6f4 Mon Sep 17 00:00:00 2001 From: Benjamin Chung Date: Mon, 28 Oct 2024 02:17:35 -0700 Subject: [PATCH 4/7] Add a note to the docstring about initialize and finalize --- src/systems/callbacks.jl | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/systems/callbacks.jl b/src/systems/callbacks.jl index 1729796a14..03869e9779 100644 --- a/src/systems/callbacks.jl +++ b/src/systems/callbacks.jl @@ -111,6 +111,9 @@ DAEs will be reinitialized using `reinitializealg` (which defaults to `SciMLBase This reinitialization algorithm ensures that the DAE is satisfied after the callback runs. The default value of `CheckInit` will simply validate that the newly-assigned values indeed satisfy the algebraic system; see the documentation on DAE initialization for a more detailed discussion of initialization. + +Initial and final affects can also be specified with SCC, which are specified identically to positive and negative edge affects. Initialization affects +will run as soon as the solver starts, while finalization affects will be executed after termination. """ struct SymbolicContinuousCallback eqs::Vector{Equation} From 9e638232e4fa7d24f412111656814af62b7c0d27 Mon Sep 17 00:00:00 2001 From: Benjamin Chung Date: Mon, 28 Oct 2024 02:20:36 -0700 Subject: [PATCH 5/7] Test VCC initialization/finalization --- test/symbolic_events.jl | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/test/symbolic_events.jl b/test/symbolic_events.jl index 1e9321770e..727e190c72 100644 --- a/test/symbolic_events.jl +++ b/test/symbolic_events.jl @@ -982,6 +982,25 @@ end @test sol[x][1] ≈ 1.0 @test sol[x][2] ≈ 1.5 # the initialize affect has been applied @test seen == true + + + @variables x(t) + seen = false + f = ModelingToolkit.FunctionalAffect(f=(i,u,p,c)->seen=true, sts=[], pars=[], discretes=[]) + cb1 = ModelingToolkit.SymbolicContinuousCallback([x ~ 0], Equation[], initialize=[x~1.5], finalize=f) + inited = false + finaled = false + a = ModelingToolkit.FunctionalAffect(f=(i,u,p,c)->inited=true, sts=[], pars=[], discretes=[]) + b = ModelingToolkit.FunctionalAffect(f=(i,u,p,c)->finaled=true, sts=[], pars=[], discretes=[]) + cb2= ModelingToolkit.SymbolicContinuousCallback([x ~ 0.1], Equation[], initialize=a, finalize=b) + @mtkbuild sys = ODESystem(D(x) ~ -1, t, [x], []; continuous_events = [cb1, cb2]) + prob = ODEProblem(sys, [x => 1.0], (0.0, 2), []) + sol = solve(prob, Tsit5()) + @test sol[x][1] ≈ 1.0 + @test sol[x][2] ≈ 1.5 # the initialize affect has been applied + @test seen == true + @test inited == true + @test finaled == true end @testset "Bump" begin From 6424ca9da6597a7fc8c1a6da1dee233535f6e602 Mon Sep 17 00:00:00 2001 From: Benjamin Chung Date: Mon, 28 Oct 2024 18:28:21 -0700 Subject: [PATCH 6/7] Initialize and finalize for discrete callbacks --- src/systems/callbacks.jl | 88 ++++++++++++++++++++++++++++------------ test/symbolic_events.jl | 48 ++++++++++++++++++++++ 2 files changed, 111 insertions(+), 25 deletions(-) diff --git a/src/systems/callbacks.jl b/src/systems/callbacks.jl index 03869e9779..18497a0d1e 100644 --- a/src/systems/callbacks.jl +++ b/src/systems/callbacks.jl @@ -328,13 +328,16 @@ struct SymbolicDiscreteCallback # TODO: Iterative condition::Any affects::Any + initialize::Any + finalize::Any reinitializealg::SciMLBase.DAEInitializationAlgorithm function SymbolicDiscreteCallback( - condition, affects = NULL_AFFECT, reinitializealg = SciMLBase.CheckInit()) + condition, affects = NULL_AFFECT; reinitializealg = SciMLBase.CheckInit(), + initialize=NULL_AFFECT, finalize=NULL_AFFECT) c = scalarize_condition(condition) a = scalarize_affects(affects) - new(c, a, reinitializealg) + new(c, a, scalarize_affects(initialize), scalarize_affects(finalize), reinitializealg) end # Default affect to nothing end @@ -373,11 +376,16 @@ function Base.show(io::IO, db::SymbolicDiscreteCallback) end function Base.:(==)(e1::SymbolicDiscreteCallback, e2::SymbolicDiscreteCallback) - isequal(e1.condition, e2.condition) && isequal(e1.affects, e2.affects) + isequal(e1.condition, e2.condition) && isequal(e1.affects, e2.affects) && + isequal(e1.initialize, e2.initialize) && isequal(e1.finalize, e2.finalize) end function Base.hash(cb::SymbolicDiscreteCallback, s::UInt) s = hash(cb.condition, s) - cb.affects isa AbstractVector ? foldr(hash, cb.affects, init = s) : hash(cb.affects, s) + s = cb.affects isa AbstractVector ? foldr(hash, cb.affects, init = s) : hash(cb.affects, s) + s = cb.initialize isa AbstractVector ? foldr(hash, cb.initialize, init = s) : hash(cb.initialize, s) + s = cb.finalize isa AbstractVector ? foldr(hash, cb.finalize, init = s) : hash(cb.finalize, s) + s = hash(cb.reinitializealg, s) + return s end condition(cb::SymbolicDiscreteCallback) = cb.condition @@ -397,10 +405,23 @@ function reinitialization_algs(cbs::Vector{SymbolicDiscreteCallback}) reinitialization_alg, vcat, cbs, init = SciMLBase.DAEInitializationAlgorithm[]) end + +initialize_affects(cb::SymbolicDiscreteCallback) = cb.initialize +function initialize_affects(cbs::Vector{SymbolicDiscreteCallback}) + mapreduce(initialize_affects, vcat, cbs, init = Equation[]) +end + +finalize_affects(cb::SymbolicDiscreteCallback) = cb.finalize +function finalize_affects(cbs::Vector{SymbolicDiscreteCallback}) + mapreduce(finalize_affects, vcat, cbs, init = Equation[]) +end + function namespace_callback(cb::SymbolicDiscreteCallback, s)::SymbolicDiscreteCallback - af = affects(cb) - af = af isa AbstractVector ? namespace_affect.(af, Ref(s)) : namespace_affect(af, s) - SymbolicDiscreteCallback(namespace_condition(condition(cb), s), af) + function namespace_affects(af) + return af isa AbstractVector ? namespace_affect.(af, Ref(s)) : namespace_affect(af, s) + end + SymbolicDiscreteCallback(namespace_condition(condition(cb), s), namespace_affects(affects(cb)), + reinitializealg=cb.reinitializealg, initialize=namespace_affects(initialize_affects(cb)), finalize=namespace_affects(finalize_affects(cb))) end SymbolicDiscreteCallbacks(cb::Pair) = SymbolicDiscreteCallback[SymbolicDiscreteCallback(cb)] @@ -773,10 +794,10 @@ function generate_vector_rootfinding_callback( let save_idxs = save_idxs if !isnothing(fn.initialize) (i) -> begin + fn.initialize(i) for idx in save_idxs SciMLBase.save_discretes!(i, idx) end - fn.initialize(i) end else (i) -> begin @@ -809,20 +830,13 @@ function compile_affect_fn(cb, sys::AbstractODESystem, dvs, ps, kwargs) eq_aff = affects(cb) eq_neg_aff = affect_negs(cb) affect = compile_affect(eq_aff, cb, sys, dvs, ps; expression = Val{false}, kwargs...) - function compile_optional_affect(aff, default = nothing) - if isnothing(aff) || aff == default - return nothing - else - return compile_affect(aff, cb, sys, dvs, ps; expression = Val{false}, kwargs...) - end - end if eq_neg_aff === eq_aff affect_neg = affect else - affect_neg = compile_optional_affect(eq_neg_aff) + affect_neg = _compile_optional_affect(NULL_AFFECT, eq_neg_aff, cb, sys, dvs, ps; kwargs...) end - initialize = compile_optional_affect(initialize_affects(cb), NULL_AFFECT) - finalize = compile_optional_affect(finalize_affects(cb), NULL_AFFECT) + initialize = _compile_optional_affect(NULL_AFFECT, initialize_affects(cb), cb, sys, dvs, ps; kwargs...) + finalize = _compile_optional_affect(NULL_AFFECT, finalize_affects(cb), cb, sys, dvs, ps; kwargs...) (affect = affect, affect_neg = affect_neg, initialize = initialize, finalize = finalize) end @@ -914,31 +928,48 @@ function compile_affect(affect::FunctionalAffect, cb, sys, dvs, ps; kwargs...) compile_user_affect(affect, cb, sys, dvs, ps; kwargs...) end + +function _compile_optional_affect(default, aff, cb, sys, dvs, ps; kwargs...) + if isnothing(aff) || aff == default + return nothing + else + return compile_affect(aff, cb, sys, dvs, ps; expression = Val{false}, kwargs...) + end +end function generate_timed_callback(cb, sys, dvs, ps; postprocess_affect_expr! = nothing, kwargs...) cond = condition(cb) as = compile_affect(affects(cb), cb, sys, dvs, ps; expression = Val{false}, postprocess_affect_expr!, kwargs...) + + user_initfun = _compile_optional_affect(NULL_AFFECT, initialize_affects(cb), cb, sys, dvs, ps; kwargs...) + user_finfun = _compile_optional_affect(NULL_AFFECT, finalize_affects(cb), cb, sys, dvs, ps; kwargs...) if has_index_cache(sys) && (ic = get_index_cache(sys)) !== nothing && (save_idxs = get(ic.callback_to_clocks, cb, nothing)) !== nothing - initfn = let save_idxs = save_idxs + initfn = let + save_idxs = save_idxs + initfun=user_initfun function (cb, u, t, integrator) + if !isnothing(initfun) + initfun(integrator) + end for idx in save_idxs SciMLBase.save_discretes!(integrator, idx) end end end else - initfn = SciMLBase.INITIALIZE_DEFAULT + initfn = isnothing(user_initfun) ? SciMLBase.INITIALIZE_DEFAULT : (_,_,_,i) -> user_initfun(i) end + finfun = isnothing(user_finfun) ? SciMLBase.FINALIZE_DEFAULT : (_,_,_,i) -> user_finfun(i) if cond isa AbstractVector # Preset Time return PresetTimeCallback( - cond, as; initialize = initfn, initializealg = reinitialization_alg(cb)) + cond, as; initialize = initfn, finalize=finfun, initializealg = reinitialization_alg(cb)) else # Periodic return PeriodicCallback( - as, cond; initialize = initfn, initializealg = reinitialization_alg(cb)) + as, cond; initialize = initfn, finalize=finfun, initializealg = reinitialization_alg(cb)) end end @@ -951,20 +982,27 @@ function generate_discrete_callback(cb, sys, dvs, ps; postprocess_affect_expr! = c = compile_condition(cb, sys, dvs, ps; expression = Val{false}, kwargs...) as = compile_affect(affects(cb), cb, sys, dvs, ps; expression = Val{false}, postprocess_affect_expr!, kwargs...) + + user_initfun = _compile_optional_affect(NULL_AFFECT, initialize_affects(cb), cb, sys, dvs, ps; kwargs...) + user_finfun = _compile_optional_affect(NULL_AFFECT, finalize_affects(cb), cb, sys, dvs, ps; kwargs...) if has_index_cache(sys) && (ic = get_index_cache(sys)) !== nothing && (save_idxs = get(ic.callback_to_clocks, cb, nothing)) !== nothing - initfn = let save_idxs = save_idxs + initfn = let save_idxs = save_idxs, initfun = user_initfun function (cb, u, t, integrator) + if !isnothing(initfun) + initfun(integrator) + end for idx in save_idxs SciMLBase.save_discretes!(integrator, idx) end end end else - initfn = SciMLBase.INITIALIZE_DEFAULT + initfn = isnothing(user_initfun) ? SciMLBase.INITIALIZE_DEFAULT : (_,_,_,i) -> user_initfun(i) end + finfun = isnothing(user_finfun) ? SciMLBase.FINALIZE_DEFAULT : (_,_,_,i) -> user_finfun(i) return DiscreteCallback( - c, as; initialize = initfn, initializealg = reinitialization_alg(cb)) + c, as; initialize = initfn, finalize = finfun, initializealg = reinitialization_alg(cb)) end end diff --git a/test/symbolic_events.jl b/test/symbolic_events.jl index 727e190c72..54180ad7c6 100644 --- a/test/symbolic_events.jl +++ b/test/symbolic_events.jl @@ -1001,6 +1001,54 @@ end @test seen == true @test inited == true @test finaled == true + + #periodic + inited = false + finaled = false + cb3 = ModelingToolkit.SymbolicDiscreteCallback(1.0, [x ~ 2], initialize=a, finalize=b) + @mtkbuild sys = ODESystem(D(x) ~ -1, t, [x], []; discrete_events = [cb3]) + prob = ODEProblem(sys, [x => 1.0], (0.0, 2), []) + sol = solve(prob, Tsit5()) + @test inited == true + @test finaled == true + @test isapprox(sol[x][3], 0.0, atol=1e-9) + @test sol[x][4] ≈ 2.0 + @test sol[x][5] ≈ 1.0 + + + seen = false + inited = false + finaled = false + cb3 = ModelingToolkit.SymbolicDiscreteCallback(1.0, f, initialize=a, finalize=b) + @mtkbuild sys = ODESystem(D(x) ~ -1, t, [x], []; discrete_events = [cb3]) + prob = ODEProblem(sys, [x => 1.0], (0.0, 2), []) + sol = solve(prob, Tsit5()) + @test seen == true + @test inited == true + + #preset + seen = false + inited = false + finaled = false + cb3 = ModelingToolkit.SymbolicDiscreteCallback([1.0], f, initialize=a, finalize=b) + @mtkbuild sys = ODESystem(D(x) ~ -1, t, [x], []; discrete_events = [cb3]) + prob = ODEProblem(sys, [x => 1.0], (0.0, 2), []) + sol = solve(prob, Tsit5()) + @test seen == true + @test inited == true + @test finaled == true + + #equational + seen = false + inited = false + finaled = false + cb3 = ModelingToolkit.SymbolicDiscreteCallback(t == 1.0, f, initialize=a, finalize=b) + @mtkbuild sys = ODESystem(D(x) ~ -1, t, [x], []; discrete_events = [cb3]) + prob = ODEProblem(sys, [x => 1.0], (0.0, 2), []) + sol = solve(prob, Tsit5(); tstops=1.0) + @test seen == true + @test inited == true + @test finaled == true end @testset "Bump" begin From e0e0e460a61b5ccc9c20841745b275338995f090 Mon Sep 17 00:00:00 2001 From: Benjamin Chung Date: Mon, 28 Oct 2024 18:29:20 -0700 Subject: [PATCH 7/7] Format --- src/systems/callbacks.jl | 149 +++++++++++++++++++++++---------------- test/symbolic_events.jl | 60 +++++++++------- 2 files changed, 121 insertions(+), 88 deletions(-) diff --git a/src/systems/callbacks.jl b/src/systems/callbacks.jl index 18497a0d1e..0b758a6e86 100644 --- a/src/systems/callbacks.jl +++ b/src/systems/callbacks.jl @@ -131,7 +131,7 @@ struct SymbolicContinuousCallback finalize = NULL_AFFECT, rootfind = SciMLBase.LeftRootFind, reinitializealg = SciMLBase.CheckInit()) - new(eqs, initialize, finalize, make_affect(affect), + new(eqs, initialize, finalize, make_affect(affect), make_affect(affect_neg), rootfind, reinitializealg) end # Default affect to nothing end @@ -227,18 +227,19 @@ function SymbolicContinuousCallback(args...) end # wrap eq in vector SymbolicContinuousCallback(p::Pair) = SymbolicContinuousCallback(p[1], p[2]) SymbolicContinuousCallback(cb::SymbolicContinuousCallback) = cb # passthrough -function SymbolicContinuousCallback(eqs::Equation, affect = NULL_AFFECT; - initialize=NULL_AFFECT, finalize=NULL_AFFECT, - affect_neg = affect, rootfind = SciMLBase.LeftRootFind) +function SymbolicContinuousCallback(eqs::Equation, affect = NULL_AFFECT; + initialize = NULL_AFFECT, finalize = NULL_AFFECT, + affect_neg = affect, rootfind = SciMLBase.LeftRootFind) SymbolicContinuousCallback( - eqs = [eqs], affect = affect, affect_neg = affect_neg, - initialize=initialize, finalize=finalize, rootfind = rootfind) + eqs = [eqs], affect = affect, affect_neg = affect_neg, + initialize = initialize, finalize = finalize, rootfind = rootfind) end function SymbolicContinuousCallback(eqs::Vector{Equation}, affect = NULL_AFFECT; - affect_neg = affect, initialize=NULL_AFFECT, finalize=NULL_AFFECT, + affect_neg = affect, initialize = NULL_AFFECT, finalize = NULL_AFFECT, rootfind = SciMLBase.LeftRootFind) SymbolicContinuousCallback( - eqs = eqs, affect = affect, affect_neg = affect_neg, initialize=initialize, finalize=finalize, rootfind = rootfind) + eqs = eqs, affect = affect, affect_neg = affect_neg, + initialize = initialize, finalize = finalize, rootfind = rootfind) end SymbolicContinuousCallbacks(cb::SymbolicContinuousCallback) = [cb] @@ -334,10 +335,11 @@ struct SymbolicDiscreteCallback function SymbolicDiscreteCallback( condition, affects = NULL_AFFECT; reinitializealg = SciMLBase.CheckInit(), - initialize=NULL_AFFECT, finalize=NULL_AFFECT) + initialize = NULL_AFFECT, finalize = NULL_AFFECT) c = scalarize_condition(condition) a = scalarize_affects(affects) - new(c, a, scalarize_affects(initialize), scalarize_affects(finalize), reinitializealg) + new(c, a, scalarize_affects(initialize), + scalarize_affects(finalize), reinitializealg) end # Default affect to nothing end @@ -376,14 +378,17 @@ function Base.show(io::IO, db::SymbolicDiscreteCallback) end function Base.:(==)(e1::SymbolicDiscreteCallback, e2::SymbolicDiscreteCallback) - isequal(e1.condition, e2.condition) && isequal(e1.affects, e2.affects) && - isequal(e1.initialize, e2.initialize) && isequal(e1.finalize, e2.finalize) + isequal(e1.condition, e2.condition) && isequal(e1.affects, e2.affects) && + isequal(e1.initialize, e2.initialize) && isequal(e1.finalize, e2.finalize) end function Base.hash(cb::SymbolicDiscreteCallback, s::UInt) s = hash(cb.condition, s) - s = cb.affects isa AbstractVector ? foldr(hash, cb.affects, init = s) : hash(cb.affects, s) - s = cb.initialize isa AbstractVector ? foldr(hash, cb.initialize, init = s) : hash(cb.initialize, s) - s = cb.finalize isa AbstractVector ? foldr(hash, cb.finalize, init = s) : hash(cb.finalize, s) + s = cb.affects isa AbstractVector ? foldr(hash, cb.affects, init = s) : + hash(cb.affects, s) + s = cb.initialize isa AbstractVector ? foldr(hash, cb.initialize, init = s) : + hash(cb.initialize, s) + s = cb.finalize isa AbstractVector ? foldr(hash, cb.finalize, init = s) : + hash(cb.finalize, s) s = hash(cb.reinitializealg, s) return s end @@ -405,7 +410,6 @@ function reinitialization_algs(cbs::Vector{SymbolicDiscreteCallback}) reinitialization_alg, vcat, cbs, init = SciMLBase.DAEInitializationAlgorithm[]) end - initialize_affects(cb::SymbolicDiscreteCallback) = cb.initialize function initialize_affects(cbs::Vector{SymbolicDiscreteCallback}) mapreduce(initialize_affects, vcat, cbs, init = Equation[]) @@ -418,10 +422,13 @@ end function namespace_callback(cb::SymbolicDiscreteCallback, s)::SymbolicDiscreteCallback function namespace_affects(af) - return af isa AbstractVector ? namespace_affect.(af, Ref(s)) : namespace_affect(af, s) + return af isa AbstractVector ? namespace_affect.(af, Ref(s)) : + namespace_affect(af, s) end - SymbolicDiscreteCallback(namespace_condition(condition(cb), s), namespace_affects(affects(cb)), - reinitializealg=cb.reinitializealg, initialize=namespace_affects(initialize_affects(cb)), finalize=namespace_affects(finalize_affects(cb))) + SymbolicDiscreteCallback( + namespace_condition(condition(cb), s), namespace_affects(affects(cb)), + reinitializealg = cb.reinitializealg, initialize = namespace_affects(initialize_affects(cb)), + finalize = namespace_affects(finalize_affects(cb))) end SymbolicDiscreteCallbacks(cb::Pair) = SymbolicDiscreteCallback[SymbolicDiscreteCallback(cb)] @@ -698,7 +705,7 @@ function generate_single_rootfinding_callback( end end user_initfun = isnothing(affect_function.initialize) ? SciMLBase.INITIALIZE_DEFAULT : - (c, u, t, i) -> affect_function.initialize(i) + (c, u, t, i) -> affect_function.initialize(i) if has_index_cache(sys) && (ic = get_index_cache(sys)) !== nothing && (save_idxs = get(ic.callback_to_clocks, cb, nothing)) !== nothing initfn = let save_idxs = save_idxs @@ -715,7 +722,8 @@ function generate_single_rootfinding_callback( return ContinuousCallback( cond, affect_function.affect, affect_function.affect_neg, rootfind = cb.rootfind, initialize = initfn, - finalize = isnothing(affect_function.finalize) ? SciMLBase.FINALIZE_DEFAULT : (c, u, t, i) -> affect_function.finalize(i), + finalize = isnothing(affect_function.finalize) ? SciMLBase.FINALIZE_DEFAULT : + (c, u, t, i) -> affect_function.finalize(i), initializealg = reinitialization_alg(cb)) end @@ -742,8 +750,8 @@ function generate_vector_rootfinding_callback( affect_neg::Union{Function, Nothing}, initialize::Union{Function, Nothing}, finalize::Union{Function, Nothing}}[ - compile_affect_fn(cb, sys, dvs, ps, kwargs) - for cb in cbs] + compile_affect_fn(cb, sys, dvs, ps, kwargs) + for cb in cbs] cond = function (out, u, t, integ) rf_ip(out, u, parameter_values(integ), t) @@ -789,31 +797,37 @@ function generate_vector_rootfinding_callback( end initialize = nothing if has_index_cache(sys) && (ic = get_index_cache(sys)) !== nothing - initialize = handle_optional_setup_fn(map((cb, fn) -> begin - if (save_idxs = get(ic.callback_to_clocks, cb, nothing)) !== nothing - let save_idxs = save_idxs - if !isnothing(fn.initialize) - (i) -> begin - fn.initialize(i) - for idx in save_idxs - SciMLBase.save_discretes!(i, idx) - end - end - else - (i) -> begin - for idx in save_idxs - SciMLBase.save_discretes!(i, idx) + initialize = handle_optional_setup_fn( + map( + (cb, fn) -> begin + if (save_idxs = get(ic.callback_to_clocks, cb, nothing)) !== nothing + let save_idxs = save_idxs + if !isnothing(fn.initialize) + (i) -> begin + fn.initialize(i) + for idx in save_idxs + SciMLBase.save_discretes!(i, idx) + end + end + else + (i) -> begin + for idx in save_idxs + SciMLBase.save_discretes!(i, idx) + end + end end end + else + fn.initialize end - end - else - fn.initialize - end - end, cbs, affect_functions), SciMLBase.INITIALIZE_DEFAULT) - + end, + cbs, + affect_functions), + SciMLBase.INITIALIZE_DEFAULT) + else - initialize = handle_optional_setup_fn(map(fn -> fn.initialize, affect_functions), SciMLBase.INITIALIZE_DEFAULT) + initialize = handle_optional_setup_fn( + map(fn -> fn.initialize, affect_functions), SciMLBase.INITIALIZE_DEFAULT) end finalize = handle_optional_setup_fn( @@ -833,10 +847,13 @@ function compile_affect_fn(cb, sys::AbstractODESystem, dvs, ps, kwargs) if eq_neg_aff === eq_aff affect_neg = affect else - affect_neg = _compile_optional_affect(NULL_AFFECT, eq_neg_aff, cb, sys, dvs, ps; kwargs...) + affect_neg = _compile_optional_affect( + NULL_AFFECT, eq_neg_aff, cb, sys, dvs, ps; kwargs...) end - initialize = _compile_optional_affect(NULL_AFFECT, initialize_affects(cb), cb, sys, dvs, ps; kwargs...) - finalize = _compile_optional_affect(NULL_AFFECT, finalize_affects(cb), cb, sys, dvs, ps; kwargs...) + initialize = _compile_optional_affect( + NULL_AFFECT, initialize_affects(cb), cb, sys, dvs, ps; kwargs...) + finalize = _compile_optional_affect( + NULL_AFFECT, finalize_affects(cb), cb, sys, dvs, ps; kwargs...) (affect = affect, affect_neg = affect_neg, initialize = initialize, finalize = finalize) end @@ -928,7 +945,6 @@ function compile_affect(affect::FunctionalAffect, cb, sys, dvs, ps; kwargs...) compile_user_affect(affect, cb, sys, dvs, ps; kwargs...) end - function _compile_optional_affect(default, aff, cb, sys, dvs, ps; kwargs...) if isnothing(aff) || aff == default return nothing @@ -942,13 +958,15 @@ function generate_timed_callback(cb, sys, dvs, ps; postprocess_affect_expr! = no as = compile_affect(affects(cb), cb, sys, dvs, ps; expression = Val{false}, postprocess_affect_expr!, kwargs...) - user_initfun = _compile_optional_affect(NULL_AFFECT, initialize_affects(cb), cb, sys, dvs, ps; kwargs...) - user_finfun = _compile_optional_affect(NULL_AFFECT, finalize_affects(cb), cb, sys, dvs, ps; kwargs...) + user_initfun = _compile_optional_affect( + NULL_AFFECT, initialize_affects(cb), cb, sys, dvs, ps; kwargs...) + user_finfun = _compile_optional_affect( + NULL_AFFECT, finalize_affects(cb), cb, sys, dvs, ps; kwargs...) if has_index_cache(sys) && (ic = get_index_cache(sys)) !== nothing && (save_idxs = get(ic.callback_to_clocks, cb, nothing)) !== nothing - initfn = let + initfn = let save_idxs = save_idxs - initfun=user_initfun + initfun = user_initfun function (cb, u, t, integrator) if !isnothing(initfun) initfun(integrator) @@ -959,17 +977,21 @@ function generate_timed_callback(cb, sys, dvs, ps; postprocess_affect_expr! = no end end else - initfn = isnothing(user_initfun) ? SciMLBase.INITIALIZE_DEFAULT : (_,_,_,i) -> user_initfun(i) + initfn = isnothing(user_initfun) ? SciMLBase.INITIALIZE_DEFAULT : + (_, _, _, i) -> user_initfun(i) end - finfun = isnothing(user_finfun) ? SciMLBase.FINALIZE_DEFAULT : (_,_,_,i) -> user_finfun(i) + finfun = isnothing(user_finfun) ? SciMLBase.FINALIZE_DEFAULT : + (_, _, _, i) -> user_finfun(i) if cond isa AbstractVector # Preset Time return PresetTimeCallback( - cond, as; initialize = initfn, finalize=finfun, initializealg = reinitialization_alg(cb)) + cond, as; initialize = initfn, finalize = finfun, + initializealg = reinitialization_alg(cb)) else # Periodic return PeriodicCallback( - as, cond; initialize = initfn, finalize=finfun, initializealg = reinitialization_alg(cb)) + as, cond; initialize = initfn, finalize = finfun, + initializealg = reinitialization_alg(cb)) end end @@ -983,8 +1005,10 @@ function generate_discrete_callback(cb, sys, dvs, ps; postprocess_affect_expr! = as = compile_affect(affects(cb), cb, sys, dvs, ps; expression = Val{false}, postprocess_affect_expr!, kwargs...) - user_initfun = _compile_optional_affect(NULL_AFFECT, initialize_affects(cb), cb, sys, dvs, ps; kwargs...) - user_finfun = _compile_optional_affect(NULL_AFFECT, finalize_affects(cb), cb, sys, dvs, ps; kwargs...) + user_initfun = _compile_optional_affect( + NULL_AFFECT, initialize_affects(cb), cb, sys, dvs, ps; kwargs...) + user_finfun = _compile_optional_affect( + NULL_AFFECT, finalize_affects(cb), cb, sys, dvs, ps; kwargs...) if has_index_cache(sys) && (ic = get_index_cache(sys)) !== nothing && (save_idxs = get(ic.callback_to_clocks, cb, nothing)) !== nothing initfn = let save_idxs = save_idxs, initfun = user_initfun @@ -998,11 +1022,14 @@ function generate_discrete_callback(cb, sys, dvs, ps; postprocess_affect_expr! = end end else - initfn = isnothing(user_initfun) ? SciMLBase.INITIALIZE_DEFAULT : (_,_,_,i) -> user_initfun(i) + initfn = isnothing(user_initfun) ? SciMLBase.INITIALIZE_DEFAULT : + (_, _, _, i) -> user_initfun(i) end - finfun = isnothing(user_finfun) ? SciMLBase.FINALIZE_DEFAULT : (_,_,_,i) -> user_finfun(i) + finfun = isnothing(user_finfun) ? SciMLBase.FINALIZE_DEFAULT : + (_, _, _, i) -> user_finfun(i) return DiscreteCallback( - c, as; initialize = initfn, finalize = finfun, initializealg = reinitialization_alg(cb)) + c, as; initialize = initfn, finalize = finfun, + initializealg = reinitialization_alg(cb)) end end diff --git a/test/symbolic_events.jl b/test/symbolic_events.jl index 54180ad7c6..b58d5911f4 100644 --- a/test/symbolic_events.jl +++ b/test/symbolic_events.jl @@ -970,84 +970,90 @@ end @test sol[c] == [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0] end - @testset "Initialization" begin @variables x(t) seen = false - f = ModelingToolkit.FunctionalAffect(f=(i,u,p,c)->seen=true, sts=[], pars=[], discretes=[]) - cb1 = ModelingToolkit.SymbolicContinuousCallback([x ~ 0], Equation[], initialize=[x~1.5], finalize=f) + f = ModelingToolkit.FunctionalAffect( + f = (i, u, p, c) -> seen = true, sts = [], pars = [], discretes = []) + cb1 = ModelingToolkit.SymbolicContinuousCallback( + [x ~ 0], Equation[], initialize = [x ~ 1.5], finalize = f) @mtkbuild sys = ODESystem(D(x) ~ -1, t, [x], []; continuous_events = [cb1]) prob = ODEProblem(sys, [x => 1.0], (0.0, 2), []) - sol = solve(prob, Tsit5(); dtmax=0.01) + sol = solve(prob, Tsit5(); dtmax = 0.01) @test sol[x][1] ≈ 1.0 @test sol[x][2] ≈ 1.5 # the initialize affect has been applied @test seen == true - @variables x(t) seen = false - f = ModelingToolkit.FunctionalAffect(f=(i,u,p,c)->seen=true, sts=[], pars=[], discretes=[]) - cb1 = ModelingToolkit.SymbolicContinuousCallback([x ~ 0], Equation[], initialize=[x~1.5], finalize=f) - inited = false + f = ModelingToolkit.FunctionalAffect( + f = (i, u, p, c) -> seen = true, sts = [], pars = [], discretes = []) + cb1 = ModelingToolkit.SymbolicContinuousCallback( + [x ~ 0], Equation[], initialize = [x ~ 1.5], finalize = f) + inited = false finaled = false - a = ModelingToolkit.FunctionalAffect(f=(i,u,p,c)->inited=true, sts=[], pars=[], discretes=[]) - b = ModelingToolkit.FunctionalAffect(f=(i,u,p,c)->finaled=true, sts=[], pars=[], discretes=[]) - cb2= ModelingToolkit.SymbolicContinuousCallback([x ~ 0.1], Equation[], initialize=a, finalize=b) + a = ModelingToolkit.FunctionalAffect( + f = (i, u, p, c) -> inited = true, sts = [], pars = [], discretes = []) + b = ModelingToolkit.FunctionalAffect( + f = (i, u, p, c) -> finaled = true, sts = [], pars = [], discretes = []) + cb2 = ModelingToolkit.SymbolicContinuousCallback( + [x ~ 0.1], Equation[], initialize = a, finalize = b) @mtkbuild sys = ODESystem(D(x) ~ -1, t, [x], []; continuous_events = [cb1, cb2]) prob = ODEProblem(sys, [x => 1.0], (0.0, 2), []) sol = solve(prob, Tsit5()) @test sol[x][1] ≈ 1.0 @test sol[x][2] ≈ 1.5 # the initialize affect has been applied @test seen == true - @test inited == true + @test inited == true @test finaled == true #periodic - inited = false + inited = false finaled = false - cb3 = ModelingToolkit.SymbolicDiscreteCallback(1.0, [x ~ 2], initialize=a, finalize=b) + cb3 = ModelingToolkit.SymbolicDiscreteCallback( + 1.0, [x ~ 2], initialize = a, finalize = b) @mtkbuild sys = ODESystem(D(x) ~ -1, t, [x], []; discrete_events = [cb3]) prob = ODEProblem(sys, [x => 1.0], (0.0, 2), []) sol = solve(prob, Tsit5()) - @test inited == true + @test inited == true @test finaled == true - @test isapprox(sol[x][3], 0.0, atol=1e-9) + @test isapprox(sol[x][3], 0.0, atol = 1e-9) @test sol[x][4] ≈ 2.0 @test sol[x][5] ≈ 1.0 - seen = false - inited = false + inited = false finaled = false - cb3 = ModelingToolkit.SymbolicDiscreteCallback(1.0, f, initialize=a, finalize=b) + cb3 = ModelingToolkit.SymbolicDiscreteCallback(1.0, f, initialize = a, finalize = b) @mtkbuild sys = ODESystem(D(x) ~ -1, t, [x], []; discrete_events = [cb3]) prob = ODEProblem(sys, [x => 1.0], (0.0, 2), []) sol = solve(prob, Tsit5()) @test seen == true - @test inited == true + @test inited == true #preset seen = false - inited = false + inited = false finaled = false - cb3 = ModelingToolkit.SymbolicDiscreteCallback([1.0], f, initialize=a, finalize=b) + cb3 = ModelingToolkit.SymbolicDiscreteCallback([1.0], f, initialize = a, finalize = b) @mtkbuild sys = ODESystem(D(x) ~ -1, t, [x], []; discrete_events = [cb3]) prob = ODEProblem(sys, [x => 1.0], (0.0, 2), []) sol = solve(prob, Tsit5()) @test seen == true - @test inited == true + @test inited == true @test finaled == true #equational seen = false - inited = false + inited = false finaled = false - cb3 = ModelingToolkit.SymbolicDiscreteCallback(t == 1.0, f, initialize=a, finalize=b) + cb3 = ModelingToolkit.SymbolicDiscreteCallback( + t == 1.0, f, initialize = a, finalize = b) @mtkbuild sys = ODESystem(D(x) ~ -1, t, [x], []; discrete_events = [cb3]) prob = ODEProblem(sys, [x => 1.0], (0.0, 2), []) - sol = solve(prob, Tsit5(); tstops=1.0) + sol = solve(prob, Tsit5(); tstops = 1.0) @test seen == true - @test inited == true + @test inited == true @test finaled == true end