From 186a7d3de1cc87847983096a586e3da83b8b57d5 Mon Sep 17 00:00:00 2001 From: Torkel Loman Date: Sat, 15 Mar 2025 19:38:29 +0000 Subject: [PATCH 01/12] add toplevel arguments --- src/systems/abstractsystem.jl | 22 ++++++++++++---------- src/systems/callbacks.jl | 32 +++++++++++++++++--------------- 2 files changed, 29 insertions(+), 25 deletions(-) diff --git a/src/systems/abstractsystem.jl b/src/systems/abstractsystem.jl index c0b3601e21..010456a094 100644 --- a/src/systems/abstractsystem.jl +++ b/src/systems/abstractsystem.jl @@ -1293,10 +1293,10 @@ Get the unknown variables of the system `sys` and its subsystems. See also [`ModelingToolkit.get_unknowns`](@ref). """ -function unknowns(sys::AbstractSystem) +function unknowns(sys::AbstractSystem; toplevel = false) sts = get_unknowns(sys) systems = get_systems(sys) - nonunique_unknowns = if isempty(systems) + nonunique_unknowns = if toplevel || isempty(systems) sts else system_unknowns = reduce(vcat, namespace_variables.(systems)) @@ -1320,7 +1320,7 @@ Get the parameters of the system `sys` and its subsystems. See also [`@parameters`](@ref) and [`ModelingToolkit.get_ps`](@ref). """ -function parameters(sys::AbstractSystem; initial_parameters = false) +function parameters(sys::AbstractSystem; initial_parameters = false, toplevel = false) ps = get_ps(sys) if ps == SciMLBase.NullParameters() return [] @@ -1329,8 +1329,8 @@ function parameters(sys::AbstractSystem; initial_parameters = false) ps = first.(ps) end systems = get_systems(sys) - result = unique(isempty(systems) ? ps : - [ps; reduce(vcat, namespace_parameters.(systems))]) + result = unique(toplevel || isempty(systems) ? + ps : [ps; reduce(vcat, namespace_parameters.(systems))]) if !initial_parameters if is_time_dependent(sys) # time-dependent systems have `Initial` parameters for all their @@ -1490,8 +1490,9 @@ function controls(sys::AbstractSystem) isempty(systems) ? ctrls : [ctrls; reduce(vcat, namespace_controls.(systems))] end -function observed(sys::AbstractSystem) +function observed(sys::AbstractSystem; toplevel = false) obs = get_observed(sys) + toplevel && return obs systems = get_systems(sys) [obs; reduce(vcat, @@ -1510,7 +1511,7 @@ If they are not explicitly provided, variables and parameters are initialized to See also [`initialization_equations`](@ref), [`parameter_dependencies`](@ref) and [`ModelingToolkit.get_defaults`](@ref). """ -function defaults(sys::AbstractSystem) +function defaults(sys::AbstractSystem; toplevel = false) systems = get_systems(sys) defs = get_defaults(sys) # `mapfoldr` is really important!!! We should prefer the base model for @@ -1519,7 +1520,8 @@ function defaults(sys::AbstractSystem) # `compose(ODESystem(...; defaults=defs), ...)` # # Thus, right associativity is required and crucial for correctness. - isempty(systems) ? defs : mapfoldr(namespace_defaults, merge, systems; init = defs) + (toplevel ||isempty(systems)) ? + defs : mapfoldr(namespace_defaults, merge, systems; init = defs) end function defaults_and_guesses(sys::AbstractSystem) @@ -1549,10 +1551,10 @@ It is often the most useful way to inspect the equations of a system. See also [`full_equations`](@ref) and [`ModelingToolkit.get_eqs`](@ref). """ -function equations(sys::AbstractSystem) +function equations(sys::AbstractSystem; toplevel = false) eqs = get_eqs(sys) systems = get_systems(sys) - if isempty(systems) + if toplevel || isempty(systems) return eqs else eqs = Equation[eqs; diff --git a/src/systems/callbacks.jl b/src/systems/callbacks.jl index 5cefe65651..5b9dab6fb0 100644 --- a/src/systems/callbacks.jl +++ b/src/systems/callbacks.jl @@ -94,17 +94,17 @@ A [`ContinuousCallback`](@ref SciMLBase.ContinuousCallback) specified symbolical as well as the positive-edge `affect` and negative-edge `affect_neg` that apply when *any* of `eq` are satisfied. By default `affect_neg = affect`; to only get rising edges specify `affect_neg = nothing`. -Assume without loss of generality that the equation is of the form `c(u,p,t) ~ 0`; we denote the integrator state as `i.u`. +Assume without loss of generality that the equation is of the form `c(u,p,t) ~ 0`; we denote the integrator state as `i.u`. For compactness, we define `prev_sign = sign(c(u[t-1], p[t-1], t-1))` and `cur_sign = sign(c(u[t], p[t], t))`. -A condition edge will be detected and the callback will be invoked iff `prev_sign * cur_sign <= 0`. +A condition edge will be detected and the callback will be invoked iff `prev_sign * cur_sign <= 0`. The positive edge `affect` will be triggered iff an edge is detected and if `prev_sign < 0`; similarly, `affect_neg` will be -triggered iff an edge is detected and `prev_sign > 0`. +triggered iff an edge is detected and `prev_sign > 0`. -Inter-sample condition activation is not guaranteed; for example if we use the dirac delta function as `c` to insert a +Inter-sample condition activation is not guaranteed; for example if we use the dirac delta function as `c` to insert a sharp discontinuity between integrator steps (which in this example would not normally be identified by adaptivity) then the condition is not guaranteed to be triggered. -Once detected the integrator will "wind back" through a root-finding process to identify the point when the condition became active; the method used +Once detected the integrator will "wind back" through a root-finding process to identify the point when the condition became active; the method used is specified by `rootfind` from [`SciMLBase.RootfindOpt`](@ref). If we denote the time when the condition becomes active as `tc`, the value in the integrator after windback will be: * `u[tc-epsilon], p[tc-epsilon], tc` if `LeftRootFind` is used, @@ -116,7 +116,7 @@ it passed through 0. Multiple callbacks in the same system with different `rootfind` operations will be grouped by their `rootfind` value into separate VectorContinuousCallbacks in the enumeration order of `SciMLBase.RootfindOpt`. This may cause some callbacks to not fire if several become -active at the same instant. See the `SciMLBase` documentation for more information on the semantic rules. +active at the same instant. See the `SciMLBase` documentation for more information on the semantic rules. Affects (i.e. `affect` and `affect_neg`) can be specified as either: * A list of equations that should be applied when the callback is triggered (e.g. `x ~ 3, y ~ 7`) which must be of the form `unknown ~ observed value` where each `unknown` appears only once. Equations will be applied in the order that they appear in the vector; parameters and state updates will become immediately visible to following equations. @@ -327,14 +327,15 @@ The `SymbolicContinuousCallback`s in the returned vector are structs with two fi `affect` which correspond to the first and second elements of a `Pair` used to define an event, i.e. `eqs => affect`. """ -function continuous_events(sys::AbstractSystem) - obs = get_continuous_events(sys) - filter(!isempty, obs) +function continuous_events(sys::AbstractSystem; toplevel = false) + cbs = get_continuous_events(sys) + filter(!isempty, cbs) + toplevel && return cbs systems = get_systems(sys) - cbs = [obs; + cbs = [cbs; reduce(vcat, - (map(o -> namespace_callback(o, s), continuous_events(s)) + (map(cb -> namespace_callback(cb, s), continuous_events(s)) for s in systems), init = SymbolicContinuousCallback[])] filter(!isempty, cbs) @@ -482,10 +483,11 @@ The `SymbolicDiscreteCallback`s in the returned vector are structs with two fiel `affect` which correspond to the first and second elements of a `Pair` used to define an event, i.e. `condition => affect`. """ -function discrete_events(sys::AbstractSystem) - obs = get_discrete_events(sys) +function discrete_events(sys::AbstractSystem; toplevel = false) + cbs = get_discrete_events(sys) + toplevel && return cbs systems = get_systems(sys) - cbs = [obs; + cbs = [cbs; reduce(vcat, (map(o -> namespace_callback(o, s), discrete_events(s)) for s in systems), init = SymbolicDiscreteCallback[])] @@ -688,7 +690,7 @@ function generate_rootfinding_callback(sys::AbstractTimeDependentSystem, generate_rootfinding_callback(cbs, sys, dvs, ps; kwargs...) end """ -Generate a single rootfinding callback; this happens if there is only one equation in `cbs` passed to +Generate a single rootfinding callback; this happens if there is only one equation in `cbs` passed to generate_rootfinding_callback and thus we can produce a ContinuousCallback instead of a VectorContinuousCallback. """ function generate_single_rootfinding_callback( From 1d872e04a7e727cf4f2e4d6c1bc59bb5748339e8 Mon Sep 17 00:00:00 2001 From: Torkel Loman Date: Sun, 16 Mar 2025 12:11:54 +0000 Subject: [PATCH 02/12] update with aayush code to handle flattened systems --- src/systems/abstractsystem.jl | 29 +++++++++++++++++++++++++++++ src/systems/callbacks.jl | 12 ++++++++++-- 2 files changed, 39 insertions(+), 2 deletions(-) diff --git a/src/systems/abstractsystem.jl b/src/systems/abstractsystem.jl index 010456a094..6f72991297 100644 --- a/src/systems/abstractsystem.jl +++ b/src/systems/abstractsystem.jl @@ -1292,8 +1292,12 @@ $(TYPEDSIGNATURES) Get the unknown variables of the system `sys` and its subsystems. See also [`ModelingToolkit.get_unknowns`](@ref). + +Arguments: +- `toplevel = false`: if set to true, do not return the continuous events of the subsystems. """ function unknowns(sys::AbstractSystem; toplevel = false) + toplevel && (sys = recursive_get_parent(sys)) sts = get_unknowns(sys) systems = get_systems(sys) nonunique_unknowns = if toplevel || isempty(systems) @@ -1319,8 +1323,12 @@ $(TYPEDSIGNATURES) Get the parameters of the system `sys` and its subsystems. See also [`@parameters`](@ref) and [`ModelingToolkit.get_ps`](@ref). + +Arguments: +- `toplevel = false`: if set to true, do not return the continuous events of the subsystems. """ function parameters(sys::AbstractSystem; initial_parameters = false, toplevel = false) + toplevel && (sys = recursive_get_parent(sys)) ps = get_ps(sys) if ps == SciMLBase.NullParameters() return [] @@ -1353,6 +1361,22 @@ function dependent_parameters(sys::AbstractSystem) return map(eq -> eq.lhs, parameter_dependencies(sys)) end +""" + recursive_get_parent(sys::AbstractSystem) + +Loops through parent systems to find the original parent system. + +Warning: +- Curently only used (and tested) in the context of accessor functions (e.g. `parameters`), +specifically in the context of the `toplevel` keyword argument. +""" +function recursive_get_parent(sys::AbstractSystem) + if ModelingToolkit.has_parent(sys) && (p = ModelingToolkit.get_parent(sys)) !== nothing + return recursive_get_parent(p) + end + return sys +end + """ $(TYPEDSIGNATURES) Get the parameter dependencies of the system `sys` and its subsystems. @@ -1491,6 +1515,7 @@ function controls(sys::AbstractSystem) end function observed(sys::AbstractSystem; toplevel = false) + toplevel && (sys = recursive_get_parent(sys)) obs = get_observed(sys) toplevel && return obs systems = get_systems(sys) @@ -1550,8 +1575,12 @@ It may include some abbreviations and aliases of observables. It is often the most useful way to inspect the equations of a system. See also [`full_equations`](@ref) and [`ModelingToolkit.get_eqs`](@ref). + +Arguments: +- `toplevel = false`: if set to true, do not return the continuous events of the subsystems. """ function equations(sys::AbstractSystem; toplevel = false) + toplevel && (sys = recursive_get_parent(sys)) eqs = get_eqs(sys) systems = get_systems(sys) if toplevel || isempty(systems) diff --git a/src/systems/callbacks.jl b/src/systems/callbacks.jl index 5b9dab6fb0..715b12999a 100644 --- a/src/systems/callbacks.jl +++ b/src/systems/callbacks.jl @@ -320,14 +320,18 @@ function namespace_callback(cb::SymbolicContinuousCallback, s)::SymbolicContinuo end """ - continuous_events(sys::AbstractSystem)::Vector{SymbolicContinuousCallback} + continuous_events(sys::AbstractSystem; toplevel = false)::Vector{SymbolicContinuousCallback} Returns a vector of all the `continuous_events` in an abstract system and its component subsystems. The `SymbolicContinuousCallback`s in the returned vector are structs with two fields: `eqs` and `affect` which correspond to the first and second elements of a `Pair` used to define an event, i.e. `eqs => affect`. + +Arguments: +- `toplevel = false`: if set to true, do not return the continuous events of the subsystems. """ function continuous_events(sys::AbstractSystem; toplevel = false) + toplevel && (sys = recursive_get_parent(sys)) cbs = get_continuous_events(sys) filter(!isempty, cbs) toplevel && return cbs @@ -476,14 +480,18 @@ SymbolicDiscreteCallbacks(cbs::Vector{<:SymbolicDiscreteCallback}) = cbs SymbolicDiscreteCallbacks(::Nothing) = SymbolicDiscreteCallback[] """ - discrete_events(sys::AbstractSystem) :: Vector{SymbolicDiscreteCallback} + discrete_events(sys::AbstractSystem; toplevel = false) :: Vector{SymbolicDiscreteCallback} Returns a vector of all the `discrete_events` in an abstract system and its component subsystems. The `SymbolicDiscreteCallback`s in the returned vector are structs with two fields: `condition` and `affect` which correspond to the first and second elements of a `Pair` used to define an event, i.e. `condition => affect`. + +Arguments: +- `toplevel = false`: if set to true, do not return the discrete events of the subsystems. """ function discrete_events(sys::AbstractSystem; toplevel = false) + toplevel && (sys = recursive_get_parent(sys)) cbs = get_discrete_events(sys) toplevel && return cbs systems = get_systems(sys) From 06419376dcfea0105e68bdacb5121365b7f08f8a Mon Sep 17 00:00:00 2001 From: Torkel Loman Date: Sun, 16 Mar 2025 12:11:59 +0000 Subject: [PATCH 03/12] add tests --- test/accessor_functions.jl | 142 +++++++++++++++++++++++++++++++++++++ test/runtests.jl | 1 + 2 files changed, 143 insertions(+) create mode 100644 test/accessor_functions.jl diff --git a/test/accessor_functions.jl b/test/accessor_functions.jl new file mode 100644 index 0000000000..78c00b00fc --- /dev/null +++ b/test/accessor_functions.jl @@ -0,0 +1,142 @@ +### Preparations ### + +# Fetch packages. +using ModelingToolkit, Test +using ModelingToolkit: t_nounits as t, D_nounits as D +import ModelingToolkit: get_ps, get_unknowns, get_observed, get_eqs, get_continuous_events, + get_discrete_events, namespace_equations + +# Creates helper functions. +function all_sets_equal(args...) + for arg in args[2:end] + issetequal(args[1], arg) || return false + end + return true +end +function sym_issubset(set1, set2) + for sym1 in set1 + any(isequal(sym1, sym2) for sym2 in set2) || return false + end + return true +end + +### Basic Tests ### + +# Checks `toplevel = false` argument for various accessors (currently only for `ODESystem`s). +# Compares to `toplevel = true` version, and `get_` functions. +# Checks accessors for parameters, unknowns, equations, observables, and events. +let + # Prepares model components. + @parameters p_top p_mid1 p_mid2 p_bot d + @variables X_top(t) X_mid1(t) X_mid2(t) X_bot(t) Y(t) O(t) + + # Creates the systems (individual and hierarchical). + eqs_top = [ + D(X_top) ~ p_top - d*X_top, + D(Y) ~ log(X_top) - Y^2 + 3.0, + O ~ (p_top + d)*X_top + Y + ] + eqs_mid1 = [ + D(X_mid1) ~ p_mid1 - d*X_mid1^2, + D(Y) ~ D(X_mid1) - Y^3, + O ~ (p_mid1 + d)*X_mid1 + Y + ] + eqs_mid2 = [ + D(X_mid2) ~ p_mid2 - d*X_mid2, + X_mid2^3 ~ log(X_mid2 + Y) - Y^2 + 3.0, + O ~ (p_mid2 + d)*X_mid2 + Y + ] + eqs_bot = [ + D(X_bot) ~ p_bot - d*X_bot, + D(Y) ~ -Y^3, + O ~ (p_bot + d)*X_bot + Y + ] + cevs = [[t ~ 1.0] => [Y ~ Y + 2.0]] + devs = [(t == 2.0) => [Y ~ Y + 2.0]] + @named sys_bot = ODESystem(eqs_bot, t; systems = [], continuous_events = cevs, discrete_events = devs) + @named sys_mid2 = ODESystem(eqs_mid2, t; systems = [], continuous_events = cevs, discrete_events = devs) + @named sys_mid1 = ODESystem(eqs_mid1, t; systems = [sys_bot], continuous_events = cevs, discrete_events = devs) + @named sys_top = ODESystem(eqs_top, t; systems = [sys_mid1, sys_mid2], continuous_events = cevs, discrete_events = devs) + sys_bot_comp = complete(sys_bot) + sys_mid2_comp = complete(sys_mid2) + sys_mid1_comp = complete(sys_mid1) + sys_top_comp = complete(sys_top) + sys_bot_ss = structural_simplify(sys_bot) + sys_mid2_ss = structural_simplify(sys_mid2) + sys_mid1_ss = structural_simplify(sys_mid1) + sys_top_ss = structural_simplify(sys_top) + + # Checks `parameters` for `toplevel = false`. + @test all_sets_equal(parameters.([sys_bot, sys_bot_comp, sys_bot_ss])..., [d, p_bot]) + @test all_sets_equal(parameters.([sys_mid1, sys_mid1_comp, sys_mid1_ss])..., [d, p_mid1, sys_bot.d, sys_bot.p_bot]) + @test all_sets_equal(parameters.([sys_mid2, sys_mid2_comp, sys_mid2_ss])..., [d, p_mid2]) + @test all_sets_equal(parameters.([sys_top, sys_top_comp, sys_top_ss])..., [d, p_top, sys_mid1.d, sys_mid1.p_mid1, sys_mid1.sys_bot.d, sys_mid1.sys_bot.p_bot, sys_mid2.d, sys_mid2.p_mid2]) + + # Checks `parameters`` for `toplevel = true`. Compares to known parameters and also checks that + # these are subset of what `get_ps` returns. + @test all_sets_equal(parameters.([sys_bot, sys_bot_comp, sys_bot_ss]; toplevel = true)..., [d, p_bot]) + @test_broken all_sets_equal(parameters.([sys_mid1, sys_mid1_comp, sys_mid1_ss]; toplevel = true)..., [d, p_mid1]) + @test all_sets_equal(parameters.([sys_mid2, sys_mid2_comp, sys_mid2_ss]; toplevel = true)..., [d, p_mid2]) + @test_broken all_sets_equal(parameters.([sys_top, sys_top_comp, sys_top_ss]; toplevel = true)..., [d, p_top]) + @test all(sym_issubset(parameters(sys; toplevel = true), get_ps(sys)) for sys in [sys_bot, sys_mid2, sys_mid1, sys_top]) + + # Checks `unknowns` for `toplevel = false`. O(t) is eliminated by `structural_simplify` and + # must be considered separately. + @test all_sets_equal(unknowns.([sys_bot, sys_bot_comp])..., [O, Y, X_bot]) + @test all_sets_equal(unknowns.([sys_bot_ss])..., [Y, X_bot]) + @test all_sets_equal(unknowns.([sys_mid1, sys_mid1_comp])..., [O, Y, X_mid1, sys_bot.Y, sys_bot.O, sys_bot.X_bot]) + @test all_sets_equal(unknowns.([sys_mid1_ss])..., [Y, X_mid1, sys_bot.Y, sys_bot.X_bot]) + @test all_sets_equal(unknowns.([sys_mid2, sys_mid2_comp])..., [O, Y, X_mid2]) + @test all_sets_equal(unknowns.([sys_mid2_ss])..., [Y, X_mid2]) + @test all_sets_equal(unknowns.([sys_top, sys_top_comp])..., [O, Y, X_top, sys_mid1.O, sys_mid1.Y, sys_mid1.X_mid1, sys_mid1.sys_bot.O, sys_mid1.sys_bot.Y, sys_mid1.sys_bot.X_bot, sys_mid2.O, sys_mid2.Y, sys_mid2.X_mid2]) + @test all_sets_equal(unknowns.([sys_top_ss])..., [Y, X_top, sys_mid1.Y, sys_mid1.X_mid1, sys_mid1.sys_bot.Y, sys_mid1.sys_bot.X_bot, sys_mid2.Y, sys_mid2.X_mid2]) + + # Checks `unknowns` for `toplevel = true`. Note that O is not eliminated here (as we go back + # to original parent system). Also checks that outputs are subsets of what `get_ps` returns.. + @test all_sets_equal(unknowns.([sys_bot, sys_bot_comp, sys_bot_ss]; toplevel = true)..., [O, Y, X_bot]) + @test all_sets_equal(unknowns.([sys_mid1, sys_mid1_comp]; toplevel = true)..., [O, Y, X_mid1]) + @test all_sets_equal(unknowns.([sys_mid2, sys_mid2_comp]; toplevel = true)..., [O, Y, X_mid2]) + @test all_sets_equal(unknowns.([sys_top, sys_top_comp]; toplevel = true)..., [O, Y, X_top]) + @test all(sym_issubset(unknowns(sys; toplevel = true), get_unknowns(sys)) for sys in [sys_bot, sys_mid1, sys_mid2, sys_top]) + + # Checks `equations` for `toplevel = false`. Do not check ss equations as these might potentially + # be structurally simplified to new equations. + @test all_sets_equal(equations.([sys_bot, sys_bot_comp])..., eqs_bot) + @test all_sets_equal(equations.([sys_mid1, sys_mid1_comp])..., [eqs_mid1; namespace_equations(sys_bot)]) + @test all_sets_equal(equations.([sys_mid2, sys_mid2_comp])..., eqs_mid2) + @test all_sets_equal(equations.([sys_top, sys_top_comp])..., [eqs_top; namespace_equations(sys_mid1); namespace_equations(sys_mid2)]) + + # Checks `equations` for `toplevel = true`. Do not check ss equations directly as these + # might potentially be structurally simplified to new equations. + @test all_sets_equal(equations.([sys_bot, sys_bot_comp]; toplevel = true)..., eqs_bot) + @test all_sets_equal(equations.([sys_mid1, sys_mid1_comp]; toplevel = true)..., eqs_mid1) + @test all_sets_equal(equations.([sys_mid2, sys_mid2_comp]; toplevel = true)..., eqs_mid2) + @test all_sets_equal(equations.([sys_top, sys_top_comp]; toplevel = true)..., eqs_top) + @test all(sym_issubset(equations(sys; toplevel = true), get_eqs(sys)) for sys in [sys_bot, sys_mid2, sys_mid1, sys_top]) + + # Checks `observed for `toplevel = false`. For non-ss systems, there are no observables. + @test all_sets_equal(observed.([sys_bot, sys_bot_comp, sys_mid1, sys_mid1_comp, sys_mid2, sys_mid2_comp, sys_top, sys_top_comp])..., []) + @test issetequal(observed(sys_bot_ss), eqs_bot[3:3]) + @test issetequal(observed(sys_mid1_ss), [eqs_mid1[3:3]; namespace_equations(sys_bot)[3:3]]) + @test issetequal(observed(sys_mid2_ss), eqs_mid2[3:3]) + @test issetequal(observed(sys_top_ss), [eqs_top[3:3]; namespace_equations(sys_mid1)[3:3:6]; namespace_equations(sys_mid2)[3:3]]) + + # Checks `observed` for `toplevel = true`. Again, for non-ss systems, there are no observables. + # Also checks that `toplevel = true` yields subset of `get_observed`. + @test all_sets_equal(observed.([sys_bot, sys_bot_comp, sys_mid1, sys_mid1_comp, sys_mid2, sys_mid2_comp, sys_top, sys_top_comp]; toplevel = true)..., []) + @test_broken issetequal(observed(sys_bot_ss; toplevel = true), eqs_bot[3:3]) + @test_broken issetequal(observed(sys_mid1_ss; toplevel = true), eqs_mid1[3:3]) + @test_broken issetequal(observed(sys_mid2_ss; toplevel = true), eqs_mid2[3:3]) + @test_broken issetequal(observed(sys_top_ss; toplevel = true), eqs_top[3:3]) + @test all(sym_issubset(observed(sys; toplevel = true), get_observed(sys)) for sys in [sys_bot, sys_mid2, sys_mid1, sys_top]) + + # Checks `continuous_events` and `discrete_events` for `toplevel = true` (more straightforward + # as I stored the same singe event in all systems). Don't check for `toplevel = false` as + # technically not needed for these tests and name spacing the events is a mess. + mtk_cev = ModelingToolkit.SymbolicContinuousCallback.(cevs)[1] + mtk_dev = ModelingToolkit.SymbolicDiscreteCallback.(devs)[1] + @test all_sets_equal(continuous_events.([sys_bot, sys_bot_comp, sys_bot_ss, sys_mid1, sys_mid1_comp, sys_mid1_ss, sys_mid2, sys_mid2_comp, sys_mid2_ss, sys_top, sys_top_comp, sys_top_ss]; toplevel = true)..., [mtk_cev]) + @test all_sets_equal(discrete_events.([sys_bot, sys_bot_comp, sys_bot_ss, sys_mid1, sys_mid1_comp, sys_mid1_ss, sys_mid2, sys_mid2_comp, sys_mid2_ss, sys_top, sys_top_comp, sys_top_ss]; toplevel = true)..., [mtk_dev]) + @test all(sym_issubset(continuous_events(sys; toplevel = true), get_continuous_events(sys)) for sys in [sys_bot, sys_mid2, sys_mid1, sys_top]) + @test all(sym_issubset(discrete_events(sys; toplevel = true), get_discrete_events(sys)) for sys in [sys_bot, sys_mid2, sys_mid1, sys_top]) +end diff --git a/test/runtests.jl b/test/runtests.jl index e0ef4b8640..4537c27255 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -61,6 +61,7 @@ end @safetestset "Constants Test" include("constants.jl") @safetestset "Parameter Dependency Test" include("parameter_dependencies.jl") @safetestset "Equation Type Accessors Test" include("equation_type_accessors.jl") + @safetestset "System Accessor Functions Test" include("accessor_functions.jl") @safetestset "Equations with complex values" include("complex.jl") end end From 78672024c585e6fd320d502023a99e93c11263c6 Mon Sep 17 00:00:00 2001 From: Torkel Loman Date: Sun, 16 Mar 2025 12:19:50 +0000 Subject: [PATCH 04/12] formatting --- src/systems/abstractsystem.jl | 4 +- test/accessor_functions.jl | 142 ++++++++++++++++++++++++---------- 2 files changed, 101 insertions(+), 45 deletions(-) diff --git a/src/systems/abstractsystem.jl b/src/systems/abstractsystem.jl index 6f72991297..969f5d89ec 100644 --- a/src/systems/abstractsystem.jl +++ b/src/systems/abstractsystem.jl @@ -1545,8 +1545,8 @@ function defaults(sys::AbstractSystem; toplevel = false) # `compose(ODESystem(...; defaults=defs), ...)` # # Thus, right associativity is required and crucial for correctness. - (toplevel ||isempty(systems)) ? - defs : mapfoldr(namespace_defaults, merge, systems; init = defs) + (toplevel || isempty(systems)) ? + defs : mapfoldr(namespace_defaults, merge, systems; init = defs) end function defaults_and_guesses(sys::AbstractSystem) diff --git a/test/accessor_functions.jl b/test/accessor_functions.jl index 78c00b00fc..2402c53da7 100644 --- a/test/accessor_functions.jl +++ b/test/accessor_functions.jl @@ -4,7 +4,7 @@ using ModelingToolkit, Test using ModelingToolkit: t_nounits as t, D_nounits as D import ModelingToolkit: get_ps, get_unknowns, get_observed, get_eqs, get_continuous_events, - get_discrete_events, namespace_equations + get_discrete_events, namespace_equations # Creates helper functions. function all_sets_equal(args...) @@ -32,31 +32,35 @@ let # Creates the systems (individual and hierarchical). eqs_top = [ - D(X_top) ~ p_top - d*X_top, + D(X_top) ~ p_top - d * X_top, D(Y) ~ log(X_top) - Y^2 + 3.0, - O ~ (p_top + d)*X_top + Y + O ~ (p_top + d) * X_top + Y ] eqs_mid1 = [ - D(X_mid1) ~ p_mid1 - d*X_mid1^2, + D(X_mid1) ~ p_mid1 - d * X_mid1^2, D(Y) ~ D(X_mid1) - Y^3, - O ~ (p_mid1 + d)*X_mid1 + Y + O ~ (p_mid1 + d) * X_mid1 + Y ] eqs_mid2 = [ - D(X_mid2) ~ p_mid2 - d*X_mid2, + D(X_mid2) ~ p_mid2 - d * X_mid2, X_mid2^3 ~ log(X_mid2 + Y) - Y^2 + 3.0, - O ~ (p_mid2 + d)*X_mid2 + Y + O ~ (p_mid2 + d) * X_mid2 + Y ] eqs_bot = [ - D(X_bot) ~ p_bot - d*X_bot, + D(X_bot) ~ p_bot - d * X_bot, D(Y) ~ -Y^3, - O ~ (p_bot + d)*X_bot + Y + O ~ (p_bot + d) * X_bot + Y ] cevs = [[t ~ 1.0] => [Y ~ Y + 2.0]] devs = [(t == 2.0) => [Y ~ Y + 2.0]] - @named sys_bot = ODESystem(eqs_bot, t; systems = [], continuous_events = cevs, discrete_events = devs) - @named sys_mid2 = ODESystem(eqs_mid2, t; systems = [], continuous_events = cevs, discrete_events = devs) - @named sys_mid1 = ODESystem(eqs_mid1, t; systems = [sys_bot], continuous_events = cevs, discrete_events = devs) - @named sys_top = ODESystem(eqs_top, t; systems = [sys_mid1, sys_mid2], continuous_events = cevs, discrete_events = devs) + @named sys_bot = ODESystem( + eqs_bot, t; systems = [], continuous_events = cevs, discrete_events = devs) + @named sys_mid2 = ODESystem( + eqs_mid2, t; systems = [], continuous_events = cevs, discrete_events = devs) + @named sys_mid1 = ODESystem( + eqs_mid1, t; systems = [sys_bot], continuous_events = cevs, discrete_events = devs) + @named sys_top = ODESystem(eqs_top, t; systems = [sys_mid1, sys_mid2], + continuous_events = cevs, discrete_events = devs) sys_bot_comp = complete(sys_bot) sys_mid2_comp = complete(sys_mid2) sys_mid1_comp = complete(sys_mid1) @@ -68,75 +72,127 @@ let # Checks `parameters` for `toplevel = false`. @test all_sets_equal(parameters.([sys_bot, sys_bot_comp, sys_bot_ss])..., [d, p_bot]) - @test all_sets_equal(parameters.([sys_mid1, sys_mid1_comp, sys_mid1_ss])..., [d, p_mid1, sys_bot.d, sys_bot.p_bot]) - @test all_sets_equal(parameters.([sys_mid2, sys_mid2_comp, sys_mid2_ss])..., [d, p_mid2]) - @test all_sets_equal(parameters.([sys_top, sys_top_comp, sys_top_ss])..., [d, p_top, sys_mid1.d, sys_mid1.p_mid1, sys_mid1.sys_bot.d, sys_mid1.sys_bot.p_bot, sys_mid2.d, sys_mid2.p_mid2]) + @test all_sets_equal(parameters.([sys_mid1, sys_mid1_comp, sys_mid1_ss])..., + [d, p_mid1, sys_bot.d, sys_bot.p_bot]) + @test all_sets_equal( + parameters.([sys_mid2, sys_mid2_comp, sys_mid2_ss])..., [d, p_mid2]) + @test all_sets_equal(parameters.([sys_top, sys_top_comp, sys_top_ss])..., + [d, p_top, sys_mid1.d, sys_mid1.p_mid1, sys_mid1.sys_bot.d, + sys_mid1.sys_bot.p_bot, sys_mid2.d, sys_mid2.p_mid2]) # Checks `parameters`` for `toplevel = true`. Compares to known parameters and also checks that # these are subset of what `get_ps` returns. - @test all_sets_equal(parameters.([sys_bot, sys_bot_comp, sys_bot_ss]; toplevel = true)..., [d, p_bot]) - @test_broken all_sets_equal(parameters.([sys_mid1, sys_mid1_comp, sys_mid1_ss]; toplevel = true)..., [d, p_mid1]) - @test all_sets_equal(parameters.([sys_mid2, sys_mid2_comp, sys_mid2_ss]; toplevel = true)..., [d, p_mid2]) - @test_broken all_sets_equal(parameters.([sys_top, sys_top_comp, sys_top_ss]; toplevel = true)..., [d, p_top]) - @test all(sym_issubset(parameters(sys; toplevel = true), get_ps(sys)) for sys in [sys_bot, sys_mid2, sys_mid1, sys_top]) + @test all_sets_equal( + parameters.([sys_bot, sys_bot_comp, sys_bot_ss]; toplevel = true)..., [d, p_bot]) + @test_broken all_sets_equal( + parameters.([sys_mid1, sys_mid1_comp, sys_mid1_ss]; toplevel = true)..., + [d, p_mid1]) + @test all_sets_equal( + parameters.([sys_mid2, sys_mid2_comp, sys_mid2_ss]; toplevel = true)..., + [d, p_mid2]) + @test_broken all_sets_equal( + parameters.([sys_top, sys_top_comp, sys_top_ss]; toplevel = true)..., [d, p_top]) + @test all(sym_issubset(parameters(sys; toplevel = true), get_ps(sys)) + for sys in [sys_bot, sys_mid2, sys_mid1, sys_top]) # Checks `unknowns` for `toplevel = false`. O(t) is eliminated by `structural_simplify` and # must be considered separately. @test all_sets_equal(unknowns.([sys_bot, sys_bot_comp])..., [O, Y, X_bot]) @test all_sets_equal(unknowns.([sys_bot_ss])..., [Y, X_bot]) - @test all_sets_equal(unknowns.([sys_mid1, sys_mid1_comp])..., [O, Y, X_mid1, sys_bot.Y, sys_bot.O, sys_bot.X_bot]) + @test all_sets_equal(unknowns.([sys_mid1, sys_mid1_comp])..., + [O, Y, X_mid1, sys_bot.Y, sys_bot.O, sys_bot.X_bot]) @test all_sets_equal(unknowns.([sys_mid1_ss])..., [Y, X_mid1, sys_bot.Y, sys_bot.X_bot]) @test all_sets_equal(unknowns.([sys_mid2, sys_mid2_comp])..., [O, Y, X_mid2]) @test all_sets_equal(unknowns.([sys_mid2_ss])..., [Y, X_mid2]) - @test all_sets_equal(unknowns.([sys_top, sys_top_comp])..., [O, Y, X_top, sys_mid1.O, sys_mid1.Y, sys_mid1.X_mid1, sys_mid1.sys_bot.O, sys_mid1.sys_bot.Y, sys_mid1.sys_bot.X_bot, sys_mid2.O, sys_mid2.Y, sys_mid2.X_mid2]) - @test all_sets_equal(unknowns.([sys_top_ss])..., [Y, X_top, sys_mid1.Y, sys_mid1.X_mid1, sys_mid1.sys_bot.Y, sys_mid1.sys_bot.X_bot, sys_mid2.Y, sys_mid2.X_mid2]) + @test all_sets_equal(unknowns.([sys_top, sys_top_comp])..., + [O, Y, X_top, sys_mid1.O, sys_mid1.Y, sys_mid1.X_mid1, + sys_mid1.sys_bot.O, sys_mid1.sys_bot.Y, sys_mid1.sys_bot.X_bot, + sys_mid2.O, sys_mid2.Y, sys_mid2.X_mid2]) + @test all_sets_equal(unknowns.([sys_top_ss])..., + [Y, X_top, sys_mid1.Y, sys_mid1.X_mid1, sys_mid1.sys_bot.Y, + sys_mid1.sys_bot.X_bot, sys_mid2.Y, sys_mid2.X_mid2]) # Checks `unknowns` for `toplevel = true`. Note that O is not eliminated here (as we go back # to original parent system). Also checks that outputs are subsets of what `get_ps` returns.. - @test all_sets_equal(unknowns.([sys_bot, sys_bot_comp, sys_bot_ss]; toplevel = true)..., [O, Y, X_bot]) - @test all_sets_equal(unknowns.([sys_mid1, sys_mid1_comp]; toplevel = true)..., [O, Y, X_mid1]) - @test all_sets_equal(unknowns.([sys_mid2, sys_mid2_comp]; toplevel = true)..., [O, Y, X_mid2]) - @test all_sets_equal(unknowns.([sys_top, sys_top_comp]; toplevel = true)..., [O, Y, X_top]) - @test all(sym_issubset(unknowns(sys; toplevel = true), get_unknowns(sys)) for sys in [sys_bot, sys_mid1, sys_mid2, sys_top]) + @test all_sets_equal( + unknowns.([sys_bot, sys_bot_comp, sys_bot_ss]; toplevel = true)..., [O, Y, X_bot]) + @test all_sets_equal( + unknowns.([sys_mid1, sys_mid1_comp]; toplevel = true)..., [O, Y, X_mid1]) + @test all_sets_equal( + unknowns.([sys_mid2, sys_mid2_comp]; toplevel = true)..., [O, Y, X_mid2]) + @test all_sets_equal( + unknowns.([sys_top, sys_top_comp]; toplevel = true)..., [O, Y, X_top]) + @test all(sym_issubset(unknowns(sys; toplevel = true), get_unknowns(sys)) + for sys in [sys_bot, sys_mid1, sys_mid2, sys_top]) # Checks `equations` for `toplevel = false`. Do not check ss equations as these might potentially # be structurally simplified to new equations. @test all_sets_equal(equations.([sys_bot, sys_bot_comp])..., eqs_bot) - @test all_sets_equal(equations.([sys_mid1, sys_mid1_comp])..., [eqs_mid1; namespace_equations(sys_bot)]) + @test all_sets_equal( + equations.([sys_mid1, sys_mid1_comp])..., [eqs_mid1; namespace_equations(sys_bot)]) @test all_sets_equal(equations.([sys_mid2, sys_mid2_comp])..., eqs_mid2) - @test all_sets_equal(equations.([sys_top, sys_top_comp])..., [eqs_top; namespace_equations(sys_mid1); namespace_equations(sys_mid2)]) + @test all_sets_equal(equations.([sys_top, sys_top_comp])..., + [eqs_top; namespace_equations(sys_mid1); namespace_equations(sys_mid2)]) # Checks `equations` for `toplevel = true`. Do not check ss equations directly as these # might potentially be structurally simplified to new equations. @test all_sets_equal(equations.([sys_bot, sys_bot_comp]; toplevel = true)..., eqs_bot) - @test all_sets_equal(equations.([sys_mid1, sys_mid1_comp]; toplevel = true)..., eqs_mid1) - @test all_sets_equal(equations.([sys_mid2, sys_mid2_comp]; toplevel = true)..., eqs_mid2) + @test all_sets_equal( + equations.([sys_mid1, sys_mid1_comp]; toplevel = true)..., eqs_mid1) + @test all_sets_equal( + equations.([sys_mid2, sys_mid2_comp]; toplevel = true)..., eqs_mid2) @test all_sets_equal(equations.([sys_top, sys_top_comp]; toplevel = true)..., eqs_top) - @test all(sym_issubset(equations(sys; toplevel = true), get_eqs(sys)) for sys in [sys_bot, sys_mid2, sys_mid1, sys_top]) + @test all(sym_issubset(equations(sys; toplevel = true), get_eqs(sys)) + for sys in [sys_bot, sys_mid2, sys_mid1, sys_top]) # Checks `observed for `toplevel = false`. For non-ss systems, there are no observables. - @test all_sets_equal(observed.([sys_bot, sys_bot_comp, sys_mid1, sys_mid1_comp, sys_mid2, sys_mid2_comp, sys_top, sys_top_comp])..., []) + @test all_sets_equal( + observed.([sys_bot, sys_bot_comp, sys_mid1, sys_mid1_comp, + sys_mid2, sys_mid2_comp, sys_top, sys_top_comp])..., + []) @test issetequal(observed(sys_bot_ss), eqs_bot[3:3]) - @test issetequal(observed(sys_mid1_ss), [eqs_mid1[3:3]; namespace_equations(sys_bot)[3:3]]) + @test issetequal( + observed(sys_mid1_ss), [eqs_mid1[3:3]; namespace_equations(sys_bot)[3:3]]) @test issetequal(observed(sys_mid2_ss), eqs_mid2[3:3]) - @test issetequal(observed(sys_top_ss), [eqs_top[3:3]; namespace_equations(sys_mid1)[3:3:6]; namespace_equations(sys_mid2)[3:3]]) + @test issetequal(observed(sys_top_ss), + [eqs_top[3:3]; namespace_equations(sys_mid1)[3:3:6]; + namespace_equations(sys_mid2)[3:3]]) # Checks `observed` for `toplevel = true`. Again, for non-ss systems, there are no observables. # Also checks that `toplevel = true` yields subset of `get_observed`. - @test all_sets_equal(observed.([sys_bot, sys_bot_comp, sys_mid1, sys_mid1_comp, sys_mid2, sys_mid2_comp, sys_top, sys_top_comp]; toplevel = true)..., []) + @test all_sets_equal( + observed.( + [sys_bot, sys_bot_comp, sys_mid1, sys_mid1_comp, + sys_mid2, sys_mid2_comp, sys_top, sys_top_comp]; + toplevel = true)..., + []) @test_broken issetequal(observed(sys_bot_ss; toplevel = true), eqs_bot[3:3]) @test_broken issetequal(observed(sys_mid1_ss; toplevel = true), eqs_mid1[3:3]) @test_broken issetequal(observed(sys_mid2_ss; toplevel = true), eqs_mid2[3:3]) @test_broken issetequal(observed(sys_top_ss; toplevel = true), eqs_top[3:3]) - @test all(sym_issubset(observed(sys; toplevel = true), get_observed(sys)) for sys in [sys_bot, sys_mid2, sys_mid1, sys_top]) + @test all(sym_issubset(observed(sys; toplevel = true), get_observed(sys)) + for sys in [sys_bot, sys_mid2, sys_mid1, sys_top]) # Checks `continuous_events` and `discrete_events` for `toplevel = true` (more straightforward # as I stored the same singe event in all systems). Don't check for `toplevel = false` as # technically not needed for these tests and name spacing the events is a mess. mtk_cev = ModelingToolkit.SymbolicContinuousCallback.(cevs)[1] mtk_dev = ModelingToolkit.SymbolicDiscreteCallback.(devs)[1] - @test all_sets_equal(continuous_events.([sys_bot, sys_bot_comp, sys_bot_ss, sys_mid1, sys_mid1_comp, sys_mid1_ss, sys_mid2, sys_mid2_comp, sys_mid2_ss, sys_top, sys_top_comp, sys_top_ss]; toplevel = true)..., [mtk_cev]) - @test all_sets_equal(discrete_events.([sys_bot, sys_bot_comp, sys_bot_ss, sys_mid1, sys_mid1_comp, sys_mid1_ss, sys_mid2, sys_mid2_comp, sys_mid2_ss, sys_top, sys_top_comp, sys_top_ss]; toplevel = true)..., [mtk_dev]) - @test all(sym_issubset(continuous_events(sys; toplevel = true), get_continuous_events(sys)) for sys in [sys_bot, sys_mid2, sys_mid1, sys_top]) - @test all(sym_issubset(discrete_events(sys; toplevel = true), get_discrete_events(sys)) for sys in [sys_bot, sys_mid2, sys_mid1, sys_top]) + @test all_sets_equal( + continuous_events.( + [sys_bot, sys_bot_comp, sys_bot_ss, sys_mid1, sys_mid1_comp, sys_mid1_ss, + sys_mid2, sys_mid2_comp, sys_mid2_ss, sys_top, sys_top_comp, sys_top_ss]; + toplevel = true)..., + [mtk_cev]) + @test all_sets_equal( + discrete_events.( + [sys_bot, sys_bot_comp, sys_bot_ss, sys_mid1, sys_mid1_comp, sys_mid1_ss, + sys_mid2, sys_mid2_comp, sys_mid2_ss, sys_top, sys_top_comp, sys_top_ss]; + toplevel = true)..., + [mtk_dev]) + @test all(sym_issubset( + continuous_events(sys; toplevel = true), get_continuous_events(sys)) + for sys in [sys_bot, sys_mid2, sys_mid1, sys_top]) + @test all(sym_issubset(discrete_events(sys; toplevel = true), get_discrete_events(sys)) + for sys in [sys_bot, sys_mid2, sys_mid1, sys_top]) end From 3e07adf208fbeb90dfc1e73faa14f3ffb2b70e4d Mon Sep 17 00:00:00 2001 From: Torkel Loman Date: Sun, 16 Mar 2025 13:20:04 +0000 Subject: [PATCH 05/12] create separate _toplevel function versions isntead --- src/systems/abstractsystem.jl | 77 ++++++++++++++++++++--------------- src/systems/callbacks.jl | 48 +++++++++++++++------- 2 files changed, 79 insertions(+), 46 deletions(-) diff --git a/src/systems/abstractsystem.jl b/src/systems/abstractsystem.jl index 969f5d89ec..d23289d964 100644 --- a/src/systems/abstractsystem.jl +++ b/src/systems/abstractsystem.jl @@ -1292,15 +1292,11 @@ $(TYPEDSIGNATURES) Get the unknown variables of the system `sys` and its subsystems. See also [`ModelingToolkit.get_unknowns`](@ref). - -Arguments: -- `toplevel = false`: if set to true, do not return the continuous events of the subsystems. """ -function unknowns(sys::AbstractSystem; toplevel = false) - toplevel && (sys = recursive_get_parent(sys)) +function unknowns(sys::AbstractSystem) sts = get_unknowns(sys) systems = get_systems(sys) - nonunique_unknowns = if toplevel || isempty(systems) + nonunique_unknowns = if isempty(systems) sts else system_unknowns = reduce(vcat, namespace_variables.(systems)) @@ -1317,6 +1313,19 @@ function unknowns(sys::AbstractSystem; toplevel = false) unique(nonunique_unknowns) end +""" + unknowns_toplevel(sys::AbstractSystem) + +Replicates the behaviour of `unknowns`, but ignores unknowns of subsystems. +""" +function unknowns_toplevel(sys::AbstractSystem) + if has_parent(sys) && (parent = get_parent(sys)) !== nothing + return unknowns_toplevel(parent) + end + return get_unknowns(sys) +end + + """ $(TYPEDSIGNATURES) @@ -1324,11 +1333,8 @@ Get the parameters of the system `sys` and its subsystems. See also [`@parameters`](@ref) and [`ModelingToolkit.get_ps`](@ref). -Arguments: -- `toplevel = false`: if set to true, do not return the continuous events of the subsystems. """ -function parameters(sys::AbstractSystem; initial_parameters = false, toplevel = false) - toplevel && (sys = recursive_get_parent(sys)) +function parameters(sys::AbstractSystem; initial_parameters = false) ps = get_ps(sys) if ps == SciMLBase.NullParameters() return [] @@ -1337,7 +1343,7 @@ function parameters(sys::AbstractSystem; initial_parameters = false, toplevel = ps = first.(ps) end systems = get_systems(sys) - result = unique(toplevel || isempty(systems) ? + result = unique(isempty(systems) ? ps : [ps; reduce(vcat, namespace_parameters.(systems))]) if !initial_parameters if is_time_dependent(sys) @@ -1362,19 +1368,15 @@ function dependent_parameters(sys::AbstractSystem) end """ - recursive_get_parent(sys::AbstractSystem) + parameters_toplevel(sys::AbstractSystem) -Loops through parent systems to find the original parent system. - -Warning: -- Curently only used (and tested) in the context of accessor functions (e.g. `parameters`), -specifically in the context of the `toplevel` keyword argument. +Replicates the behaviour of `parameters`, but ignores parameters of subsystems. """ -function recursive_get_parent(sys::AbstractSystem) - if ModelingToolkit.has_parent(sys) && (p = ModelingToolkit.get_parent(sys)) !== nothing - return recursive_get_parent(p) +function parameters_toplevel(sys::AbstractSystem) + if has_parent(sys) && (parent = get_parent(sys)) !== nothing + return parameters_toplevel(parent) end - return sys + return get_ps(sys) end """ @@ -1514,10 +1516,8 @@ function controls(sys::AbstractSystem) isempty(systems) ? ctrls : [ctrls; reduce(vcat, namespace_controls.(systems))] end -function observed(sys::AbstractSystem; toplevel = false) - toplevel && (sys = recursive_get_parent(sys)) +function observed(sys::AbstractSystem) obs = get_observed(sys) - toplevel && return obs systems = get_systems(sys) [obs; reduce(vcat, @@ -1536,7 +1536,7 @@ If they are not explicitly provided, variables and parameters are initialized to See also [`initialization_equations`](@ref), [`parameter_dependencies`](@ref) and [`ModelingToolkit.get_defaults`](@ref). """ -function defaults(sys::AbstractSystem; toplevel = false) +function defaults(sys::AbstractSystem) systems = get_systems(sys) defs = get_defaults(sys) # `mapfoldr` is really important!!! We should prefer the base model for @@ -1545,8 +1545,7 @@ function defaults(sys::AbstractSystem; toplevel = false) # `compose(ODESystem(...; defaults=defs), ...)` # # Thus, right associativity is required and crucial for correctness. - (toplevel || isempty(systems)) ? - defs : mapfoldr(namespace_defaults, merge, systems; init = defs) + isempty(systems) ? defs : mapfoldr(namespace_defaults, merge, systems; init = defs) end function defaults_and_guesses(sys::AbstractSystem) @@ -1576,14 +1575,11 @@ It is often the most useful way to inspect the equations of a system. See also [`full_equations`](@ref) and [`ModelingToolkit.get_eqs`](@ref). -Arguments: -- `toplevel = false`: if set to true, do not return the continuous events of the subsystems. """ -function equations(sys::AbstractSystem; toplevel = false) - toplevel && (sys = recursive_get_parent(sys)) +function equations(sys::AbstractSystem) eqs = get_eqs(sys) systems = get_systems(sys) - if toplevel || isempty(systems) + if isempty(systems) return eqs else eqs = Equation[eqs; @@ -1594,6 +1590,23 @@ function equations(sys::AbstractSystem; toplevel = false) end end + +""" + equations_toplevel(sys::AbstractSystem) + +Replicates the behaviour of `equations`, but ignores equations of subsystems. + +Notes: +- Cannot be applied to non-complete systems. +""" +function equations_toplevel(sys::AbstractSystem) + iscomplete(sys) && error("Cannot apply `equations_toplevel` to complete systems.") + if has_parent(sys) && (parent = get_parent(sys)) !== nothing + return equations_toplevel(parent) + end + return get_eqs(sys) +end + """ $(TYPEDSIGNATURES) diff --git a/src/systems/callbacks.jl b/src/systems/callbacks.jl index 715b12999a..710ccc4946 100644 --- a/src/systems/callbacks.jl +++ b/src/systems/callbacks.jl @@ -320,21 +320,16 @@ function namespace_callback(cb::SymbolicContinuousCallback, s)::SymbolicContinuo end """ - continuous_events(sys::AbstractSystem; toplevel = false)::Vector{SymbolicContinuousCallback} + continuous_events(sys::AbstractSystem)::Vector{SymbolicContinuousCallback} Returns a vector of all the `continuous_events` in an abstract system and its component subsystems. The `SymbolicContinuousCallback`s in the returned vector are structs with two fields: `eqs` and `affect` which correspond to the first and second elements of a `Pair` used to define an event, i.e. `eqs => affect`. - -Arguments: -- `toplevel = false`: if set to true, do not return the continuous events of the subsystems. """ -function continuous_events(sys::AbstractSystem; toplevel = false) - toplevel && (sys = recursive_get_parent(sys)) +function continuous_events(sys::AbstractSystem) cbs = get_continuous_events(sys) filter(!isempty, cbs) - toplevel && return cbs systems = get_systems(sys) cbs = [cbs; @@ -361,6 +356,21 @@ function vars!(vars, cb::SymbolicContinuousCallback; op = Differential) return vars end +""" + continuous_events_toplevel(sys::AbstractSystem) + +Replicates the behaviour of `continuous_events`, but ignores events of subsystems. + +Notes: +- Cannot be applied to non-complete systems. +""" +function continuous_events_toplevel(sys::AbstractSystem) + if has_parent(sys) && (parent = get_parent(sys)) !== nothing + return continuous_events_toplevel(parent) + end + return get_continuous_events(sys) +end + #################################### discrete events ##################################### struct SymbolicDiscreteCallback @@ -480,20 +490,15 @@ SymbolicDiscreteCallbacks(cbs::Vector{<:SymbolicDiscreteCallback}) = cbs SymbolicDiscreteCallbacks(::Nothing) = SymbolicDiscreteCallback[] """ - discrete_events(sys::AbstractSystem; toplevel = false) :: Vector{SymbolicDiscreteCallback} + discrete_events(sys::AbstractSystem) :: Vector{SymbolicDiscreteCallback} Returns a vector of all the `discrete_events` in an abstract system and its component subsystems. The `SymbolicDiscreteCallback`s in the returned vector are structs with two fields: `condition` and `affect` which correspond to the first and second elements of a `Pair` used to define an event, i.e. `condition => affect`. - -Arguments: -- `toplevel = false`: if set to true, do not return the discrete events of the subsystems. """ -function discrete_events(sys::AbstractSystem; toplevel = false) - toplevel && (sys = recursive_get_parent(sys)) +function discrete_events(sys::AbstractSystem) cbs = get_discrete_events(sys) - toplevel && return cbs systems = get_systems(sys) cbs = [cbs; reduce(vcat, @@ -524,6 +529,21 @@ function vars!(vars, cb::SymbolicDiscreteCallback; op = Differential) return vars end +""" + discrete_events_toplevel(sys::AbstractSystem) + +Replicates the behaviour of `discrete_events`, but ignores events of subsystems. + +Notes: +- Cannot be applied to non-complete systems. +""" +function discrete_events_toplevel(sys::AbstractSystem) + if has_parent(sys) && (parent = get_parent(sys)) !== nothing + return discrete_events_toplevel(parent) + end + return get_discrete_events(sys) +end + ################################# compilation functions #################################### # handles ensuring that affect! functions work with integrator arguments From 04d8dda22eeb39005015c2831c9acf018f7096de Mon Sep 17 00:00:00 2001 From: Torkel Loman Date: Sun, 16 Mar 2025 13:20:11 +0000 Subject: [PATCH 06/12] update for new tests --- test/accessor_functions.jl | 97 ++++++++++++++------------------------ 1 file changed, 35 insertions(+), 62 deletions(-) diff --git a/test/accessor_functions.jl b/test/accessor_functions.jl index 2402c53da7..1ad4ef174d 100644 --- a/test/accessor_functions.jl +++ b/test/accessor_functions.jl @@ -5,6 +5,8 @@ using ModelingToolkit, Test using ModelingToolkit: t_nounits as t, D_nounits as D import ModelingToolkit: get_ps, get_unknowns, get_observed, get_eqs, get_continuous_events, get_discrete_events, namespace_equations +import ModelingToolkit: parameters_toplevel, unknowns_toplevel, equations_toplevel, + continuous_events_toplevel, discrete_events_toplevel # Creates helper functions. function all_sets_equal(args...) @@ -23,8 +25,9 @@ end ### Basic Tests ### # Checks `toplevel = false` argument for various accessors (currently only for `ODESystem`s). -# Compares to `toplevel = true` version, and `get_` functions. +# Compares to `` version, and `get_` functions. # Checks accessors for parameters, unknowns, equations, observables, and events. +# Some tests looks funny (caused by the formatter). let # Prepares model components. @parameters p_top p_mid1 p_mid2 p_bot d @@ -70,7 +73,7 @@ let sys_mid1_ss = structural_simplify(sys_mid1) sys_top_ss = structural_simplify(sys_top) - # Checks `parameters` for `toplevel = false`. + # Checks `parameters1. @test all_sets_equal(parameters.([sys_bot, sys_bot_comp, sys_bot_ss])..., [d, p_bot]) @test all_sets_equal(parameters.([sys_mid1, sys_mid1_comp, sys_mid1_ss])..., [d, p_mid1, sys_bot.d, sys_bot.p_bot]) @@ -80,22 +83,22 @@ let [d, p_top, sys_mid1.d, sys_mid1.p_mid1, sys_mid1.sys_bot.d, sys_mid1.sys_bot.p_bot, sys_mid2.d, sys_mid2.p_mid2]) - # Checks `parameters`` for `toplevel = true`. Compares to known parameters and also checks that + # Checks `parameters_toplevel`. Compares to known parameters and also checks that # these are subset of what `get_ps` returns. @test all_sets_equal( - parameters.([sys_bot, sys_bot_comp, sys_bot_ss]; toplevel = true)..., [d, p_bot]) + parameters_toplevel.([sys_bot, sys_bot_comp, sys_bot_ss])..., [d, p_bot]) @test_broken all_sets_equal( - parameters.([sys_mid1, sys_mid1_comp, sys_mid1_ss]; toplevel = true)..., + parameters_toplevel.([sys_mid1, sys_mid1_comp, sys_mid1_ss])..., [d, p_mid1]) @test all_sets_equal( - parameters.([sys_mid2, sys_mid2_comp, sys_mid2_ss]; toplevel = true)..., + parameters_toplevel.([sys_mid2, sys_mid2_comp, sys_mid2_ss])..., [d, p_mid2]) @test_broken all_sets_equal( - parameters.([sys_top, sys_top_comp, sys_top_ss]; toplevel = true)..., [d, p_top]) - @test all(sym_issubset(parameters(sys; toplevel = true), get_ps(sys)) + parameters_toplevel.([sys_top, sys_top_comp, sys_top_ss])..., [d, p_top]) + @test all(sym_issubset(parameters_toplevel(sys), get_ps(sys)) for sys in [sys_bot, sys_mid2, sys_mid1, sys_top]) - # Checks `unknowns` for `toplevel = false`. O(t) is eliminated by `structural_simplify` and + # Checks `unknowns`. O(t) is eliminated by `structural_simplify` and # must be considered separately. @test all_sets_equal(unknowns.([sys_bot, sys_bot_comp])..., [O, Y, X_bot]) @test all_sets_equal(unknowns.([sys_bot_ss])..., [Y, X_bot]) @@ -112,20 +115,20 @@ let [Y, X_top, sys_mid1.Y, sys_mid1.X_mid1, sys_mid1.sys_bot.Y, sys_mid1.sys_bot.X_bot, sys_mid2.Y, sys_mid2.X_mid2]) - # Checks `unknowns` for `toplevel = true`. Note that O is not eliminated here (as we go back - # to original parent system). Also checks that outputs are subsets of what `get_ps` returns.. + # Checks `unknowns_toplevel`. Note that O is not eliminated here (as we go back + # to original parent system). Also checks that outputs are subsets of what `get_unknowns` returns. @test all_sets_equal( - unknowns.([sys_bot, sys_bot_comp, sys_bot_ss]; toplevel = true)..., [O, Y, X_bot]) + unknowns_toplevel.([sys_bot, sys_bot_comp, sys_bot_ss])..., [O, Y, X_bot]) @test all_sets_equal( - unknowns.([sys_mid1, sys_mid1_comp]; toplevel = true)..., [O, Y, X_mid1]) + unknowns_toplevel.([sys_mid1, sys_mid1_comp])..., [O, Y, X_mid1]) @test all_sets_equal( - unknowns.([sys_mid2, sys_mid2_comp]; toplevel = true)..., [O, Y, X_mid2]) + unknowns_toplevel.([sys_mid2, sys_mid2_comp])..., [O, Y, X_mid2]) @test all_sets_equal( - unknowns.([sys_top, sys_top_comp]; toplevel = true)..., [O, Y, X_top]) - @test all(sym_issubset(unknowns(sys; toplevel = true), get_unknowns(sys)) + unknowns_toplevel.([sys_top, sys_top_comp])..., [O, Y, X_top]) + @test all(sym_issubset(unknowns_toplevel(sys), get_unknowns(sys)) for sys in [sys_bot, sys_mid1, sys_mid2, sys_top]) - # Checks `equations` for `toplevel = false`. Do not check ss equations as these might potentially + # Checks `equations`. Do not check ss equations as these might potentially # be structurally simplified to new equations. @test all_sets_equal(equations.([sys_bot, sys_bot_comp])..., eqs_bot) @test all_sets_equal( @@ -134,65 +137,35 @@ let @test all_sets_equal(equations.([sys_top, sys_top_comp])..., [eqs_top; namespace_equations(sys_mid1); namespace_equations(sys_mid2)]) - # Checks `equations` for `toplevel = true`. Do not check ss equations directly as these - # might potentially be structurally simplified to new equations. - @test all_sets_equal(equations.([sys_bot, sys_bot_comp]; toplevel = true)..., eqs_bot) + # Checks `equations_toplevel`. Do not check ss equations directly as these + # might potentially be structurally simplified to new equations. Do not check + @test all_sets_equal(equations_toplevel.([sys_bot])..., eqs_bot) @test all_sets_equal( - equations.([sys_mid1, sys_mid1_comp]; toplevel = true)..., eqs_mid1) + equations_toplevel.([sys_mid1])..., eqs_mid1) @test all_sets_equal( - equations.([sys_mid2, sys_mid2_comp]; toplevel = true)..., eqs_mid2) - @test all_sets_equal(equations.([sys_top, sys_top_comp]; toplevel = true)..., eqs_top) - @test all(sym_issubset(equations(sys; toplevel = true), get_eqs(sys)) + equations_toplevel.([sys_mid2])..., eqs_mid2) + @test all_sets_equal(equations_toplevel.([sys_top])..., eqs_top) + @test all(sym_issubset(equations_toplevel(sys), get_eqs(sys)) for sys in [sys_bot, sys_mid2, sys_mid1, sys_top]) - # Checks `observed for `toplevel = false`. For non-ss systems, there are no observables. - @test all_sets_equal( - observed.([sys_bot, sys_bot_comp, sys_mid1, sys_mid1_comp, - sys_mid2, sys_mid2_comp, sys_top, sys_top_comp])..., - []) - @test issetequal(observed(sys_bot_ss), eqs_bot[3:3]) - @test issetequal( - observed(sys_mid1_ss), [eqs_mid1[3:3]; namespace_equations(sys_bot)[3:3]]) - @test issetequal(observed(sys_mid2_ss), eqs_mid2[3:3]) - @test issetequal(observed(sys_top_ss), - [eqs_top[3:3]; namespace_equations(sys_mid1)[3:3:6]; - namespace_equations(sys_mid2)[3:3]]) - - # Checks `observed` for `toplevel = true`. Again, for non-ss systems, there are no observables. - # Also checks that `toplevel = true` yields subset of `get_observed`. - @test all_sets_equal( - observed.( - [sys_bot, sys_bot_comp, sys_mid1, sys_mid1_comp, - sys_mid2, sys_mid2_comp, sys_top, sys_top_comp]; - toplevel = true)..., - []) - @test_broken issetequal(observed(sys_bot_ss; toplevel = true), eqs_bot[3:3]) - @test_broken issetequal(observed(sys_mid1_ss; toplevel = true), eqs_mid1[3:3]) - @test_broken issetequal(observed(sys_mid2_ss; toplevel = true), eqs_mid2[3:3]) - @test_broken issetequal(observed(sys_top_ss; toplevel = true), eqs_top[3:3]) - @test all(sym_issubset(observed(sys; toplevel = true), get_observed(sys)) - for sys in [sys_bot, sys_mid2, sys_mid1, sys_top]) - - # Checks `continuous_events` and `discrete_events` for `toplevel = true` (more straightforward - # as I stored the same singe event in all systems). Don't check for `toplevel = false` as + # Checks `continuous_events_toplevel` and `discrete_events_toplevel` (straightforward + # as I stored the same singe event in all systems). Don't check for non-toplevel cases as # technically not needed for these tests and name spacing the events is a mess. mtk_cev = ModelingToolkit.SymbolicContinuousCallback.(cevs)[1] mtk_dev = ModelingToolkit.SymbolicDiscreteCallback.(devs)[1] @test all_sets_equal( - continuous_events.( + continuous_events_toplevel.( [sys_bot, sys_bot_comp, sys_bot_ss, sys_mid1, sys_mid1_comp, sys_mid1_ss, - sys_mid2, sys_mid2_comp, sys_mid2_ss, sys_top, sys_top_comp, sys_top_ss]; - toplevel = true)..., + sys_mid2, sys_mid2_comp, sys_mid2_ss, sys_top, sys_top_comp, sys_top_ss])..., [mtk_cev]) @test all_sets_equal( - discrete_events.( + discrete_events_toplevel.( [sys_bot, sys_bot_comp, sys_bot_ss, sys_mid1, sys_mid1_comp, sys_mid1_ss, - sys_mid2, sys_mid2_comp, sys_mid2_ss, sys_top, sys_top_comp, sys_top_ss]; - toplevel = true)..., + sys_mid2, sys_mid2_comp, sys_mid2_ss, sys_top, sys_top_comp, sys_top_ss])..., [mtk_dev]) @test all(sym_issubset( - continuous_events(sys; toplevel = true), get_continuous_events(sys)) + continuous_events_toplevel(sys), get_continuous_events(sys)) for sys in [sys_bot, sys_mid2, sys_mid1, sys_top]) - @test all(sym_issubset(discrete_events(sys; toplevel = true), get_discrete_events(sys)) + @test all(sym_issubset(discrete_events_toplevel(sys), get_discrete_events(sys)) for sys in [sys_bot, sys_mid2, sys_mid1, sys_top]) end From 9e459e20266712154d8ac17818ab211abe8c1b4a Mon Sep 17 00:00:00 2001 From: Torkel Loman Date: Sun, 16 Mar 2025 13:20:31 +0000 Subject: [PATCH 07/12] formating --- src/systems/abstractsystem.jl | 2 -- test/accessor_functions.jl | 6 +++--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/systems/abstractsystem.jl b/src/systems/abstractsystem.jl index d23289d964..de69338ec4 100644 --- a/src/systems/abstractsystem.jl +++ b/src/systems/abstractsystem.jl @@ -1325,7 +1325,6 @@ function unknowns_toplevel(sys::AbstractSystem) return get_unknowns(sys) end - """ $(TYPEDSIGNATURES) @@ -1590,7 +1589,6 @@ function equations(sys::AbstractSystem) end end - """ equations_toplevel(sys::AbstractSystem) diff --git a/test/accessor_functions.jl b/test/accessor_functions.jl index 1ad4ef174d..d930d202e5 100644 --- a/test/accessor_functions.jl +++ b/test/accessor_functions.jl @@ -156,15 +156,15 @@ let @test all_sets_equal( continuous_events_toplevel.( [sys_bot, sys_bot_comp, sys_bot_ss, sys_mid1, sys_mid1_comp, sys_mid1_ss, - sys_mid2, sys_mid2_comp, sys_mid2_ss, sys_top, sys_top_comp, sys_top_ss])..., + sys_mid2, sys_mid2_comp, sys_mid2_ss, sys_top, sys_top_comp, sys_top_ss])..., [mtk_cev]) @test all_sets_equal( discrete_events_toplevel.( [sys_bot, sys_bot_comp, sys_bot_ss, sys_mid1, sys_mid1_comp, sys_mid1_ss, - sys_mid2, sys_mid2_comp, sys_mid2_ss, sys_top, sys_top_comp, sys_top_ss])..., + sys_mid2, sys_mid2_comp, sys_mid2_ss, sys_top, sys_top_comp, sys_top_ss])..., [mtk_dev]) @test all(sym_issubset( - continuous_events_toplevel(sys), get_continuous_events(sys)) + continuous_events_toplevel(sys), get_continuous_events(sys)) for sys in [sys_bot, sys_mid2, sys_mid1, sys_top]) @test all(sym_issubset(discrete_events_toplevel(sys), get_discrete_events(sys)) for sys in [sys_bot, sys_mid2, sys_mid1, sys_top]) From e151999d5aeccf70fb1415df71f24b799c760ade Mon Sep 17 00:00:00 2001 From: Torkel Loman Date: Sun, 16 Mar 2025 13:29:11 +0000 Subject: [PATCH 08/12] add docs --- docs/src/basics/AbstractSystem.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/src/basics/AbstractSystem.md b/docs/src/basics/AbstractSystem.md index e68a7bb94e..e1ff695245 100644 --- a/docs/src/basics/AbstractSystem.md +++ b/docs/src/basics/AbstractSystem.md @@ -69,6 +69,8 @@ Optionally, a system could have: Note that if you know a system is an `AbstractTimeDependentSystem` you could use `get_iv` to get the unique independent variable directly, rather than using `independent_variables(sys)[1]`, which is clunky and may cause problems if `sys` is an `AbstractMultivariateSystem` because there may be more than one independent variable. `AbstractTimeIndependentSystem`s do not have a method `get_iv`, and `independent_variables(sys)` will return a size-zero result for such. For an `AbstractMultivariateSystem`, `get_ivs` is equivalent. +For the `parameters`, `unknowns`, `continuous_events`, and `discrete_events` accessors there are corresponding `parameters_toplevel`, `unknowns_toplevel`, `continuous_events_toplevel`, and `discrete_events_toplevel` accessors which works similarly, but ignores the content of subsystems. Furthermore, a `equations_toplevel` version of `equations` exists as well, however, it can only be applied to non-complete systems. + A system could also have caches: - `get_jac(sys)`: The Jacobian of a system. From be8d20d8a96c53eb8271dc3fba96401e34dc2284 Mon Sep 17 00:00:00 2001 From: Torkel Loman Date: Sun, 16 Mar 2025 13:31:12 +0000 Subject: [PATCH 09/12] format fixes --- src/systems/abstractsystem.jl | 5 ++--- src/systems/callbacks.jl | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/systems/abstractsystem.jl b/src/systems/abstractsystem.jl index de69338ec4..9f0a2b61d4 100644 --- a/src/systems/abstractsystem.jl +++ b/src/systems/abstractsystem.jl @@ -1342,8 +1342,8 @@ function parameters(sys::AbstractSystem; initial_parameters = false) ps = first.(ps) end systems = get_systems(sys) - result = unique(isempty(systems) ? - ps : [ps; reduce(vcat, namespace_parameters.(systems))]) + result = unique(isempty(systems) ? ps : + [ps; reduce(vcat, namespace_parameters.(systems))]) if !initial_parameters if is_time_dependent(sys) # time-dependent systems have `Initial` parameters for all their @@ -1573,7 +1573,6 @@ It may include some abbreviations and aliases of observables. It is often the most useful way to inspect the equations of a system. See also [`full_equations`](@ref) and [`ModelingToolkit.get_eqs`](@ref). - """ function equations(sys::AbstractSystem) eqs = get_eqs(sys) diff --git a/src/systems/callbacks.jl b/src/systems/callbacks.jl index 710ccc4946..937264d083 100644 --- a/src/systems/callbacks.jl +++ b/src/systems/callbacks.jl @@ -502,7 +502,7 @@ function discrete_events(sys::AbstractSystem) systems = get_systems(sys) cbs = [cbs; reduce(vcat, - (map(o -> namespace_callback(o, s), discrete_events(s)) for s in systems), + (map(cb -> namespace_callback(cb, s), discrete_events(s)) for s in systems), init = SymbolicDiscreteCallback[])] cbs end From 56ec62202fa7f4f6d4250d140ae8fedb5a22a7c9 Mon Sep 17 00:00:00 2001 From: Torkel Loman Date: Mon, 17 Mar 2025 10:42:35 +0000 Subject: [PATCH 10/12] Update src/systems/abstractsystem.jl Co-authored-by: Aayush Sabharwal --- src/systems/abstractsystem.jl | 1 - 1 file changed, 1 deletion(-) diff --git a/src/systems/abstractsystem.jl b/src/systems/abstractsystem.jl index 9f0a2b61d4..9e6e875283 100644 --- a/src/systems/abstractsystem.jl +++ b/src/systems/abstractsystem.jl @@ -1331,7 +1331,6 @@ $(TYPEDSIGNATURES) Get the parameters of the system `sys` and its subsystems. See also [`@parameters`](@ref) and [`ModelingToolkit.get_ps`](@ref). - """ function parameters(sys::AbstractSystem; initial_parameters = false) ps = get_ps(sys) From 4536f9800d2ac6ffe4ecd8fa917701b209e0f002 Mon Sep 17 00:00:00 2001 From: Torkel Loman Date: Mon, 17 Mar 2025 10:43:06 +0000 Subject: [PATCH 11/12] Update docs/src/basics/AbstractSystem.md Co-authored-by: Aayush Sabharwal --- docs/src/basics/AbstractSystem.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/basics/AbstractSystem.md b/docs/src/basics/AbstractSystem.md index e1ff695245..3aa43e6d25 100644 --- a/docs/src/basics/AbstractSystem.md +++ b/docs/src/basics/AbstractSystem.md @@ -69,7 +69,7 @@ Optionally, a system could have: Note that if you know a system is an `AbstractTimeDependentSystem` you could use `get_iv` to get the unique independent variable directly, rather than using `independent_variables(sys)[1]`, which is clunky and may cause problems if `sys` is an `AbstractMultivariateSystem` because there may be more than one independent variable. `AbstractTimeIndependentSystem`s do not have a method `get_iv`, and `independent_variables(sys)` will return a size-zero result for such. For an `AbstractMultivariateSystem`, `get_ivs` is equivalent. -For the `parameters`, `unknowns`, `continuous_events`, and `discrete_events` accessors there are corresponding `parameters_toplevel`, `unknowns_toplevel`, `continuous_events_toplevel`, and `discrete_events_toplevel` accessors which works similarly, but ignores the content of subsystems. Furthermore, a `equations_toplevel` version of `equations` exists as well, however, it can only be applied to non-complete systems. +For the `parameters`, `unknowns`, `continuous_events`, and `discrete_events` accessors there are corresponding `parameters_toplevel`, `unknowns_toplevel`, `continuous_events_toplevel`, and `discrete_events_toplevel` accessors which work similarly, but ignore the content of subsystems. Furthermore, a `equations_toplevel` version of `equations` exists as well, however, it can only be applied to non-complete systems. A system could also have caches: From 9a19c1d4c2b0fe75c6537936455831ba46ceef24 Mon Sep 17 00:00:00 2001 From: Torkel Loman Date: Mon, 17 Mar 2025 12:45:40 +0000 Subject: [PATCH 12/12] broken tests no longer broken --- test/accessor_functions.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/accessor_functions.jl b/test/accessor_functions.jl index d930d202e5..7ce477155b 100644 --- a/test/accessor_functions.jl +++ b/test/accessor_functions.jl @@ -87,13 +87,13 @@ let # these are subset of what `get_ps` returns. @test all_sets_equal( parameters_toplevel.([sys_bot, sys_bot_comp, sys_bot_ss])..., [d, p_bot]) - @test_broken all_sets_equal( + @test all_sets_equal( parameters_toplevel.([sys_mid1, sys_mid1_comp, sys_mid1_ss])..., [d, p_mid1]) @test all_sets_equal( parameters_toplevel.([sys_mid2, sys_mid2_comp, sys_mid2_ss])..., [d, p_mid2]) - @test_broken all_sets_equal( + @test all_sets_equal( parameters_toplevel.([sys_top, sys_top_comp, sys_top_ss])..., [d, p_top]) @test all(sym_issubset(parameters_toplevel(sys), get_ps(sys)) for sys in [sys_bot, sys_mid2, sys_mid1, sys_top])