|  | 
|  | 1 | +### Preparations ### | 
|  | 2 | + | 
|  | 3 | +# Fetch packages. | 
|  | 4 | +using ModelingToolkit, Test | 
|  | 5 | +using ModelingToolkit: t_nounits as t, D_nounits as D | 
|  | 6 | +import ModelingToolkit: get_ps, get_unknowns, get_observed, get_eqs, get_continuous_events, | 
|  | 7 | +    get_discrete_events, namespace_equations | 
|  | 8 | + | 
|  | 9 | +# Creates helper functions. | 
|  | 10 | +function all_sets_equal(args...) | 
|  | 11 | +    for arg in args[2:end] | 
|  | 12 | +        issetequal(args[1], arg) || return false | 
|  | 13 | +    end | 
|  | 14 | +    return true | 
|  | 15 | +end | 
|  | 16 | +function sym_issubset(set1, set2) | 
|  | 17 | +    for sym1 in set1 | 
|  | 18 | +        any(isequal(sym1, sym2) for sym2 in set2) || return false | 
|  | 19 | +    end | 
|  | 20 | +    return true | 
|  | 21 | +end | 
|  | 22 | + | 
|  | 23 | +### Basic Tests ### | 
|  | 24 | + | 
|  | 25 | +# Checks `toplevel = false` argument for various accessors (currently only for `ODESystem`s). | 
|  | 26 | +# Compares to `toplevel = true` version, and `get_` functions. | 
|  | 27 | +# Checks  accessors for parameters, unknowns, equations, observables, and events. | 
|  | 28 | +let | 
|  | 29 | +    # Prepares model components. | 
|  | 30 | +    @parameters p_top p_mid1 p_mid2 p_bot d | 
|  | 31 | +    @variables X_top(t) X_mid1(t) X_mid2(t) X_bot(t) Y(t) O(t) | 
|  | 32 | + | 
|  | 33 | +    # Creates the systems (individual and hierarchical). | 
|  | 34 | +    eqs_top = [ | 
|  | 35 | +        D(X_top) ~ p_top - d*X_top, | 
|  | 36 | +        D(Y) ~ log(X_top) - Y^2 + 3.0, | 
|  | 37 | +        O ~ (p_top + d)*X_top + Y | 
|  | 38 | +    ] | 
|  | 39 | +    eqs_mid1 = [ | 
|  | 40 | +        D(X_mid1) ~ p_mid1 - d*X_mid1^2, | 
|  | 41 | +        D(Y) ~ D(X_mid1) - Y^3, | 
|  | 42 | +        O ~ (p_mid1 + d)*X_mid1 + Y | 
|  | 43 | +    ] | 
|  | 44 | +    eqs_mid2 = [ | 
|  | 45 | +        D(X_mid2) ~ p_mid2 - d*X_mid2, | 
|  | 46 | +        X_mid2^3 ~ log(X_mid2 + Y) - Y^2 + 3.0, | 
|  | 47 | +        O ~ (p_mid2 + d)*X_mid2 + Y | 
|  | 48 | +    ] | 
|  | 49 | +    eqs_bot = [ | 
|  | 50 | +        D(X_bot) ~ p_bot - d*X_bot, | 
|  | 51 | +        D(Y) ~ -Y^3, | 
|  | 52 | +        O ~ (p_bot + d)*X_bot + Y | 
|  | 53 | +    ] | 
|  | 54 | +    cevs = [[t ~ 1.0] => [Y ~ Y + 2.0]] | 
|  | 55 | +    devs = [(t == 2.0) => [Y ~ Y + 2.0]] | 
|  | 56 | +    @named sys_bot = ODESystem(eqs_bot, t; systems = [], continuous_events = cevs, discrete_events = devs) | 
|  | 57 | +    @named sys_mid2 = ODESystem(eqs_mid2, t; systems = [], continuous_events = cevs, discrete_events = devs) | 
|  | 58 | +    @named sys_mid1 = ODESystem(eqs_mid1, t; systems = [sys_bot], continuous_events = cevs, discrete_events = devs) | 
|  | 59 | +    @named sys_top = ODESystem(eqs_top, t; systems = [sys_mid1, sys_mid2], continuous_events = cevs, discrete_events = devs) | 
|  | 60 | +    sys_bot_comp = complete(sys_bot) | 
|  | 61 | +    sys_mid2_comp = complete(sys_mid2) | 
|  | 62 | +    sys_mid1_comp = complete(sys_mid1) | 
|  | 63 | +    sys_top_comp = complete(sys_top) | 
|  | 64 | +    sys_bot_ss = structural_simplify(sys_bot) | 
|  | 65 | +    sys_mid2_ss = structural_simplify(sys_mid2) | 
|  | 66 | +    sys_mid1_ss = structural_simplify(sys_mid1) | 
|  | 67 | +    sys_top_ss = structural_simplify(sys_top) | 
|  | 68 | + | 
|  | 69 | +    # Checks `parameters` for `toplevel = false`. | 
|  | 70 | +    @test all_sets_equal(parameters.([sys_bot, sys_bot_comp, sys_bot_ss])..., [d, p_bot]) | 
|  | 71 | +    @test all_sets_equal(parameters.([sys_mid1, sys_mid1_comp, sys_mid1_ss])..., [d, p_mid1, sys_bot.d, sys_bot.p_bot]) | 
|  | 72 | +    @test all_sets_equal(parameters.([sys_mid2, sys_mid2_comp, sys_mid2_ss])..., [d, p_mid2]) | 
|  | 73 | +    @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]) | 
|  | 74 | + | 
|  | 75 | +    # Checks `parameters`` for `toplevel = true`. Compares to known parameters and also checks that | 
|  | 76 | +    # these are subset of what `get_ps` returns. | 
|  | 77 | +    @test all_sets_equal(parameters.([sys_bot, sys_bot_comp, sys_bot_ss]; toplevel = true)..., [d, p_bot]) | 
|  | 78 | +    @test_broken all_sets_equal(parameters.([sys_mid1, sys_mid1_comp, sys_mid1_ss]; toplevel = true)..., [d, p_mid1]) | 
|  | 79 | +    @test all_sets_equal(parameters.([sys_mid2, sys_mid2_comp, sys_mid2_ss]; toplevel = true)..., [d, p_mid2]) | 
|  | 80 | +    @test_broken all_sets_equal(parameters.([sys_top, sys_top_comp, sys_top_ss]; toplevel = true)..., [d, p_top]) | 
|  | 81 | +    @test all(sym_issubset(parameters(sys; toplevel = true), get_ps(sys)) for sys in [sys_bot, sys_mid2, sys_mid1, sys_top]) | 
|  | 82 | + | 
|  | 83 | +    # Checks `unknowns` for `toplevel = false`. O(t) is eliminated by `structural_simplify` and | 
|  | 84 | +    # must be considered separately. | 
|  | 85 | +    @test all_sets_equal(unknowns.([sys_bot, sys_bot_comp])..., [O, Y, X_bot]) | 
|  | 86 | +    @test all_sets_equal(unknowns.([sys_bot_ss])..., [Y, X_bot]) | 
|  | 87 | +    @test all_sets_equal(unknowns.([sys_mid1, sys_mid1_comp])..., [O, Y, X_mid1, sys_bot.Y, sys_bot.O, sys_bot.X_bot]) | 
|  | 88 | +    @test all_sets_equal(unknowns.([sys_mid1_ss])..., [Y, X_mid1, sys_bot.Y, sys_bot.X_bot]) | 
|  | 89 | +    @test all_sets_equal(unknowns.([sys_mid2, sys_mid2_comp])..., [O, Y, X_mid2]) | 
|  | 90 | +    @test all_sets_equal(unknowns.([sys_mid2_ss])..., [Y, X_mid2]) | 
|  | 91 | +    @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]) | 
|  | 92 | +    @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]) | 
|  | 93 | + | 
|  | 94 | +    # Checks `unknowns` for `toplevel = true`. Note that O is not eliminated here (as we go back | 
|  | 95 | +    # to original parent system). Also checks that outputs are subsets of what `get_ps` returns.. | 
|  | 96 | +    @test all_sets_equal(unknowns.([sys_bot, sys_bot_comp, sys_bot_ss]; toplevel = true)..., [O, Y, X_bot]) | 
|  | 97 | +    @test all_sets_equal(unknowns.([sys_mid1, sys_mid1_comp]; toplevel = true)..., [O, Y, X_mid1]) | 
|  | 98 | +    @test all_sets_equal(unknowns.([sys_mid2, sys_mid2_comp]; toplevel = true)..., [O, Y, X_mid2]) | 
|  | 99 | +    @test all_sets_equal(unknowns.([sys_top, sys_top_comp]; toplevel = true)..., [O, Y, X_top]) | 
|  | 100 | +    @test all(sym_issubset(unknowns(sys; toplevel = true), get_unknowns(sys)) for sys in [sys_bot, sys_mid1, sys_mid2, sys_top]) | 
|  | 101 | + | 
|  | 102 | +    # Checks `equations` for `toplevel = false`. Do not check ss equations as these might potentially | 
|  | 103 | +    # be structurally simplified to new equations. | 
|  | 104 | +    @test all_sets_equal(equations.([sys_bot, sys_bot_comp])..., eqs_bot) | 
|  | 105 | +    @test all_sets_equal(equations.([sys_mid1, sys_mid1_comp])..., [eqs_mid1; namespace_equations(sys_bot)]) | 
|  | 106 | +    @test all_sets_equal(equations.([sys_mid2, sys_mid2_comp])..., eqs_mid2) | 
|  | 107 | +    @test all_sets_equal(equations.([sys_top, sys_top_comp])..., [eqs_top; namespace_equations(sys_mid1); namespace_equations(sys_mid2)]) | 
|  | 108 | + | 
|  | 109 | +    # Checks `equations` for `toplevel = true`. Do not check ss equations  directly as these | 
|  | 110 | +    # might potentially be structurally simplified to new equations. | 
|  | 111 | +    @test all_sets_equal(equations.([sys_bot, sys_bot_comp]; toplevel = true)..., eqs_bot) | 
|  | 112 | +    @test all_sets_equal(equations.([sys_mid1, sys_mid1_comp]; toplevel = true)..., eqs_mid1) | 
|  | 113 | +    @test all_sets_equal(equations.([sys_mid2, sys_mid2_comp]; toplevel = true)..., eqs_mid2) | 
|  | 114 | +    @test all_sets_equal(equations.([sys_top, sys_top_comp]; toplevel = true)..., eqs_top) | 
|  | 115 | +    @test all(sym_issubset(equations(sys; toplevel = true), get_eqs(sys)) for sys in [sys_bot, sys_mid2, sys_mid1, sys_top]) | 
|  | 116 | + | 
|  | 117 | +    # Checks `observed for `toplevel = false`. For non-ss systems, there are no observables. | 
|  | 118 | +    @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])..., []) | 
|  | 119 | +    @test issetequal(observed(sys_bot_ss), eqs_bot[3:3]) | 
|  | 120 | +    @test issetequal(observed(sys_mid1_ss), [eqs_mid1[3:3]; namespace_equations(sys_bot)[3:3]]) | 
|  | 121 | +    @test issetequal(observed(sys_mid2_ss), eqs_mid2[3:3]) | 
|  | 122 | +    @test issetequal(observed(sys_top_ss), [eqs_top[3:3]; namespace_equations(sys_mid1)[3:3:6]; namespace_equations(sys_mid2)[3:3]]) | 
|  | 123 | + | 
|  | 124 | +    # Checks `observed` for `toplevel = true`. Again, for non-ss systems, there are no observables. | 
|  | 125 | +    # Also checks that `toplevel = true` yields subset of `get_observed`. | 
|  | 126 | +    @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)..., []) | 
|  | 127 | +    @test_broken issetequal(observed(sys_bot_ss; toplevel = true), eqs_bot[3:3]) | 
|  | 128 | +    @test_broken issetequal(observed(sys_mid1_ss; toplevel = true), eqs_mid1[3:3]) | 
|  | 129 | +    @test_broken issetequal(observed(sys_mid2_ss; toplevel = true), eqs_mid2[3:3]) | 
|  | 130 | +    @test_broken issetequal(observed(sys_top_ss; toplevel = true), eqs_top[3:3]) | 
|  | 131 | +    @test all(sym_issubset(observed(sys; toplevel = true), get_observed(sys)) for sys in [sys_bot, sys_mid2, sys_mid1, sys_top]) | 
|  | 132 | + | 
|  | 133 | +    # Checks `continuous_events` and  `discrete_events` for `toplevel = true` (more straightforward | 
|  | 134 | +    # as I stored the same singe event in all systems). Don't check for `toplevel = false` as | 
|  | 135 | +    # technically not needed for these tests and name spacing the events is a mess. | 
|  | 136 | +    mtk_cev = ModelingToolkit.SymbolicContinuousCallback.(cevs)[1] | 
|  | 137 | +    mtk_dev = ModelingToolkit.SymbolicDiscreteCallback.(devs)[1] | 
|  | 138 | +    @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]) | 
|  | 139 | +    @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]) | 
|  | 140 | +    @test all(sym_issubset(continuous_events(sys; toplevel = true), get_continuous_events(sys)) for sys in [sys_bot, sys_mid2, sys_mid1, sys_top]) | 
|  | 141 | +    @test all(sym_issubset(discrete_events(sys; toplevel = true), get_discrete_events(sys)) for sys in [sys_bot, sys_mid2, sys_mid1, sys_top]) | 
|  | 142 | +end | 
0 commit comments