diff --git a/ext/MTKFMIExt.jl b/ext/MTKFMIExt.jl index 1c70a385a6..87ed6662d4 100644 --- a/ext/MTKFMIExt.jl +++ b/ext/MTKFMIExt.jl @@ -235,8 +235,8 @@ function MTK.FMIComponent(::Val{Ver}; fmu = nothing, tolerance = 1e-6, # instance management callback which deallocates the instance when # necessary and notifies the FMU of completed integrator steps - finalize_affect = MTK.FunctionalAffect(fmiFinalize!, [], [wrapper], []) - step_affect = MTK.FunctionalAffect(Returns(nothing), [], [], []) + finalize_affect = MTK.ImperativeAffect(fmiFinalize!; observed = (; wrapper)) + step_affect = MTK.ImperativeAffect(Returns((;))) instance_management_callback = MTK.SymbolicDiscreteCallback( (t == t - 1), step_affect; finalize = finalize_affect, reinitializealg = SciMLBase.NoInit()) @@ -273,7 +273,7 @@ function MTK.FMIComponent(::Val{Ver}; fmu = nothing, tolerance = 1e-6, end initialize_affect = MTK.ImperativeAffect(fmiCSInitialize!; observed = cb_observed, modified = cb_modified, ctx = _functor) - finalize_affect = MTK.FunctionalAffect(fmiFinalize!, [], [wrapper], []) + finalize_affect = MTK.ImperativeAffect(fmiFinalize!; observed = (; wrapper)) # the callback affect performs the stepping step_affect = MTK.ImperativeAffect( fmiCSStep!; observed = cb_observed, modified = cb_modified, ctx = _functor) @@ -708,15 +708,15 @@ end """ $(TYPEDSIGNATURES) -An affect function for use inside a `FunctionalAffect`. This should be triggered at the +An affect function for use inside an `ImperativeAffect`. This should be triggered at the end of the solve, regardless of whether it succeeded or failed. Expects `p` to be a 1-length array containing the index of the instance wrapper (`FMI2InstanceWrapper` or `FMI3InstanceWrapper`) in the parameter object. """ -function fmiFinalize!(integrator, u, p, ctx) - wrapper_idx = p[1] - wrapper = integrator.ps[wrapper_idx] +function fmiFinalize!(m, o, ctx, integrator) + wrapper = o.wrapper reset_instance!(wrapper) + return (;) end """ diff --git a/src/linearization.jl b/src/linearization.jl index 8d6edf8f07..25b42dffb3 100644 --- a/src/linearization.jl +++ b/src/linearization.jl @@ -612,7 +612,7 @@ function markio!(state, orig_inputs, inputs, outputs, disturbances; check = true end (all(values(outputset)) || error( "Some specified outputs were not found in system. The following Dict indicates the found variables ", - outputset)) + outputset)) end state, orig_inputs end diff --git a/src/systems/callbacks.jl b/src/systems/callbacks.jl index 412c4e9bc7..7be1397922 100644 --- a/src/systems/callbacks.jl +++ b/src/systems/callbacks.jl @@ -1,58 +1,7 @@ abstract type AbstractCallback end -struct FunctionalAffect - f::Any - sts::Vector - sts_syms::Vector{Symbol} - pars::Vector - pars_syms::Vector{Symbol} - discretes::Vector - ctx::Any -end - -function FunctionalAffect(f, sts, pars, discretes, ctx = nothing) - # sts & pars contain either pairs: resistor.R => R, or Syms: R - vs = [x isa Pair ? x.first : x for x in sts] - vs_syms = Symbol[x isa Pair ? Symbol(x.second) : getname(x) for x in sts] - length(vs_syms) == length(unique(vs_syms)) || error("Variables are not unique") - - ps = [x isa Pair ? x.first : x for x in pars] - ps_syms = Symbol[x isa Pair ? Symbol(x.second) : getname(x) for x in pars] - length(ps_syms) == length(unique(ps_syms)) || error("Parameters are not unique") - - FunctionalAffect(f, vs, vs_syms, ps, ps_syms, discretes, ctx) -end - -function FunctionalAffect(; f, sts, pars, discretes, ctx = nothing) - FunctionalAffect(f, sts, pars, discretes, ctx) -end - -func(a::FunctionalAffect) = a.f -context(a::FunctionalAffect) = a.ctx -parameters(a::FunctionalAffect) = a.pars -parameters_syms(a::FunctionalAffect) = a.pars_syms -unknowns(a::FunctionalAffect) = a.sts -unknowns_syms(a::FunctionalAffect) = a.sts_syms -discretes(a::FunctionalAffect) = a.discretes - -function Base.:(==)(a1::FunctionalAffect, a2::FunctionalAffect) - isequal(a1.f, a2.f) && isequal(a1.sts, a2.sts) && isequal(a1.pars, a2.pars) && - isequal(a1.sts_syms, a2.sts_syms) && isequal(a1.pars_syms, a2.pars_syms) && - isequal(a1.ctx, a2.ctx) -end - -function Base.hash(a::FunctionalAffect, s::UInt) - s = hash(a.f, s) - s = hash(a.sts, s) - s = hash(a.sts_syms, s) - s = hash(a.pars, s) - s = hash(a.pars_syms, s) - s = hash(a.discretes, s) - hash(a.ctx, s) -end - function has_functional_affect(cb) - (affects(cb) isa FunctionalAffect || affects(cb) isa ImperativeAffect) + affects(cb) isa ImperativeAffect end struct AffectSystem @@ -97,7 +46,7 @@ function Base.hash(a::AffectSystem, s::UInt) hash(aff_to_sys(a), s) end -function vars!(vars, aff::Union{FunctionalAffect, AffectSystem}; op = Differential) +function vars!(vars, aff::AffectSystem; op = Differential) for var in Iterators.flatten((unknowns(aff), parameters(aff), discretes(aff))) vars!(vars, var) end @@ -161,7 +110,7 @@ end ############################### ###### Continuous events ###### ############################### -const Affect = Union{AffectSystem, FunctionalAffect, ImperativeAffect} +const Affect = Union{AffectSystem, ImperativeAffect} """ SymbolicContinuousCallback(eqs::Vector{Equation}, affect = nothing, iv = nothing; @@ -233,7 +182,7 @@ struct SymbolicContinuousCallback <: AbstractCallback conditions = (conditions isa AbstractVector) ? conditions : [conditions] if isnothing(reinitializealg) - if any(a -> (a isa FunctionalAffect || a isa ImperativeAffect), + if any(a -> a isa ImperativeAffect, [affect, affect_neg, initialize, finalize]) reinitializealg = SciMLBase.CheckInit() else @@ -263,8 +212,8 @@ function SymbolicContinuousCallback(cb::Tuple, args...; kwargs...) end make_affect(affect::Nothing; kwargs...) = nothing -make_affect(affect::Tuple; kwargs...) = FunctionalAffect(affect...) -make_affect(affect::NamedTuple; kwargs...) = FunctionalAffect(; affect...) +make_affect(affect::Tuple; kwargs...) = ImperativeAffect(affect...) +make_affect(affect::NamedTuple; kwargs...) = ImperativeAffect(; affect...) make_affect(affect::Affect; kwargs...) = affect function make_affect(affect::Vector{Equation}; discrete_parameters = Any[], @@ -446,7 +395,7 @@ struct SymbolicDiscreteCallback <: AbstractCallback c = is_timed_condition(condition) ? condition : value(scalarize(condition)) if isnothing(reinitializealg) - if any(a -> (a isa FunctionalAffect || a isa ImperativeAffect), + if any(a -> a isa ImperativeAffect, [affect, initialize, finalize]) reinitializealg = SciMLBase.CheckInit() else @@ -498,16 +447,6 @@ end ############################################ ########## Namespacing Utilities ########### ############################################ -function namespace_affects(affect::FunctionalAffect, s) - FunctionalAffect(func(affect), - renamespace.((s,), unknowns(affect)), - unknowns_syms(affect), - renamespace.((s,), parameters(affect)), - parameters_syms(affect), - renamespace.((s,), discretes(affect)), - context(affect)) -end - function namespace_affects(affect::AffectSystem, s) AffectSystem(renamespace(s, system(affect)), renamespace.((s,), unknowns(affect)), @@ -652,36 +591,6 @@ function compile_condition( return CompiledCondition{is_discrete(cbs)}(fs) end -""" -Compile user-defined functional affect. -""" -function compile_functional_affect(affect::FunctionalAffect, sys; kwargs...) - dvs = unknowns(sys) - ps = parameters(sys) - dvs_ind = Dict(reverse(en) for en in enumerate(dvs)) - v_inds = map(sym -> dvs_ind[sym], unknowns(affect)) - - if has_index_cache(sys) && (ic = get_index_cache(sys)) !== nothing - p_inds = [(pind = parameter_index(sys, sym)) === nothing ? sym : pind - for sym in parameters(affect)] - else - ps_ind = Dict(reverse(en) for en in enumerate(ps)) - p_inds = map(sym -> get(ps_ind, sym, sym), parameters(affect)) - end - # HACK: filter out eliminated symbols. Not clear this is the right thing to do - # (MTK should keep these symbols) - u = filter(x -> !isnothing(x[2]), collect(zip(unknowns_syms(affect), v_inds))) |> - NamedTuple - p = filter(x -> !isnothing(x[2]), collect(zip(parameters_syms(affect), p_inds))) |> - NamedTuple - - let u = u, p = p, user_affect = func(affect), ctx = context(affect) - (integ) -> begin - user_affect(integ, u, p, ctx) - end - end -end - is_discrete(cb::AbstractCallback) = cb isa SymbolicDiscreteCallback is_discrete(cb::Vector{<:AbstractCallback}) = eltype(cb) isa SymbolicDiscreteCallback @@ -837,7 +746,7 @@ function compile_affect( elseif aff isa AffectSystem f = compile_equational_affect(aff, sys; kwargs...) wrap_save_discretes(f, save_idxs) - elseif aff isa FunctionalAffect || aff isa ImperativeAffect + elseif aff isa ImperativeAffect f = compile_functional_affect(aff, sys; kwargs...) wrap_save_discretes(f, save_idxs) end @@ -946,7 +855,8 @@ function compile_equational_affect( end else return let dvs_to_update = dvs_to_update, aff_map = aff_map, sys_map = sys_map, - affsys = affsys, ps_to_update = ps_to_update, aff = aff, sys = sys + affsys = affsys, ps_to_update = ps_to_update, aff = aff, sys = sys, + reset_jumps = reset_jumps dvs_to_access = [aff_map[u] for u in unknowns(affsys)] ps_to_access = [unPre(p) for p in parameters(affsys)] @@ -979,6 +889,8 @@ function compile_equational_affect( u_setter!(integ, u_getter(affsol)) p_setter!(integ, p_getter(affsol)) + + reset_jumps && reset_aggregated_jumps!(integ) end end end diff --git a/src/systems/imperative_affect.jl b/src/systems/imperative_affect.jl index 7b05a3ead2..7b1a9fb286 100644 --- a/src/systems/imperative_affect.jl +++ b/src/systems/imperative_affect.jl @@ -28,7 +28,7 @@ Where we use Setfield to copy the tuple `m` with a new value for `x`, then retur `modified`; a runtime error will be produced if a value is written that does not appear in `modified`. The user can dynamically decide not to write a value back by not including it in the returned tuple, in which case the associated field will not be updated. """ -@kwdef struct ImperativeAffect +struct ImperativeAffect f::Any obs::Vector obs_syms::Vector{Symbol} @@ -63,6 +63,9 @@ function ImperativeAffect( ImperativeAffect( f, observed = observed, modified = modified, ctx = ctx, skip_checks = skip_checks) end +function ImperativeAffect(; f, kwargs...) + ImperativeAffect(f; kwargs...) +end function Base.show(io::IO, mfa::ImperativeAffect) obs_vals = join(map((ob, nm) -> "$ob => $nm", mfa.obs, mfa.obs_syms), ", ") @@ -164,7 +167,8 @@ function check_assignable(sys, sym) end end -function compile_functional_affect(affect::ImperativeAffect, sys; kwargs...) +function compile_functional_affect( + affect::ImperativeAffect, sys; reset_jumps = false, kwargs...) #= Implementation sketch: generate observed function (oop), should save to a component array under obs_syms @@ -244,7 +248,7 @@ function compile_functional_affect(affect::ImperativeAffect, sys; kwargs...) upd_funs = NamedTuple{mod_names}((setu.((sys,), first.(mod_pairs))...,)) - let user_affect = func(affect), ctx = context(affect) + let user_affect = func(affect), ctx = context(affect), reset_jumps = reset_jumps @inline function (integ) # update the to-be-mutated values; this ensures that if you do a no-op then nothing happens modvals = mod_og_val_fun(integ.u, integ.p, integ.t) @@ -259,6 +263,8 @@ function compile_functional_affect(affect::ImperativeAffect, sys; kwargs...) # write the new values back to the integrator _generated_writeback(integ, upd_funs, upd_vals) + + reset_jumps && reset_aggregated_jumps!(integ) end end end diff --git a/src/systems/index_cache.jl b/src/systems/index_cache.jl index 948af4dfa5..5ea5fa16a7 100644 --- a/src/systems/index_cache.jl +++ b/src/systems/index_cache.jl @@ -117,8 +117,7 @@ function IndexCache(sys::AbstractSystem) affs = [affs] end for affect in affs - if affect isa AffectSystem || affect isa FunctionalAffect || - affect isa ImperativeAffect + if affect isa AffectSystem || affect isa ImperativeAffect union!(discs, unwrap.(discretes(affect))) elseif isnothing(affect) continue diff --git a/test/funcaffect.jl b/test/funcaffect.jl deleted file mode 100644 index b0745c8a9d..0000000000 --- a/test/funcaffect.jl +++ /dev/null @@ -1,300 +0,0 @@ -using ModelingToolkit, Test, OrdinaryDiffEq -using ModelingToolkitStandardLibrary.Electrical -using ModelingToolkitStandardLibrary.Blocks -using ModelingToolkit: t_nounits as t, D_nounits as D - -@constants h=1 zr=0 -@variables u(t) - -eqs = [D(u) ~ -u] - -affect1!(integ, u, p, ctx) = integ.u[u.u] += 10 - -@named sys = System(eqs, t, [u], [], - discrete_events = [[4.0] => (affect1!, [u], [], [], nothing)]) -prob = ODEProblem(complete(sys), [u => 10.0], (0, 10.0)) -sol = solve(prob, Tsit5()) -i4 = findfirst(==(4.0), sol[:t]) -@test sol.u[i4 + 1][1] > 10.0 - -# callback -cb = ModelingToolkit.SymbolicDiscreteCallback(t == zr, - (f = affect1!, sts = [], pars = [], discretes = [], - ctx = [1])) -cb1 = ModelingToolkit.SymbolicDiscreteCallback(t == zr, (affect1!, [], [], [], [1])) -@test ModelingToolkit.affects(cb) isa ModelingToolkit.FunctionalAffect -@test cb == cb1 -@test ModelingToolkit.SymbolicDiscreteCallback(cb) === cb # passthrough -@test hash(cb) == hash(cb1) -ModelingToolkit.generate_callback(cb, sys); - -cb = ModelingToolkit.SymbolicContinuousCallback([t ~ zr], - (f = affect1!, sts = [], pars = [], discretes = [], - ctx = [1])) -cb1 = ModelingToolkit.SymbolicContinuousCallback([t ~ zr], (affect1!, [], [], [], [1])) -@test cb == cb1 -@test ModelingToolkit.SymbolicContinuousCallback(cb) === cb # passthrough -@test hash(cb) == hash(cb1) - -# named tuple -sys1 = System(eqs, t, [u], [], name = :sys, - discrete_events = [ - [4.0] => (f = affect1!, sts = [u], pars = [], discretes = [], ctx = nothing) - ]) -@test sys == sys1 - -# has_functional_affect -de = ModelingToolkit.get_discrete_events(sys1) -@test length(de) == 1 -de = de[1] -@test ModelingToolkit.conditions(de) == [4.0] -@test ModelingToolkit.has_functional_affect(de) - -sys2 = System(eqs, t, [u], [], name = :sys, - discrete_events = [[4.0] => [u ~ -u * h]]) -@test !ModelingToolkit.has_functional_affect(ModelingToolkit.get_discrete_events(sys2)[1]) - -# context -function affect2!(integ, u, p, ctx) - integ.u[u.u] += ctx[1] - ctx[1] *= 2 -end -ctx1 = [10.0] -@named sys = System(eqs, t, [u], [], - discrete_events = [[4.0, 8.0] => (affect2!, [u], [], [], ctx1)]) -prob = ODEProblem(complete(sys), [u => 10.0], (0, 10.0)) -sol = solve(prob, Tsit5()) -i4 = findfirst(==(4.0), sol[:t]) -@test sol.u[i4 + 1][1] > 10.0 -i8 = findfirst(==(8.0), sol[:t]) -@test sol.u[i8 + 1][1] > 20.0 -@test ctx1[1] == 40.0 - -# parameter -function affect3!(integ, u, p, ctx) - integ.u[u.u] += integ.ps[p.a] - integ.ps[p.a] *= 2 -end - -@parameters a = 10.0 -@named sys = System(eqs, t, [u], [a], - discrete_events = [[4.0, 8.0] => (affect3!, [u], [a], [a], nothing)]) -prob = ODEProblem(complete(sys), [u => 10.0], (0, 10.0)) - -sol = solve(prob, Tsit5()) -i4 = findfirst(==(4.0), sol[:t]) -@test sol.u[i4 + 1][1] > 10.0 -i8 = findfirst(==(8.0), sol[:t]) -@test sol.u[i8 + 1][1] > 20.0 - -# rename parameter -function affect3!(integ, u, p, ctx) - integ.u[u.u] += integ.ps[p.b] - integ.ps[p.b] *= 2 -end - -@named sys = System(eqs, t, [u], [a], - discrete_events = [ - [4.0, 8.0] => (affect3!, [u], [a => :b], [a], nothing) - ]) -prob = ODEProblem(complete(sys), [u => 10.0], (0, 10.0)) - -sol = solve(prob, Tsit5()) -i4 = findfirst(==(4.0), sol[:t]) -@test sol.u[i4 + 1][1] > 10.0 -i8 = findfirst(==(8.0), sol[:t]) -@test sol.u[i8 + 1][1] > 20.0 - -# same name -@variables v(t) -@test_throws ErrorException System(eqs, t, [u], [a], - discrete_events = [ - [4.0, 8.0] => (affect3!, [u, v => :u], [a], [a], - nothing) - ]; name = :sys) - -@test_nowarn System(eqs, t, [u], [a], - discrete_events = [ - [4.0, 8.0] => (affect3!, [u], [a => :u], [a], nothing) - ]; name = :sys) - -@named resistor = System(D(v) ~ v, t, [v], []) - -# nested namespace -ctx = [0] -function affect4!(integ, u, p, ctx) - ctx[1] += 1 - @test u.resistor₊v == 1 -end -s1 = compose( - System(Equation[], t, [], [], name = :s1, - discrete_events = 1.0 => (affect4!, [resistor.v], [], [], ctx)), - resistor) -s2 = structural_simplify(s1) -prob = ODEProblem(s2, [resistor.v => 10.0], (0, 2.01)) -sol = solve(prob, Tsit5()) -@test ctx[1] == 2 - -include("common/rc_model.jl") - -function affect5!(integ, u, p, ctx) - @test integ.u[u.capacitor₊v] ≈ 0.3 - integ.ps[p.C] *= 200 -end - -@unpack capacitor = rc_model -@named event_sys = System(Equation[], t; - continuous_events = [ - [capacitor.v ~ 0.3] => (affect5!, [capacitor.v], - [capacitor.C => :C], [capacitor.C], nothing) - ]) -rc_model = extend(rc_model, event_sys) -# rc_model = compose(rc_model, [resistor, capacitor, source, ground]) - -sys = structural_simplify(rc_model) -u0 = [capacitor.v => 0.0 - capacitor.p.i => 0.0 - resistor.v => 0.0] - -prob = ODEProblem(sys, u0, (0, 10.0)) -sol = solve(prob, Rodas4()) -@test all(sol[rc_model.capacitor.v] .< 0.4) - -# hierarchical - result should be identical - -function affect6!(integ, u, p, ctx) - @test integ.u[u.v] ≈ 0.3 - integ.ps[p.C] *= 200 -end - -function Capacitor2(; name, C = 1.0) - @named oneport = OnePort() - @unpack v, i = oneport - ps = @parameters C = C - eqs = [ - D(v) ~ i / C - ] - extend( - System(eqs, t, [], ps; name = name, - continuous_events = [[v ~ 0.3] => (affect6!, [v], [C], [C], nothing)]), - oneport) -end - -@named begin - capacitor2 = Capacitor2(C = 1.0) - resistor = Resistor(R = 1.0) - capacitor = Capacitor(C = 1.0) - shape = Constant(k = 1.0) - source = Voltage() - ground = Ground() -end - -rc_eqs2 = [connect(shape.output, source.V) - connect(source.p, resistor.p) - connect(resistor.n, capacitor2.p) - connect(capacitor2.n, source.n) - connect(capacitor2.n, ground.g)] - -@named rc_model2 = System(rc_eqs2, t) -rc_model2 = compose(rc_model2, [resistor, capacitor2, shape, source, ground]) - -sys2 = structural_simplify(rc_model2) -u0 = [capacitor2.v => 0.0 - capacitor2.p.i => 0.0 - resistor.v => 0.0] - -prob2 = ODEProblem(sys2, u0, (0, 10.0)) -sol2 = solve(prob2, Rodas4()) -@test all(sol2[rc_model2.capacitor2.v] .== sol[rc_model.capacitor.v]) - -# discrete events - -a7_count = 0 -function affect7!(integ, u, p, ctx) - integ.ps[p.g] = 0 - ctx[1] += 1 - @test ctx[1] <= 2 - @test (ctx[1] == 1 && integ.t == 1.0) || (ctx[1] == 2 && integ.t == 2.0) - global a7_count += 1 -end - -a7_ctx = [0] -function Ball(; name, g = 9.8, anti_gravity_time = 1.0) - pars = @parameters g = g - sts = @variables x(t), v(t) - eqs = [D(x) ~ v, D(v) ~ g] - System(eqs, t, sts, pars; name = name, - discrete_events = [[anti_gravity_time] => (affect7!, [], [g], [g], a7_ctx)]) -end - -@named ball1 = Ball(anti_gravity_time = 1.0) -@named ball2 = Ball(anti_gravity_time = 2.0) - -@named balls = System(Equation[], t) -balls = compose(balls, [ball1, ball2]) - -@test ModelingToolkit.has_discrete_events(balls) -@test length(ModelingToolkit.affects(ModelingToolkit.discrete_events(balls))) == 2 - -prob = ODEProblem(complete(balls), - [ball1.x => 10.0, ball1.v => 0, ball2.x => 10.0, ball2.v => 0], - (0, 3.0)) -sol = solve(prob, Tsit5()) - -@test a7_count == 2 -@test sol(0.99)[1] == sol(0.99)[3] -@test sol(1.01)[4] > sol(1.01)[2] -@test sol(1.99)[2] == sol(1.01)[2] -@test sol(1.99)[4] > sol(1.01)[4] -@test sol(2.5)[4] == sol(3.0)[4] - -# bouncing ball - -# DiffEq implementation -function f_(du, u, p, t) - du[1] = u[2] - du[2] = -p -end - -function condition_(u, t, integrator) # Event when event_f(u,t) == 0 - u[1] -end - -function affect_!(integrator) - integrator.u[2] = -integrator.u[2] -end - -cb_ = ContinuousCallback(condition_, affect_!) - -u0 = [50.0, 0.0] -tspan = (0.0, 15.0) -p = 9.8 -prob_ = ODEProblem(f_, u0, tspan, p) -sol_ = solve(prob_, Tsit5(), callback = cb_) - -# same - with MTK -sts = @variables y(t), v(t) -par = @parameters g = 9.8 -bb_eqs = [D(y) ~ v - D(v) ~ -g] - -function bb_affect!(integ, u, p, ctx) - integ.u[u.v] = -integ.u[u.v] -end - -@named bb_model = System(bb_eqs, t, sts, [par; zr], - continuous_events = [ - [y ~ zr] => (bb_affect!, [v], [], [], nothing) - ]) - -bb_sys = structural_simplify(bb_model) -@test only(ModelingToolkit.affects(ModelingToolkit.continuous_events(bb_sys))) isa - ModelingToolkit.FunctionalAffect - -u0 = [v => 0.0, y => 50.0] - -bb_prob = ODEProblem(bb_sys, u0, (0, 15.0)) -bb_sol = solve(bb_prob, Tsit5()) - -@test bb_sol[y] ≈ map(u -> u[1], sol_.u) -@test bb_sol[v] ≈ map(u -> u[2], sol_.u) diff --git a/test/index_cache.jl b/test/index_cache.jl index 3048084d96..5573563d32 100644 --- a/test/index_cache.jl +++ b/test/index_cache.jl @@ -98,15 +98,17 @@ mutable struct ParamTest end (pt::ParamTest)(x) = pt.y - x @testset "Issue#3215: Callable discrete parameter" begin - function update_affect!(integ, u, p, ctx) - integ.p[p.p_1].y = integ.t + function update_affect!(mod, obs, ctx, integ) + p_1 = mod.p_1 + p_1.y = integ.t + return (; p_1) end tp1 = typeof(ParamTest(1)) @parameters (p_1::tp1)(..) = ParamTest(1) @variables x(ModelingToolkit.t_nounits) = 0 - event1 = [1.0, 2, 3] => (update_affect!, [], [p_1], [p_1], nothing) + event1 = [1.0, 2, 3] => (f = update_affect!, modified = (; p_1)) @named sys = System([ ModelingToolkit.D_nounits(x) ~ p_1(x) diff --git a/test/initializationsystem.jl b/test/initializationsystem.jl index eda381b5a3..cca4be1f76 100644 --- a/test/initializationsystem.jl +++ b/test/initializationsystem.jl @@ -1367,13 +1367,13 @@ end @testset "Issue#3342" begin @variables x(t) y(t) - stop!(integrator, _, _, _) = terminate!(integrator) + stop!(mod, obs, ctx, integrator) = (terminate!(integrator); return (;)) @named sys = System([D(x) ~ 1.0 D(y) ~ 1.0], t; initialization_eqs = [ y ~ 0.0 ], continuous_events = [ - [y ~ 0.5] => (stop!, [y], [], [], nothing) + [y ~ 0.5] => (; f = stop!) ]) sys = structural_simplify(sys) prob0 = ODEProblem(sys, [x => NaN], (0.0, 1.0), []) diff --git a/test/jumpsystem.jl b/test/jumpsystem.jl index a42ba5ecb9..b48b17ce60 100644 --- a/test/jumpsystem.jl +++ b/test/jumpsystem.jl @@ -516,12 +516,12 @@ end @test all(abs.(Xv .- Xact) .<= 0.05 .* Xv) @test all(abs.(Yv .- Yact) .<= 0.05 .* Yv) - function affect!(integ, u, p, ctx) + function affect!(mod, obs, ctx, integ) savevalues!(integ, true) terminate!(integ) - nothing + (;) end - cevents = [t ~ 0.2] => (affect!, [], [], [], nothing) + cevents = [t ~ 0.2] => (; f = affect!) @named jsys = JumpSystem([maj, crj, vrj, eqs[1]], t, [X, Y], [α, β]; continuous_events = cevents) jsys = complete(jsys) diff --git a/test/parameter_dependencies.jl b/test/parameter_dependencies.jl index 5498ecf4d8..1ea796507d 100644 --- a/test/parameter_dependencies.jl +++ b/test/parameter_dependencies.jl @@ -14,10 +14,10 @@ using NonlinearSolve @parameters p1(t)=1.0 p2 @variables x(t) cb1 = SymbolicContinuousCallback([x ~ 2.0] => [p1 ~ 2.0], discrete_parameters = [p1]) # triggers at t=-2+√6 - function affect1!(integ, u, p, ctx) - integ.ps[p[1]] = integ.ps[p[2]] + function affect1!(mod, obs, ctx, integ) + return (; p1 = obs.p2) end - cb2 = [x ~ 4.0] => (affect1!, [], [p1, p2], [p1]) # triggers at t=-2+√7 + cb2 = [x ~ 4.0] => (f = affect1!, observed = (; p2), modified = (; p1)) # triggers at t=-2+√7 cb3 = SymbolicDiscreteCallback([1.0] => [p1 ~ 5.0], discrete_parameters = [p1]) @mtkbuild sys = System( diff --git a/test/runtests.jl b/test/runtests.jl index 426993c414..2014162cc3 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -57,7 +57,6 @@ end @safetestset "DAE Jacobians Test" include("dae_jacobian.jl") @safetestset "Jacobian Sparsity" include("jacobiansparsity.jl") @safetestset "Modelingtoolkitize Test" include("modelingtoolkitize.jl") - @safetestset "FuncAffect Test" include("funcaffect.jl") @safetestset "Constants Test" include("constants.jl") @safetestset "Parameter Dependency Test" include("parameter_dependencies.jl") @safetestset "Equation Type Accessors Test" include("equation_type_accessors.jl") diff --git a/test/symbolic_events.jl b/test/symbolic_events.jl index ee0eaf6392..d3c767ee0d 100644 --- a/test/symbolic_events.jl +++ b/test/symbolic_events.jl @@ -514,11 +514,10 @@ end testsol(ssys3, SDEProblem, RI5, u0, p, tspan; tstops = [1.0], paramtotest = k) # mixing with a func affect - function affect!(integrator, u, p, ctx) - integrator.ps[p.k] = 1.0 - nothing + function affect!(mod, obs, ctx, integ) + return (; k = 1.0) end - cb2‵‵ = [2.0] => (affect!, [], [k], [k], nothing) + cb2‵‵ = [2.0] => (f = affect!, modified = (; k)) @named osys4 = System(eqs, t, [A], [k, t1], discrete_events = [cb1, cb2‵‵]) @named ssys4 = SDESystem(eqs, [0.0], t, [A], [k, t1], discrete_events = [cb1, cb2‵‵]) @@ -527,7 +526,7 @@ end testsol(ssys4, SDEProblem, RI5, u0, p, tspan; tstops = [1.0], paramtotest = k) # mixing with symbolic condition in the func affect - cb2‵‵‵ = (t == t2) => (affect!, [], [k], [k], nothing) + cb2‵‵‵ = (t == t2) => (f = affect!, modified = (; k)) @named osys5 = System(eqs, t, [A], [k, t1, t2], discrete_events = [cb1, cb2‵‵‵]) @named ssys5 = SDESystem(eqs, [0.0], t, [A], [k, t1, t2], discrete_events = [cb1, cb2‵‵‵]) @@ -605,17 +604,15 @@ end testsol(jsys3, u0, p, tspan; tstops = [1.0], rng, paramtotest = k) # mixing with a func affect - function affect!(integrator, u, p, ctx) - integrator.ps[p.k] = 1.0 - reset_aggregated_jumps!(integrator) - nothing + function affect!(mod, obs, ctx, integrator) + return (; k = 1.0) end - cb2‵‵ = [2.0] => (affect!, [], [k], [k], nothing) + cb2‵‵ = [2.0] => (f = affect!, modified = (; k)) @named jsys4 = JumpSystem(eqs, t, [A], [k, t1], discrete_events = [cb1, cb2‵‵]) testsol(jsys4, u0, p, tspan; tstops = [1.0], rng, paramtotest = k) # mixing with symbolic condition in the func affect - cb2‵‵‵ = (t == t2) => (affect!, [], [k], [k], nothing) + cb2‵‵‵ = (t == t2) => (f = affect!, modified = (; k)) @named jsys5 = JumpSystem(eqs, t, [A], [k, t1, t2], discrete_events = [cb1, cb2‵‵‵]) testsol(jsys5, u0, p, tspan; tstops = [1.0, 2.0], rng, paramtotest = k) @named jsys6 = JumpSystem(eqs, t, [A], [k, t1, t2], discrete_events = [cb2‵‵‵, cb1]) @@ -649,13 +646,16 @@ end # baseline affect (pos + neg + left root find) @variables c1(t)=1.0 c2(t)=1.0 # c1 = cos(t), c2 = cos(3t) eqs = [D(c1) ~ -sin(t); D(c2) ~ -3 * sin(3 * t)] - record_crossings(i, u, _, c) = push!(c, i.t => i.u[u.v]) + function record_crossings(mod, obs, ctx, integ) + push!(ctx, integ.t => obs.v) + return (;) + end cr1 = [] cr2 = [] evt1 = ModelingToolkit.SymbolicContinuousCallback( - [c1 ~ 0], (record_crossings, [c1 => :v], [], [], cr1)) + [c1 ~ 0], (f = record_crossings, observed = (; v = c1), ctx = cr1)) evt2 = ModelingToolkit.SymbolicContinuousCallback( - [c2 ~ 0], (record_crossings, [c2 => :v], [], [], cr2)) + [c2 ~ 0], (f = record_crossings, observed = (; v = c2), ctx = cr2)) @named trigsys = System(eqs, t; continuous_events = [evt1, evt2]) trigsys_ss = structural_simplify(trigsys) prob = ODEProblem(trigsys_ss, [], (0.0, 2π)) @@ -673,11 +673,11 @@ end cr1n = [] cr2n = [] evt1 = ModelingToolkit.SymbolicContinuousCallback( - [c1 ~ 0], (record_crossings, [c1 => :v], [], [], cr1p); - affect_neg = (record_crossings, [c1 => :v], [], [], cr1n)) + [c1 ~ 0], (f = record_crossings, observed = (; v = c1), ctx = cr1p); + affect_neg = (f = record_crossings, observed = (; v = c1), ctx = cr1n)) evt2 = ModelingToolkit.SymbolicContinuousCallback( - [c2 ~ 0], (record_crossings, [c2 => :v], [], [], cr2p); - affect_neg = (record_crossings, [c2 => :v], [], [], cr2n)) + [c2 ~ 0], (f = record_crossings, observed = (; v = c2), ctx = cr2p); + affect_neg = (f = record_crossings, observed = (; v = c2), ctx = cr2n)) @named trigsys = System(eqs, t; continuous_events = [evt1, evt2]) trigsys_ss = structural_simplify(trigsys) prob = ODEProblem(trigsys_ss, [], (0.0, 2π)) @@ -699,9 +699,9 @@ end cr1p = [] cr2p = [] evt1 = ModelingToolkit.SymbolicContinuousCallback( - [c1 ~ 0], (record_crossings, [c1 => :v], [], [], cr1p); affect_neg = nothing) + [c1 ~ 0], (f = record_crossings, observed = (; v = c1), ctx = cr1p); affect_neg = nothing) evt2 = ModelingToolkit.SymbolicContinuousCallback( - [c2 ~ 0], (record_crossings, [c2 => :v], [], [], cr2p); affect_neg = nothing) + [c2 ~ 0], (f = record_crossings, observed = (; v = c2), ctx = cr2p); affect_neg = nothing) @named trigsys = System(eqs, t; continuous_events = [evt1, evt2]) trigsys_ss = structural_simplify(trigsys) prob = ODEProblem(trigsys_ss, [], (0.0, 2π)) @@ -717,10 +717,10 @@ end cr1n = [] cr2n = [] evt1 = ModelingToolkit.SymbolicContinuousCallback( - [c1 ~ 0], (record_crossings, [c1 => :v], [], [], cr1p); affect_neg = nothing) + [c1 ~ 0], (f = record_crossings, observed = (; v = c1), ctx = cr1p); affect_neg = nothing) evt2 = ModelingToolkit.SymbolicContinuousCallback( - [c2 ~ 0], (record_crossings, [c2 => :v], [], [], cr2p); - affect_neg = (record_crossings, [c2 => :v], [], [], cr2n)) + [c2 ~ 0], (f = record_crossings, observed = (; v = c2), ctx = cr2p); + affect_neg = (f = record_crossings, observed = (; v = c2), ctx = cr2n)) @named trigsys = System(eqs, t; continuous_events = [evt1, evt2]) trigsys_ss = structural_simplify(trigsys) prob = ODEProblem(trigsys_ss, [], (0.0, 2π)) @@ -740,10 +740,10 @@ end cr1 = [] cr2 = [] evt1 = ModelingToolkit.SymbolicContinuousCallback( - [c1 ~ 0], (record_crossings, [c1 => :v], [], [], cr1); + [c1 ~ 0], (f = record_crossings, observed = (; v = c1), ctx = cr1); rootfind = SciMLBase.RightRootFind) evt2 = ModelingToolkit.SymbolicContinuousCallback( - [c2 ~ 0], (record_crossings, [c2 => :v], [], [], cr2); + [c2 ~ 0], (f = record_crossings, observed = (; v = c2), ctx = cr2); rootfind = SciMLBase.RightRootFind) @named trigsys = System(eqs, t; continuous_events = [evt1, evt2]) trigsys_ss = structural_simplify(trigsys) @@ -760,10 +760,10 @@ end cr1 = [] cr2 = [] evt1 = ModelingToolkit.SymbolicContinuousCallback( - [c1 ~ 0], (record_crossings, [c1 => :v], [], [], cr1); + [c1 ~ 0], (f = record_crossings, observed = (; v = c1), ctx = cr1); rootfind = SciMLBase.LeftRootFind) evt2 = ModelingToolkit.SymbolicContinuousCallback( - [c2 ~ 0], (record_crossings, [c2 => :v], [], [], cr2); + [c2 ~ 0], (f = record_crossings, observed = (; v = c2), ctx = cr2); rootfind = SciMLBase.RightRootFind) @named trigsys = System(eqs, t; continuous_events = [evt1, evt2]) trigsys_ss = structural_simplify(trigsys) @@ -778,10 +778,10 @@ end cr1 = [] cr2 = [] evt1 = ModelingToolkit.SymbolicContinuousCallback( - [c1 ~ 0], (record_crossings, [c1 => :v], [], [], cr1); + [c1 ~ 0], (f = record_crossings, observed = (; v = c1), ctx = cr1); rootfind = SciMLBase.LeftRootFind) evt2 = ModelingToolkit.SymbolicContinuousCallback( - [c2 ~ 0], (record_crossings, [c2 => :v], [], [], cr2); + [c2 ~ 0], (f = record_crossings, observed = (; v = c2), ctx = cr2); rootfind = SciMLBase.RightRootFind) @named trigsys = System(eqs, t; continuous_events = [evt2, evt1]) trigsys_ss = structural_simplify(trigsys) @@ -879,10 +879,10 @@ end @variables x(t) @parameters a(t) b(t) c(t) cb1 = SymbolicContinuousCallback([x ~ 1.0] => [a ~ -Pre(a)], discrete_parameters = [a]) - function save_affect!(integ, u, p, ctx) - integ.ps[p.b] = 5.0 + function save_affect!(mod, obs, ctx, integ) + return (; b = 5.0) end - cb2 = [x ~ 0.5] => (save_affect!, [], [b], [b], nothing) + cb2 = [x ~ 0.5] => (f = save_affect!, modified = (; b)) cb3 = SymbolicDiscreteCallback(1.0 => [c ~ t], discrete_parameters = [c]) @mtkbuild sys = System(D(x) ~ cos(t), t, [x], [a, b, c]; @@ -1067,8 +1067,7 @@ end @testset "Initialization" begin @variables x(t) seen = false - f = ModelingToolkit.FunctionalAffect( - f = (i, u, p, c) -> seen = true, sts = [], pars = [], discretes = []) + f = ModelingToolkit.ImperativeAffect(f = (m, o, ctx, int) -> (seen = true; return (;))) cb1 = ModelingToolkit.SymbolicContinuousCallback( [x ~ 0], nothing, initialize = [x ~ 1.5], finalize = f) @mtkbuild sys = System(D(x) ~ -1, t, [x], []; continuous_events = [cb1]) @@ -1080,16 +1079,13 @@ end @variables x(t) seen = false - f = ModelingToolkit.FunctionalAffect( - f = (i, u, p, c) -> seen = true, sts = [], pars = [], discretes = []) + f = ModelingToolkit.ImperativeAffect(f = (m, o, ctx, int) -> (seen = true; return (;))) cb1 = ModelingToolkit.SymbolicContinuousCallback( [x ~ 0], nothing, 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 = []) + a = ModelingToolkit.ImperativeAffect(f = (m, o, ctx, int) -> (inited = true; return (;))) + b = ModelingToolkit.ImperativeAffect(f = (m, o, ctx, int) -> (finaled = true; return (;))) cb2 = ModelingToolkit.SymbolicContinuousCallback( [x ~ 0.1], nothing, initialize = a, finalize = b) @mtkbuild sys = System(D(x) ~ -1, t, [x], []; continuous_events = [cb1, cb2])