From c4fa35573cebc43ea4f7c0a4ccd3715c5b30c0d1 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Mon, 14 Apr 2025 18:57:00 +0530 Subject: [PATCH 001/122] chore!: bump MAJOR version --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 1de504cbae..47cec3add7 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "ModelingToolkit" uuid = "961ee093-0014-501f-94e3-6117800e7a78" authors = ["Yingbo Ma ", "Chris Rackauckas and contributors"] -version = "9.76.0" +version = "10.0.0" [deps] ADTypes = "47edcb42-4c32-4615-8424-f2b9edc5f35b" From d2e08213cf3fac871bb61d96f6c488424513d8e6 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Mon, 14 Apr 2025 18:57:09 +0530 Subject: [PATCH 002/122] ci: run workflows on PR to v10 branch --- .github/workflows/Documentation.yml | 1 + .github/workflows/FormatCheck.yml | 1 + .github/workflows/Tests.yml | 1 + 3 files changed, 3 insertions(+) diff --git a/.github/workflows/Documentation.yml b/.github/workflows/Documentation.yml index 14bea532b3..e696b42380 100644 --- a/.github/workflows/Documentation.yml +++ b/.github/workflows/Documentation.yml @@ -4,6 +4,7 @@ on: push: branches: - master + - v10 tags: '*' pull_request: diff --git a/.github/workflows/FormatCheck.yml b/.github/workflows/FormatCheck.yml index 6185015c44..0d3052b969 100644 --- a/.github/workflows/FormatCheck.yml +++ b/.github/workflows/FormatCheck.yml @@ -4,6 +4,7 @@ on: push: branches: - 'master' + - v10 tags: '*' pull_request: diff --git a/.github/workflows/Tests.yml b/.github/workflows/Tests.yml index 52c5482970..a62f7f272d 100644 --- a/.github/workflows/Tests.yml +++ b/.github/workflows/Tests.yml @@ -5,6 +5,7 @@ on: branches: - master - 'release-' + - v10 paths-ignore: - 'docs/**' push: From eb2e1fbce0bf1f678f8160ac167271fc8157d520 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Mon, 14 Apr 2025 19:10:06 +0530 Subject: [PATCH 003/122] docs: bump MTK compat --- docs/Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Project.toml b/docs/Project.toml index ca13773492..ae0d89dd3a 100644 --- a/docs/Project.toml +++ b/docs/Project.toml @@ -40,7 +40,7 @@ Documenter = "1" DynamicQuantities = "^0.11.2, 0.12, 1" FMI = "0.14" FMIZoo = "1" -ModelingToolkit = "8.33, 9" +ModelingToolkit = "10" ModelingToolkitStandardLibrary = "2.19" NonlinearSolve = "3, 4" Optim = "1.7" From 6eba59469cd740de72ee30c0d878ff9e495ce0cb Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Mon, 14 Apr 2025 19:15:37 +0530 Subject: [PATCH 004/122] TEMP COMMIT: use branch of MTKStdlib --- Project.toml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 47cec3add7..1d7899d521 100644 --- a/Project.toml +++ b/Project.toml @@ -174,6 +174,7 @@ Logging = "56ddb016-857b-54e1-b83d-db4d58db5568" ModelingToolkitStandardLibrary = "16a59e39-deab-5bd0-87e4-056b12336739" NonlinearSolve = "8913a72c-1f9b-4ce2-8d82-65094dcecaec" Optimization = "7f7a1694-90dd-40f0-9382-eb1efda571ba" +OptimizationBase = "bca83a33-5cc9-4baa-983d-23429ab6bcbb" OptimizationMOI = "fd9f6733-72f4-499f-8506-86b2bdd0dea1" OptimizationOptimJL = "36348300-93cb-4f02-beb5-3c3902f8871e" OrdinaryDiffEq = "1dea7af3-3e70-54e6-95c3-0bf5283fa5ed" @@ -193,5 +194,10 @@ StochasticDiffEq = "789caeaf-c7a9-5a7d-9973-96adeb23e2a0" Sundials = "c3572dad-4567-51f8-b174-8c6c989267f4" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" +[sources] +ModelingToolkitStandardLibrary = { url = "https://github.com/SciML/ModelingToolkitStandardLibrary.jl/", rev = "mtk-v10" } +OptimizationBase = { url = "https://github.com/AayushSabharwal/OptimizationBase.jl", rev = "as/mtk-v10" } +OptimizationMOI = { url = "https://github.com/AayushSabharwal/Optimization.jl", subdir = "lib/OptimizationMOI", rev = "as/mtk-v10" } + [targets] -test = ["AmplNLWriter", "BenchmarkTools", "BoundaryValueDiffEqMIRK", "BoundaryValueDiffEqAscher", "ControlSystemsBase", "DataInterpolations", "DelayDiffEq", "NonlinearSolve", "ForwardDiff", "Ipopt", "Ipopt_jll", "ModelingToolkitStandardLibrary", "Optimization", "OptimizationOptimJL", "OptimizationMOI", "OrdinaryDiffEq", "OrdinaryDiffEqCore", "OrdinaryDiffEqDefault", "REPL", "Random", "ReferenceTests", "SafeTestsets", "StableRNGs", "Statistics", "SteadyStateDiffEq", "Test", "StochasticDiffEq", "Sundials", "StochasticDelayDiffEq", "Pkg", "JET", "OrdinaryDiffEqNonlinearSolve", "Logging"] +test = ["AmplNLWriter", "BenchmarkTools", "BoundaryValueDiffEqMIRK", "BoundaryValueDiffEqAscher", "ControlSystemsBase", "DataInterpolations", "DelayDiffEq", "NonlinearSolve", "ForwardDiff", "Ipopt", "Ipopt_jll", "ModelingToolkitStandardLibrary", "Optimization", "OptimizationOptimJL", "OptimizationMOI", "OrdinaryDiffEq", "OrdinaryDiffEqCore", "OrdinaryDiffEqDefault", "REPL", "Random", "ReferenceTests", "SafeTestsets", "StableRNGs", "Statistics", "SteadyStateDiffEq", "Test", "StochasticDiffEq", "Sundials", "StochasticDelayDiffEq", "Pkg", "JET", "OrdinaryDiffEqNonlinearSolve", "Logging", "OptimizationBase"] From b976833d2a1731a213f6a7f0653cce0631958e24 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Thu, 3 Apr 2025 17:07:53 +0530 Subject: [PATCH 005/122] feat: make `@named` always wrap arguments in `ParentScope` --- src/systems/abstractsystem.jl | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/systems/abstractsystem.jl b/src/systems/abstractsystem.jl index ce70a33fe2..305e3164b8 100644 --- a/src/systems/abstractsystem.jl +++ b/src/systems/abstractsystem.jl @@ -2398,11 +2398,7 @@ function default_to_parentscope(v) uv = unwrap(v) uv isa Symbolic || return v apply_to_variables(v) do sym - if !hasmetadata(uv, SymScope) - ParentScope(sym) - else - sym - end + ParentScope(sym) end end From 50451d24d4059b9ea133afd3cac2a6a45b21b87a Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Mon, 14 Apr 2025 21:38:27 +0530 Subject: [PATCH 006/122] test: test `@named` always wrapping in `ParentScope` --- test/odesystem.jl | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/test/odesystem.jl b/test/odesystem.jl index afb9e6e440..d582c2c197 100644 --- a/test/odesystem.jl +++ b/test/odesystem.jl @@ -9,6 +9,7 @@ using SymbolicUtils: issym using ForwardDiff using ModelingToolkit: value using ModelingToolkit: t_nounits as t, D_nounits as D +using Symbolics: unwrap # Define some variables @parameters σ ρ β @@ -1732,3 +1733,26 @@ end @test obsfn_expr_oop isa Expr @test obsfn_expr_iip isa Expr end + +@testset "`@named` always wraps in `ParentScope`" begin + function SysA(; name, var1) + @variables x(t) + scope = ModelingToolkit.getmetadata(unwrap(var1), ModelingToolkit.SymScope, nothing) + @test scope isa ParentScope + @test scope.parent isa ParentScope + @test scope.parent.parent isa LocalScope + return ODESystem(D(x) ~ var1, t; name) + end + function SysB(; name, var1) + @variables x(t) + @named subsys = SysA(; var1) + return ODESystem(D(x) ~ x, t; systems = [subsys], name) + end + function SysC(; name) + @variables x(t) + @named subsys = SysB(; var1 = x) + return ODESystem(D(x) ~ x, t; systems = [subsys], name) + end + @mtkbuild sys = SysC() + @test length(unknowns(sys)) == 3 +end From e0844d71128e2f5e872c02ceb017ce2f6a475704 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Mon, 14 Apr 2025 19:47:53 +0530 Subject: [PATCH 007/122] refactor: remove `DelayParentScope` --- docs/src/basics/Composition.md | 20 +++------ src/ModelingToolkit.jl | 2 +- src/systems/abstractsystem.jl | 58 ------------------------ src/utils.jl | 2 - test/jumpsystem.jl | 22 +++++----- test/variable_scope.jl | 80 +++++++++++++++------------------- 6 files changed, 53 insertions(+), 131 deletions(-) diff --git a/docs/src/basics/Composition.md b/docs/src/basics/Composition.md index 2e5d4be831..d3de71d696 100644 --- a/docs/src/basics/Composition.md +++ b/docs/src/basics/Composition.md @@ -135,16 +135,14 @@ sys.y = u * 1.1 In a hierarchical system, variables of the subsystem get namespaced by the name of the system they are in. This prevents naming clashes, but also enforces that every unknown and parameter is local to the subsystem it is used in. In some cases it might be desirable to have variables and parameters that are shared between subsystems, or even global. This can be accomplished as follows. ```julia -@parameters a b c d e f +@parameters a b c d # a is a local variable b = ParentScope(b) # b is a variable that belongs to one level up in the hierarchy c = ParentScope(ParentScope(c)) # ParentScope can be nested -d = DelayParentScope(d) # skips one level before applying ParentScope -e = DelayParentScope(e, 2) # second argument allows skipping N levels -f = GlobalScope(f) +d = GlobalScope(d) -p = [a, b, c, d, e, f] +p = [a, b, c, d] level0 = ODESystem(Equation[], t, [], p; name = :level0) level1 = ODESystem(Equation[], t, [], []; name = :level1) ∘ level0 @@ -152,25 +150,19 @@ parameters(level1) #level0₊a #b #c -#level0₊d -#level0₊e -#f +#d level2 = ODESystem(Equation[], t, [], []; name = :level2) ∘ level1 parameters(level2) #level1₊level0₊a #level1₊b #c -#level0₊d -#level1₊level0₊e -#f +#d level3 = ODESystem(Equation[], t, [], []; name = :level3) ∘ level2 parameters(level3) #level2₊level1₊level0₊a #level2₊level1₊b #level2₊c -#level2₊level0₊d -#level1₊level0₊e -#f +#d ``` ## Structural Simplify diff --git a/src/ModelingToolkit.jl b/src/ModelingToolkit.jl index 992690cfe0..198a84d48b 100644 --- a/src/ModelingToolkit.jl +++ b/src/ModelingToolkit.jl @@ -297,7 +297,7 @@ export PDESystem export Differential, expand_derivatives, @derivatives export Equation, ConstrainedEquation export Term, Sym -export SymScope, LocalScope, ParentScope, DelayParentScope, GlobalScope +export SymScope, LocalScope, ParentScope, GlobalScope export independent_variable, equations, controls, observed, full_equations export initialization_equations, guesses, defaults, parameter_dependencies, hierarchy export structural_simplify, expand_connections, linearize, linearization_function, diff --git a/src/systems/abstractsystem.jl b/src/systems/abstractsystem.jl index 305e3164b8..caec10b288 100644 --- a/src/systems/abstractsystem.jl +++ b/src/systems/abstractsystem.jl @@ -1190,55 +1190,6 @@ function ParentScope(sym::Union{Num, Symbolic, Symbolics.Arr{Num}}) end end -""" - $(TYPEDEF) - -Denotes that a variable belongs to a system that is at least `N + 1` levels up in the -hierarchy from the system whose equations it is involved in. It is namespaced by the -first `N` parents and not namespaced by the `N+1`th parent in the hierarchy. The scope -of the variable after this point is given by `parent`. - -In other words, this scope delays applying `ParentScope` by `N` levels, and applies -`LocalScope` in the meantime. - -# Fields - -$(TYPEDFIELDS) -""" -struct DelayParentScope <: SymScope - parent::SymScope - N::Int -end - -""" - $(TYPEDSIGNATURES) - -Apply `DelayParentScope` to `sym`, with a delay of `N` and `parent` being `LocalScope`. -""" -function DelayParentScope(sym::Union{Num, Symbolic, Symbolics.Arr{Num}}, N) - Base.depwarn( - "`DelayParentScope` is deprecated and will be removed soon", :DelayParentScope) - apply_to_variables(sym) do sym - if iscall(sym) && operation(sym) == getindex - args = arguments(sym) - a1 = setmetadata(args[1], SymScope, - DelayParentScope(getmetadata(value(args[1]), SymScope, LocalScope()), N)) - maketerm(typeof(sym), operation(sym), [a1, args[2:end]...], - metadata(sym)) - else - setmetadata(sym, SymScope, - DelayParentScope(getmetadata(value(sym), SymScope, LocalScope()), N)) - end - end -end - -""" - $(TYPEDSIGNATURES) - -Apply `DelayParentScope` to `sym`, with a delay of `1` and `parent` being `LocalScope`. -""" -DelayParentScope(sym::Union{Num, Symbolic, Symbolics.Arr{Num}}) = DelayParentScope(sym, 1) - """ $(TYPEDEF) @@ -1291,15 +1242,6 @@ function renamespace(sys, x) rename(x, renamespace(getname(sys), getname(x)))::T elseif scope isa ParentScope setmetadata(x, SymScope, scope.parent)::T - elseif scope isa DelayParentScope - if scope.N > 0 - x = setmetadata(x, SymScope, - DelayParentScope(scope.parent, scope.N - 1)) - rename(x, renamespace(getname(sys), getname(x)))::T - else - #rename(x, renamespace(getname(sys), getname(x))) - setmetadata(x, SymScope, scope.parent)::T - end else # GlobalScope x::T end diff --git a/src/utils.jl b/src/utils.jl index 1884a91c19..2b3cbedab0 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -632,8 +632,6 @@ function check_scope_depth(scope, depth) return depth == 0 elseif scope isa ParentScope return depth > 0 && check_scope_depth(scope.parent, depth - 1) - elseif scope isa DelayParentScope - return depth >= scope.N && check_scope_depth(scope.parent, depth - scope.N - 1) elseif scope isa GlobalScope return depth == -1 end diff --git a/test/jumpsystem.jl b/test/jumpsystem.jl index 1ee0408758..6c96055270 100644 --- a/test/jumpsystem.jl +++ b/test/jumpsystem.jl @@ -378,22 +378,20 @@ end # scoping tests let - @variables x1(t) x2(t) x3(t) x4(t) x5(t) + @variables x1(t) x2(t) x3(t) x4(t) x2 = ParentScope(x2) x3 = ParentScope(ParentScope(x3)) - x4 = DelayParentScope(x4) - x5 = GlobalScope(x5) - @parameters p1 p2 p3 p4 p5 + x4 = GlobalScope(x4) + @parameters p1 p2 p3 p4 p2 = ParentScope(p2) p3 = ParentScope(ParentScope(p3)) - p4 = DelayParentScope(p4) - p5 = GlobalScope(p5) + p4 = GlobalScope(p4) j1 = ConstantRateJump(p1, [x1 ~ x1 + 1]) j2 = MassActionJump(p2, [x2 => 1], [x3 => -1]) j3 = VariableRateJump(p3, [x3 ~ x3 + 1, x4 ~ x4 + 1]) - j4 = MassActionJump(p4 * p5, [x1 => 1, x5 => 1], [x1 => -1, x5 => -1, x2 => 1]) - @named js = JumpSystem([j1, j2, j3, j4], t, [x1, x2, x3, x4, x5], [p1, p2, p3, p4, p5]) + j4 = MassActionJump(p4 * p4, [x1 => 1, x4 => 1], [x1 => -1, x4 => -1, x2 => 1]) + @named js = JumpSystem([j1, j2, j3, j4], t, [x1, x2, x3, x4], [p1, p2, p3, p4]) us = Set() ps = Set() @@ -414,13 +412,13 @@ let empty!.((us, ps)) MT.collect_scoped_vars!(us, ps, js, iv; depth = 2) - @test issetequal(us, [x3, x4]) - @test issetequal(ps, [p3, p4]) + @test issetequal(us, [x3]) + @test issetequal(ps, [p3]) empty!.((us, ps)) MT.collect_scoped_vars!(us, ps, js, iv; depth = -1) - @test issetequal(us, [x5]) - @test issetequal(ps, [p5]) + @test issetequal(us, [x4]) + @test issetequal(ps, [p4]) end # PDMP test diff --git a/test/variable_scope.jl b/test/variable_scope.jl index bd1d3cb0cf..59647bf441 100644 --- a/test/variable_scope.jl +++ b/test/variable_scope.jl @@ -51,13 +51,11 @@ end @test renamed([:foo :bar :baz], c) == Symbol("foo₊c") @test renamed([:foo :bar :baz], d) == :d -@parameters a b c d e f +@parameters a b c d p = [a ParentScope(b) ParentScope(ParentScope(c)) - DelayParentScope(d) - DelayParentScope(e, 2) - GlobalScope(f)] + GlobalScope(d)] level0 = ODESystem(Equation[], t, [], p; name = :level0) level1 = ODESystem(Equation[], t, [], []; name = :level1) ∘ level0 @@ -69,9 +67,7 @@ ps = ModelingToolkit.getname.(parameters(level3)) @test isequal(ps[1], :level2₊level1₊level0₊a) @test isequal(ps[2], :level2₊level1₊b) @test isequal(ps[3], :level2₊c) -@test isequal(ps[4], :level2₊level0₊d) -@test isequal(ps[5], :level1₊level0₊e) -@test isequal(ps[6], :f) +@test isequal(ps[4], :d) # Issue@2252 # Tests from PR#2354 @@ -102,40 +98,36 @@ defs = ModelingToolkit.defaults(bar) @test defs[bar.p] == 2 @test isequal(defs[bar.foo.p], bar.p) -# Issue#3101 -@variables x1(t) x2(t) x3(t) x4(t) x5(t) -x2 = ParentScope(x2) -x3 = ParentScope(ParentScope(x3)) -x4 = DelayParentScope(x4) -x5 = GlobalScope(x5) -@parameters p1 p2 p3 p4 p5 -p2 = ParentScope(p2) -p3 = ParentScope(ParentScope(p3)) -p4 = DelayParentScope(p4) -p5 = GlobalScope(p5) - -@named sys1 = ODESystem([D(x1) ~ p1, D(x2) ~ p2, D(x3) ~ p3, D(x4) ~ p4, D(x5) ~ p5], t) -@test isequal(x1, only(unknowns(sys1))) -@test isequal(p1, only(parameters(sys1))) -@named sys2 = ODESystem(Equation[], t; systems = [sys1]) -@test length(unknowns(sys2)) == 2 -@test any(isequal(x2), unknowns(sys2)) -@test length(parameters(sys2)) == 2 -@test any(isequal(p2), parameters(sys2)) -@named sys3 = ODESystem(Equation[], t) -sys3 = sys3 ∘ sys2 -@test length(unknowns(sys3)) == 4 -@test any(isequal(x3), unknowns(sys3)) -@test any(isequal(ModelingToolkit.renamespace(sys1, x4)), unknowns(sys3)) -@test length(parameters(sys3)) == 4 -@test any(isequal(p3), parameters(sys3)) -@test any(isequal(ModelingToolkit.renamespace(sys1, p4)), parameters(sys3)) -sys4 = complete(sys3) -@test length(unknowns(sys3)) == 4 -@test length(parameters(sys4)) == 5 -@test any(isequal(p5), parameters(sys4)) -sys5 = structural_simplify(sys3) -@test length(unknowns(sys5)) == 5 -@test any(isequal(x5), unknowns(sys5)) -@test length(parameters(sys5)) == 5 -@test any(isequal(p5), parameters(sys5)) +@testset "Issue#3101" begin + @variables x1(t) x2(t) x3(t) x4(t) + x2 = ParentScope(x2) + x3 = ParentScope(ParentScope(x3)) + x4 = GlobalScope(x4) + @parameters p1 p2 p3 p4 + p2 = ParentScope(p2) + p3 = ParentScope(ParentScope(p3)) + p4 = GlobalScope(p4) + + @named sys1 = ODESystem([D(x1) ~ p1, D(x2) ~ p2, D(x3) ~ p3, D(x4) ~ p4], t) + @test isequal(x1, only(unknowns(sys1))) + @test isequal(p1, only(parameters(sys1))) + @named sys2 = ODESystem(Equation[], t; systems = [sys1]) + @test length(unknowns(sys2)) == 2 + @test any(isequal(x2), unknowns(sys2)) + @test length(parameters(sys2)) == 2 + @test any(isequal(p2), parameters(sys2)) + @named sys3 = ODESystem(Equation[], t) + sys3 = sys3 ∘ sys2 + @test length(unknowns(sys3)) == 3 + @test any(isequal(x3), unknowns(sys3)) + @test length(parameters(sys3)) == 3 + @test any(isequal(p3), parameters(sys3)) + sys4 = complete(sys3) + @test length(unknowns(sys4)) == 3 + @test length(parameters(sys4)) == 4 + sys5 = structural_simplify(sys3) + @test length(unknowns(sys5)) == 4 + @test any(isequal(x4), unknowns(sys5)) + @test length(parameters(sys5)) == 4 + @test any(isequal(p4), parameters(sys5)) +end From 086bab65121230f5a168b3d94edb1945c700013b Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Mon, 14 Apr 2025 20:15:04 +0530 Subject: [PATCH 008/122] refactor: remove `time_varying_as_func` --- src/inputoutput.jl | 2 +- src/systems/abstractsystem.jl | 14 -------------- src/systems/callbacks.jl | 8 ++++---- src/systems/codegen_utils.jl | 11 ----------- 4 files changed, 5 insertions(+), 30 deletions(-) diff --git a/src/inputoutput.jl b/src/inputoutput.jl index 5f9420ff3a..28b60b13dc 100644 --- a/src/inputoutput.jl +++ b/src/inputoutput.jl @@ -218,7 +218,7 @@ function generate_control_function(sys::AbstractODESystem, inputs = unbound_inpu inputs = setdiff(inputs, disturbance_inputs) # ps = [ps; disturbance_inputs] end - inputs = map(x -> time_varying_as_func(value(x), sys), inputs) + inputs = map(value, inputs) disturbance_inputs = unwrap.(disturbance_inputs) eqs = [eq for eq in full_equations(sys)] diff --git a/src/systems/abstractsystem.jl b/src/systems/abstractsystem.jl index caec10b288..1252367d2b 100644 --- a/src/systems/abstractsystem.jl +++ b/src/systems/abstractsystem.jl @@ -1791,20 +1791,6 @@ function isaffine(sys::AbstractSystem) all(isaffine(r, unknowns(sys)) for r in rhs) end -function time_varying_as_func(x, sys::AbstractTimeDependentSystem) - # if something is not x(t) (the current unknown) - # but is `x(t-1)` or something like that, pass in `x` as a callable function rather - # than pass in a value in place of x(t). - # - # This is done by just making `x` the argument of the function. - if iscall(x) && - issym(operation(x)) && - !(length(arguments(x)) == 1 && isequal(arguments(x)[1], get_iv(sys))) - return operation(x) - end - return x -end - """ $(SIGNATURES) diff --git a/src/systems/callbacks.jl b/src/systems/callbacks.jl index 07809bf611..7d542d9bd0 100644 --- a/src/systems/callbacks.jl +++ b/src/systems/callbacks.jl @@ -598,8 +598,8 @@ Notes """ function compile_condition(cb::SymbolicDiscreteCallback, sys, dvs, ps; expression = Val{true}, eval_expression = false, eval_module = @__MODULE__, kwargs...) - u = map(x -> time_varying_as_func(value(x), sys), dvs) - p = map.(x -> time_varying_as_func(value(x), sys), reorder_parameters(sys, ps)) + u = map(value, dvs) + p = map.(value, reorder_parameters(sys, ps)) t = get_iv(sys) condit = condition(cb) cs = collect_constants(condit) @@ -685,8 +685,8 @@ function compile_affect(eqs::Vector{Equation}, cb, sys, dvs, ps; outputidxs = no _ps = ps ps = reorder_parameters(sys, ps) if checkvars - u = map(x -> time_varying_as_func(value(x), sys), dvs) - p = map.(x -> time_varying_as_func(value(x), sys), ps) + u = map(value, dvs) + p = map.(value, ps) else u = dvs p = ps diff --git a/src/systems/codegen_utils.jl b/src/systems/codegen_utils.jl index a3fe53b95d..bedfdbcc37 100644 --- a/src/systems/codegen_utils.jl +++ b/src/systems/codegen_utils.jl @@ -179,17 +179,6 @@ function build_function_wrapper(sys::AbstractSystem, expr, args...; p_start = 2, args = ntuple(Val(length(args))) do i arg = args[i] - # for time-dependent systems, all arguments are passed through `time_varying_as_func` - # TODO: This is legacy behavior and a candidate for removal in v10 since we have callable - # parameters now. - if is_time_dependent(sys) - arg = if symbolic_type(arg) == NotSymbolic() - arg isa AbstractArray ? - map(x -> time_varying_as_func(unwrap(x), sys), arg) : arg - else - time_varying_as_func(unwrap(arg), sys) - end - end # Make sure to use the proper names for arguments if symbolic_type(arg) == NotSymbolic() && arg isa AbstractArray DestructuredArgs(arg, generated_argument_name(i); create_bindings) From 607c0a506e7d53e224b67da30271d0f0c27c0aaf Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Mon, 14 Apr 2025 20:37:56 +0530 Subject: [PATCH 009/122] test: update tests with removed `time_varying_as_func` --- test/odesystem.jl | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/test/odesystem.jl b/test/odesystem.jl index d582c2c197..f5fef1fd6f 100644 --- a/test/odesystem.jl +++ b/test/odesystem.jl @@ -136,19 +136,7 @@ du = zeros(3) tgrad_iip(du, u, p, t) @test du == [0.0, -u[2], 0.0] -@parameters σ′(t - 1) -eqs = [D(x) ~ σ′ * (y - x), - D(y) ~ x * (ρ - z) - y, - D(z) ~ x * y - β * z * κ] -@named de = ODESystem(eqs, t) -test_diffeq_inference("global iv-varying", de, t, (x, y, z), (σ′, ρ, β)) - -f = generate_function(de, [x, y, z], [σ′, ρ, β], expression = Val{false})[2] -du = [0.0, 0.0, 0.0] -f(du, [1.0, 2.0, 3.0], [x -> x + 7, 2, 3], 5.0) -@test du ≈ [11, -3, -7] - -@parameters σ(..) +@parameters (σ::Function)(..) eqs = [D(x) ~ σ(t - 1) * (y - x), D(y) ~ x * (ρ - z) - y, D(z) ~ x * y - β * z * κ] From cfc6cd7277097f644bc8a482309d7c931a5ee459 Mon Sep 17 00:00:00 2001 From: vyudu Date: Fri, 18 Apr 2025 18:02:18 -0400 Subject: [PATCH 010/122] refactor: remove input_idxs output --- src/inputoutput.jl | 18 ++++++------ src/systems/clock_inference.jl | 13 +++++++++ src/systems/systems.jl | 18 +++++++----- src/systems/systemstructure.jl | 51 +++++++++++++++------------------- 4 files changed, 56 insertions(+), 44 deletions(-) diff --git a/src/inputoutput.jl b/src/inputoutput.jl index 28b60b13dc..ebd3e25af6 100644 --- a/src/inputoutput.jl +++ b/src/inputoutput.jl @@ -179,9 +179,6 @@ The return values also include the chosen state-realization (the remaining unkno If `disturbance_inputs` is an array of variables, the generated dynamics function will preserve any state and dynamics associated with disturbance inputs, but the disturbance inputs themselves will (by default) not be included as inputs to the generated function. The use case for this is to generate dynamics for state observers that estimate the influence of unmeasured disturbances, and thus require unknown variables for the disturbance model, but without disturbance inputs since the disturbances are not available for measurement. To add an input argument corresponding to the disturbance inputs, either include the disturbance inputs among the control inputs, or set `disturbance_argument=true`, in which case an additional input argument `w` is added to the generated function `(x,u,p,t,w)->rhs`. -!!! note "Un-simplified system" - This function expects `sys` to be un-simplified, i.e., `structural_simplify` or `@mtkbuild` should not be called on the system before passing it into this function. `generate_control_function` calls a special version of `structural_simplify` internally. - # Example ``` @@ -201,15 +198,17 @@ function generate_control_function(sys::AbstractODESystem, inputs = unbound_inpu eval_expression = false, eval_module = @__MODULE__, kwargs...) - isempty(inputs) && @warn("No unbound inputs were found in system.") + # Remove this when the ControlFunction gets merged. + if !iscomplete(sys) + error("A completed `ODESystem` is required. Call `complete` or `structural_simplify` on the system before creating the control function.") + end + isempty(inputs) && @warn("No unbound inputs were found in system.") if disturbance_inputs !== nothing # add to inputs for the purposes of io processing inputs = [inputs; disturbance_inputs] end - sys, _ = io_preprocessing(sys, inputs, []; simplify, kwargs...) - dvs = unknowns(sys) ps = parameters(sys; initial_parameters = true) ps = setdiff(ps, inputs) @@ -257,8 +256,11 @@ function generate_control_function(sys::AbstractODESystem, inputs = unbound_inpu (; f, dvs, ps, io_sys = sys) end -function inputs_to_parameters!(state::TransformationState, io) - check_bound = io === nothing +""" +Turn input variables into parameters of the system. +""" +function inputs_to_parameters!(state::TransformationState, inputsyms) + check_bound = inputsyms === nothing @unpack structure, fullvars, sys = state @unpack var_to_diff, graph, solvable_graph = structure @assert solvable_graph === nothing diff --git a/src/systems/clock_inference.jl b/src/systems/clock_inference.jl index b535773061..42fe28f7c7 100644 --- a/src/systems/clock_inference.jl +++ b/src/systems/clock_inference.jl @@ -1,7 +1,11 @@ struct ClockInference{S} + """Tearing state.""" ts::S + """The time domain (discrete clock, continuous) of each equation.""" eq_domain::Vector{TimeDomain} + """The output time domain (discrete clock, continuous) of each variable.""" var_domain::Vector{TimeDomain} + """The set of variables with concrete domains.""" inferred::BitSet end @@ -67,6 +71,9 @@ function substitute_sample_time(ex, dt) end end +""" +Update the equation-to-time domain mapping by inferring the time domain from the variables. +""" function infer_clocks!(ci::ClockInference) @unpack ts, eq_domain, var_domain, inferred = ci @unpack var_to_diff, graph = ts.structure @@ -132,6 +139,9 @@ function is_time_domain_conversion(v) input_timedomain(o) != output_timedomain(o) end +""" +For multi-clock systems, create a separate system for each clock in the system, along with associated equations. Return the updated tearing state, and the sets of clocked variables associated with each time domain. +""" function split_system(ci::ClockInference{S}) where {S} @unpack ts, eq_domain, var_domain, inferred = ci fullvars = get_fullvars(ts) @@ -143,11 +153,14 @@ function split_system(ci::ClockInference{S}) where {S} cid_to_eq = Vector{Int}[] var_to_cid = Vector{Int}(undef, ndsts(graph)) cid_to_var = Vector{Int}[] + # cid_counter = number of clocks cid_counter = Ref(0) for (i, d) in enumerate(eq_domain) cid = let cid_counter = cid_counter, id_to_clock = id_to_clock, continuous_id = continuous_id + # Fill the clock_to_id dict as you go, + # ContinuousClock() => 1, ... get!(clock_to_id, d) do cid = (cid_counter[] += 1) push!(id_to_clock, d) diff --git a/src/systems/systems.jl b/src/systems/systems.jl index 52f93afb9b..e27916d994 100644 --- a/src/systems/systems.jl +++ b/src/systems/systems.jl @@ -26,13 +26,16 @@ topological sort of the observed equations in `sys`. + `fully_determined=true` controls whether or not an error will be thrown if the number of equations don't match the number of inputs, outputs, and equations. + `sort_eqs=true` controls whether equations are sorted lexicographically before simplification or not. """ -function structural_simplify( - sys::AbstractSystem, io = nothing; additional_passes = [], simplify = false, split = true, +function mtkbuild( + sys::AbstractSystem; additional_passes = [], simplify = false, split = true, allow_symbolic = false, allow_parameter = true, conservative = false, fully_determined = true, + inputs = nothing, outputs = nothing, + disturbance_inputs = nothing, kwargs...) isscheduled(sys) && throw(RepeatedStructuralSimplificationError()) - newsys′ = __structural_simplify(sys, io; simplify, + newsys′ = __structural_simplify(sys; simplify, allow_symbolic, allow_parameter, conservative, fully_determined, + inputs, outputs, disturbance_inputs, kwargs...) if newsys′ isa Tuple @assert length(newsys′) == 2 @@ -70,8 +73,9 @@ function __structural_simplify(sys::SDESystem, args...; kwargs...) return __structural_simplify(ODESystem(sys), args...; kwargs...) end -function __structural_simplify( - sys::AbstractSystem, io = nothing; simplify = false, sort_eqs = true, +function __structural_simplify(sys::AbstractSystem; simplify = false, + inputs = nothing, outputs = nothing, + disturbance_inputs = nothing, kwargs...) sys = expand_connections(sys) state = TearingState(sys; sort_eqs) @@ -90,7 +94,7 @@ function __structural_simplify( end end if isempty(brown_vars) - return structural_simplify!(state, io; simplify, kwargs...) + return structural_simplify!(state; simplify, inputs, outputs, disturbance_inputs, kwargs...) else Is = Int[] Js = Int[] @@ -123,7 +127,7 @@ function __structural_simplify( if !iszero(new_idxs[i]) && invview(var_to_diff)[i] === nothing] # TODO: IO is not handled. - ode_sys = structural_simplify(sys, io; simplify, kwargs...) + ode_sys = structural_simplify(sys; simplify, inputs, outputs, disturbance_inputs, kwargs...) eqs = equations(ode_sys) sorted_g_rows = zeros(Num, length(eqs), size(g, 2)) for (i, eq) in enumerate(eqs) diff --git a/src/systems/systemstructure.jl b/src/systems/systemstructure.jl index e0feb0d34d..aa544c8db1 100644 --- a/src/systems/systemstructure.jl +++ b/src/systems/systemstructure.jl @@ -657,29 +657,22 @@ function Base.show(io::IO, mime::MIME"text/plain", ms::MatchedSystemStructure) printstyled(io, " SelectedState") end -# TODO: clean up -function merge_io(io, inputs) - isempty(inputs) && return io - if io === nothing - io = (inputs, []) - else - io = ([inputs; io[1]], io[2]) - end - return io -end - -function structural_simplify!(state::TearingState, io = nothing; simplify = false, +function structural_simplify!(state::TearingState; simplify = false, check_consistency = true, fully_determined = true, warn_initialize_determined = true, + inputs = nothing, outputs = nothing, + disturbance_inputs = nothing, kwargs...) + if state.sys isa ODESystem ci = ModelingToolkit.ClockInference(state) ci = ModelingToolkit.infer_clocks!(ci) time_domains = merge(Dict(state.fullvars .=> ci.var_domain), Dict(default_toterm.(state.fullvars) .=> ci.var_domain)) - tss, inputs, continuous_id, id_to_clock = ModelingToolkit.split_system(ci) - cont_io = merge_io(io, inputs[continuous_id]) - sys, input_idxs = _structural_simplify!(tss[continuous_id], cont_io; simplify, + tss, clocked_inputs, continuous_id, id_to_clock = ModelingToolkit.split_system(ci) + cont_inputs = [inputs; clocked_inputs[continuous_id]] + sys = _structural_simplify!(tss[continuous_id]; simplify, check_consistency, fully_determined, + cont_inputs, outputs, disturbance_inputs, kwargs...) if length(tss) > 1 if continuous_id > 0 @@ -695,8 +688,9 @@ function structural_simplify!(state::TearingState, io = nothing; simplify = fals discrete_subsystems[i] = sys continue end - dist_io = merge_io(io, inputs[i]) - ss, = _structural_simplify!(state, dist_io; simplify, check_consistency, + disc_inputs = [inputs; clocked_inputs[i]] + ss, = _structural_simplify!(state; simplify, check_consistency, + disc_inputs, outputs, disturbance_inputs, fully_determined, kwargs...) append!(appended_parameters, inputs[i], unknowns(ss)) discrete_subsystems[i] = ss @@ -713,32 +707,31 @@ function structural_simplify!(state::TearingState, io = nothing; simplify = fals for sym in get_ps(sys)] @set! sys.ps = ps else - sys, input_idxs = _structural_simplify!(state, io; simplify, check_consistency, + sys = _structural_simplify!(state; simplify, check_consistency, + inputs, outputs, disturbance_inputs, fully_determined, kwargs...) end - has_io = io !== nothing - return has_io ? (sys, input_idxs) : sys + return sys end -function _structural_simplify!(state::TearingState, io; simplify = false, +function _structural_simplify!(state::TearingState; simplify = false, check_consistency = true, fully_determined = true, warn_initialize_determined = false, dummy_derivative = true, + inputs = nothing, outputs = nothing, + disturbance_inputs = nothing, kwargs...) if fully_determined isa Bool check_consistency &= fully_determined else check_consistency = true end - has_io = io !== nothing + has_io = inputs !== nothing || outputs !== nothing orig_inputs = Set() if has_io - ModelingToolkit.markio!(state, orig_inputs, io...) - end - if io !== nothing - state, input_idxs = ModelingToolkit.inputs_to_parameters!(state, io) - else - input_idxs = 0:-1 # Empty range + ModelingToolkit.markio!(state, orig_inputs, inputs, outputs) + state = ModelingToolkit.inputs_to_parameters!(state, inputs) end + sys, mm = ModelingToolkit.alias_elimination!(state; kwargs...) if check_consistency fully_determined = ModelingToolkit.check_consistency( @@ -761,5 +754,5 @@ function _structural_simplify!(state::TearingState, io; simplify = false, fullunknowns = [observables(sys); unknowns(sys)] @set! sys.observed = ModelingToolkit.topsort_equations(observed(sys), fullunknowns) - ModelingToolkit.invalidate_cache!(sys), input_idxs + ModelingToolkit.invalidate_cache!(sys) end From d3e8841307c6db6ee93a7e2ca9f6b0e9d70ff972 Mon Sep 17 00:00:00 2001 From: vyudu Date: Mon, 21 Apr 2025 14:42:16 -0400 Subject: [PATCH 011/122] refactor: require simplify system for linearization --- docs/src/basics/Linearization.md | 17 ++++------ src/linearization.jl | 55 ++++++++++++++++---------------- src/systems/abstractsystem.jl | 15 --------- 3 files changed, 35 insertions(+), 52 deletions(-) diff --git a/docs/src/basics/Linearization.md b/docs/src/basics/Linearization.md index 1c06ce72d4..78b6d5925d 100644 --- a/docs/src/basics/Linearization.md +++ b/docs/src/basics/Linearization.md @@ -15,7 +15,7 @@ y &= Cx + Du \end{aligned} ``` -The `linearize` function expects the user to specify the inputs ``u`` and the outputs ``y`` using the syntax shown in the example below. The system model is *not* supposed to be simplified before calling `linearize`: +The `linearize` function expects the user to specify the inputs ``u`` and the outputs ``y`` using the syntax shown in the example below. ## Example @@ -29,7 +29,7 @@ eqs = [u ~ kp * (r - y) # P controller D(x) ~ -x + u # First-order plant y ~ x] # Output equation -@named sys = ODESystem(eqs, t) # Do not call @mtkbuild when linearizing +@mtkbuild sys = ODESystem(eqs, t) # Do not call @mtkbuild when linearizing matrices, simplified_sys = linearize(sys, [r], [y]) # Linearize from r to y matrices ``` @@ -45,10 +45,6 @@ using ModelingToolkit: inputs, outputs The model above has 4 variables but only three equations, there is no equation specifying the value of `r` since `r` is an input. This means that only unbalanced models can be linearized, or in other words, models that are balanced and can be simulated _cannot_ be linearized. To learn more about this, see [How to linearize a ModelingToolkit model (YouTube)](https://www.youtube.com/watch?v=-XOux-2XDGI&t=395s). Also see [ModelingToolkitStandardLibrary: Linear analysis](https://docs.sciml.ai/ModelingToolkit/stable/tutorials/linear_analysis/) for utilities that make linearization of completed models easier. -!!! note "Un-simplified system" - - Linearization expects `sys` to be un-simplified, i.e., `structural_simplify` or `@mtkbuild` should not be called on the system before linearizing. - ## Operating point The operating point to linearize around can be specified with the keyword argument `op` like this: `op = Dict(x => 1, r => 2)`. The operating point may include specification of unknown variables, input variables and parameters. For variables that are not specified in `op`, the default value specified in the model will be used if available, if no value is specified, an error is thrown. @@ -79,14 +75,15 @@ eqs = [D(x) ~ v y.u ~ x] @named duffing = ODESystem(eqs, t, systems = [y, u], defaults = [u.u => 0]) +duffing = structural_simplify(duffing, inputs = [u.u], outputs = [y.u]) # pass a constant value for `x`, since it is the variable we will change in operating points -linfun, simplified_sys = linearization_function(duffing, [u.u], [y.u]; op = Dict(x => NaN)); +linfun = linearization_function(duffing, [u.u], [y.u]; op = Dict(x => NaN)); -println(linearize(simplified_sys, linfun; op = Dict(x => 1.0))) -println(linearize(simplified_sys, linfun; op = Dict(x => 0.0))) +println(linearize(duffing, linfun; op = Dict(x => 1.0))) +println(linearize(duffing, linfun; op = Dict(x => 0.0))) -@time linearize(simplified_sys, linfun; op = Dict(x => 0.0)) +@time linearize(duffing, linfun; op = Dict(x => 0.0)) nothing # hide ``` diff --git a/src/linearization.jl b/src/linearization.jl index 77f4422b63..2e958ecc73 100644 --- a/src/linearization.jl +++ b/src/linearization.jl @@ -1,5 +1,5 @@ """ - lin_fun, simplified_sys = linearization_function(sys::AbstractSystem, inputs, outputs; simplify = false, initialize = true, initialization_solver_alg = TrustRegion(), kwargs...) + lin_fun, simplified_sys = linearization_function(sys::AbstractSystem, inputs, outputs; initialize = true, initialization_solver_alg = TrustRegion(), kwargs...) Return a function that linearizes the system `sys`. The function [`linearize`](@ref) provides a higher-level and easier to use interface. @@ -22,7 +22,6 @@ The `simplified_sys` has undergone [`structural_simplify`](@ref) and had any occ - `sys`: An [`ODESystem`](@ref). This function will automatically apply simplification passes on `sys` and return the resulting `simplified_sys`. - `inputs`: A vector of variables that indicate the inputs of the linearized input-output model. - `outputs`: A vector of variables that indicate the outputs of the linearized input-output model. - - `simplify`: Apply simplification in tearing. - `initialize`: If true, a check is performed to ensure that the operating point is consistent (satisfies algebraic equations). If the op is not consistent, initialization is performed. - `initialization_solver_alg`: A NonlinearSolve algorithm to use for solving for a feasible set of state and algebraic variables that satisfies the specified operating point. - `autodiff`: An `ADType` supported by DifferentiationInterface.jl to use for calculating the necessary jacobians. Defaults to using `AutoForwardDiff()` @@ -31,7 +30,7 @@ The `simplified_sys` has undergone [`structural_simplify`](@ref) and had any occ See also [`linearize`](@ref) which provides a higher-level interface. """ function linearization_function(sys::AbstractSystem, inputs, - outputs; simplify = false, + outputs; initialize = true, initializealg = nothing, initialization_abstol = 1e-5, @@ -58,16 +57,7 @@ function linearization_function(sys::AbstractSystem, inputs, outputs = mapreduce(vcat, outputs; init = []) do var symbolic_type(var) == ArraySymbolic() ? collect(var) : [var] end - ssys, diff_idxs, alge_idxs, input_idxs = io_preprocessing(sys, inputs, outputs; - simplify, - kwargs...) - if zero_dummy_der - dummyder = setdiff(unknowns(ssys), unknowns(sys)) - defs = Dict(x => 0.0 for x in dummyder) - @set! ssys.defaults = merge(defs, defaults(ssys)) - op = merge(defs, op) - end - sys = ssys + diff_idxs, alge_idxs = eq_idxs(sys) if initializealg === nothing initializealg = initialize ? OverrideInit() : NoInit() @@ -87,9 +77,9 @@ function linearization_function(sys::AbstractSystem, inputs, p = parameter_values(prob) t0 = current_time(prob) - inputvals = [p[idx] for idx in input_idxs] + inputvals = [prob.ps[i] for i in inputs] - hp_fun = let fun = h, setter = setp_oop(sys, input_idxs) + hp_fun = let fun = h, setter = setp_oop(sys, inputs) function hpf(du, input, u, p, t) p = setter(p, input) fun(du, u, p, t) @@ -113,7 +103,7 @@ function linearization_function(sys::AbstractSystem, inputs, # observed function is a `GeneratedFunctionWrapper` with iip component h_jac = PreparedJacobian{true}(h, similar(prob.u0, size(outputs)), autodiff, prob.u0, DI.Constant(p), DI.Constant(t0)) - pf_fun = let fun = prob.f, setter = setp_oop(sys, input_idxs) + pf_fun = let fun = prob.f, setter = setp_oop(sys, inputs) function pff(du, input, u, p, t) p = setter(p, input) SciMLBase.ParamJacobianWrapper(fun, t, u)(du, p) @@ -127,10 +117,22 @@ function linearization_function(sys::AbstractSystem, inputs, end lin_fun = LinearizationFunction( - diff_idxs, alge_idxs, input_idxs, length(unknowns(sys)), + diff_idxs, alge_idxs, length(unknowns(sys)), prob, h, u0 === nothing ? nothing : similar(u0), uf_jac, h_jac, pf_jac, hp_jac, initializealg, initialization_kwargs) - return lin_fun, sys + return lin_fun +end + +function eq_idxs(sys::AbstractSystem) + eqs = equations(sys) + alg_start_idx = findfirst(!isdiffeq, eqs) + if alg_start_idx === nothing + alg_start_idx = length(eqs) + 1 + end + diff_idxs = 1:(alg_start_idx - 1) + alge_idxs = alg_start_idx:length(eqs) + + diff_idxs, alge_idxs end """ @@ -206,7 +208,7 @@ struct LinearizationFunction{ The indexes of parameters in the linearized system which represent input variables. """ - input_idxs::II + inputs::II """ The number of unknowns in the linearized system. """ @@ -281,6 +283,7 @@ function (linfun::LinearizationFunction)(u, p, t) end fun = linfun.prob.f + input_vals = [linfun.prob.ps[i] for i in linfun.inputs] if u !== nothing # Handle systems without unknowns linfun.num_states == length(u) || error("Number of unknown variables ($(linfun.num_states)) does not match the number of input unknowns ($(length(u)))") @@ -294,15 +297,15 @@ function (linfun::LinearizationFunction)(u, p, t) end fg_xz = linfun.uf_jac(u, DI.Constant(p), DI.Constant(t)) h_xz = linfun.h_jac(u, DI.Constant(p), DI.Constant(t)) - fg_u = linfun.pf_jac([p[idx] for idx in linfun.input_idxs], + fg_u = linfun.pf_jac(input_vals, DI.Constant(u), DI.Constant(p), DI.Constant(t)) else linfun.num_states == 0 || error("Number of unknown variables (0) does not match the number of input unknowns ($(length(u)))") fg_xz = zeros(0, 0) - h_xz = fg_u = zeros(0, length(linfun.input_idxs)) + h_xz = fg_u = zeros(0, length(linfun.inputs)) end - h_u = linfun.hp_jac([p[idx] for idx in linfun.input_idxs], + h_u = linfun.hp_jac(input_vals, DI.Constant(u), DI.Constant(p), DI.Constant(t)) (f_x = fg_xz[linfun.diff_idxs, linfun.diff_idxs], f_z = fg_xz[linfun.diff_idxs, linfun.alge_idxs], @@ -461,7 +464,7 @@ function CommonSolve.solve(prob::LinearizationProblem; allow_input_derivatives = end """ - (; A, B, C, D), simplified_sys = linearize_symbolic(sys::AbstractSystem, inputs, outputs; simplify = false, allow_input_derivatives = false, kwargs...) + (; A, B, C, D), simplified_sys = linearize_symbolic(sys::AbstractSystem, inputs, outputs; allow_input_derivatives = false, kwargs...) Similar to [`linearize`](@ref), but returns symbolic matrices `A,B,C,D` rather than numeric. While `linearize` uses ForwardDiff to perform the linearization, this function uses `Symbolics.jacobian`. @@ -479,12 +482,10 @@ y &= h(x, z, u) where `x` are differential unknown variables, `z` algebraic variables, `u` inputs and `y` outputs. """ function linearize_symbolic(sys::AbstractSystem, inputs, - outputs; simplify = false, allow_input_derivatives = false, + outputs; allow_input_derivatives = false, eval_expression = false, eval_module = @__MODULE__, kwargs...) - sys, diff_idxs, alge_idxs, input_idxs = io_preprocessing( - sys, inputs, outputs; simplify, - kwargs...) + diff_idxs, alge_idxs = eq_idxs(sys) sts = unknowns(sys) t = get_iv(sys) ps = parameters(sys; initial_parameters = true) diff --git a/src/systems/abstractsystem.jl b/src/systems/abstractsystem.jl index 1252367d2b..025199c92b 100644 --- a/src/systems/abstractsystem.jl +++ b/src/systems/abstractsystem.jl @@ -2484,21 +2484,6 @@ function eliminate_constants(sys::AbstractSystem) return sys end -function io_preprocessing(sys::AbstractSystem, inputs, - outputs; simplify = false, kwargs...) - sys, input_idxs = structural_simplify(sys, (inputs, outputs); simplify, kwargs...) - - eqs = equations(sys) - alg_start_idx = findfirst(!isdiffeq, eqs) - if alg_start_idx === nothing - alg_start_idx = length(eqs) + 1 - end - diff_idxs = 1:(alg_start_idx - 1) - alge_idxs = alg_start_idx:length(eqs) - - sys, diff_idxs, alge_idxs, input_idxs -end - @latexrecipe function f(sys::AbstractSystem) return latexify(equations(sys)) end From ea858ca09fec60408db606ecc415f7a79f817462 Mon Sep 17 00:00:00 2001 From: vyudu Date: Mon, 21 Apr 2025 16:28:55 -0400 Subject: [PATCH 012/122] use mtkbuild --- docs/src/basics/AbstractSystem.md | 2 +- docs/src/basics/Composition.md | 10 ++-- docs/src/basics/Debugging.md | 2 +- docs/src/basics/Events.md | 4 +- docs/src/basics/FAQ.md | 6 +- docs/src/basics/InputOutput.md | 4 -- docs/src/basics/Linearization.md | 2 +- docs/src/basics/MTKLanguage.md | 6 +- docs/src/basics/Precompilation.md | 2 +- docs/src/basics/Validation.md | 2 +- docs/src/comparison.md | 4 +- docs/src/examples/higher_order.md | 4 +- .../modelingtoolkitize_index_reduction.md | 4 +- docs/src/examples/spring_mass.md | 8 +-- docs/src/examples/tearing_parallelism.md | 6 +- docs/src/internals.md | 2 +- docs/src/systems/DiscreteSystem.md | 2 +- docs/src/systems/ImplicitDiscreteSystem.md | 2 +- docs/src/systems/JumpSystem.md | 2 +- docs/src/systems/NonlinearSystem.md | 2 +- docs/src/systems/ODESystem.md | 2 +- docs/src/systems/SDESystem.md | 2 +- docs/src/tutorials/acausal_components.md | 2 +- docs/src/tutorials/attractors.md | 2 +- .../tutorials/change_independent_variable.md | 8 +-- docs/src/tutorials/domain_connections.md | 8 +-- docs/src/tutorials/fmi.md | 6 +- docs/src/tutorials/initialization.md | 6 +- .../tutorials/programmatically_generating.md | 2 +- ext/MTKBifurcationKitExt.jl | 4 +- ext/MTKFMIExt.jl | 2 +- src/ModelingToolkit.jl | 4 +- src/inputoutput.jl | 2 +- src/linearization.jl | 26 +++++---- .../StructuralTransformations.jl | 2 +- src/structural_transformation/pantelides.jl | 2 +- .../symbolics_tearing.jl | 2 +- src/systems/abstractsystem.jl | 16 ++--- src/systems/diffeqs/abstractodesystem.jl | 34 +++++------ src/systems/diffeqs/basic_transformations.jl | 4 +- src/systems/diffeqs/odesystem.jl | 4 +- src/systems/diffeqs/sdesystem.jl | 10 ++-- .../discrete_system/discrete_system.jl | 4 +- .../implicit_discrete_system.jl | 4 +- src/systems/index_cache.jl | 4 +- src/systems/jumps/jumpsystem.jl | 8 +-- .../nonlinear/homotopy_continuation.jl | 4 +- src/systems/nonlinear/nonlinearsystem.jl | 24 ++++---- .../optimization/optimizationsystem.jl | 8 +-- src/systems/parameter_buffer.jl | 2 +- src/systems/systems.jl | 18 +++--- src/systems/systemstructure.jl | 12 ++-- src/utils.jl | 8 +-- test/accessor_functions.jl | 10 ++-- test/analysis_points.jl | 2 +- test/basic_transformations.jl | 20 +++---- test/clock.jl | 14 ++--- test/code_generation.jl | 2 +- test/components.jl | 24 ++++---- test/constants.jl | 6 +- test/dde.jl | 8 +-- test/debugging.jl | 4 +- test/discrete_system.jl | 4 +- test/domain_connectors.jl | 2 +- test/downstream/analysis_points.jl | 22 +++---- test/downstream/inversemodel.jl | 2 +- test/downstream/linearize.jl | 27 +++++---- test/downstream/test_disturbance_model.jl | 10 ++-- test/dq_units.jl | 8 +-- test/error_handling.jl | 4 +- test/extensions/ad.jl | 4 +- test/extensions/bifurcationkit.jl | 4 +- test/funcaffect.jl | 8 +-- test/guess_propagation.jl | 8 +-- test/hierarchical_initialization_eqs.jl | 2 +- test/if_lifting.jl | 6 +- test/initializationsystem.jl | 26 ++++----- test/input_output_handling.jl | 18 +++--- test/jumpsystem.jl | 2 +- test/modelingtoolkitize.jl | 2 +- test/mtkparameters.jl | 6 +- test/nonlinearsystem.jl | 20 +++---- test/odesystem.jl | 58 +++++++++---------- test/optimizationsystem.jl | 2 +- test/parameter_dependencies.jl | 4 +- test/reduction.jl | 28 ++++----- test/scc_nonlinear_problem.jl | 4 +- test/sciml_problem_inputs.jl | 2 +- test/sdesystem.jl | 14 ++--- test/split_parameters.jl | 4 +- test/state_selection.jl | 6 +- test/static_arrays.jl | 2 +- test/stream_connectors.jl | 12 ++-- .../index_reduction.jl | 4 +- test/structural_transformation/tearing.jl | 6 +- test/structural_transformation/utils.jl | 10 ++-- test/substitute_component.jl | 4 +- test/symbolic_events.jl | 48 +++++++-------- test/symbolic_indexing_interface.jl | 2 +- test/units.jl | 8 +-- test/variable_scope.jl | 2 +- 101 files changed, 413 insertions(+), 404 deletions(-) diff --git a/docs/src/basics/AbstractSystem.md b/docs/src/basics/AbstractSystem.md index d1707f822f..276fcb44f0 100644 --- a/docs/src/basics/AbstractSystem.md +++ b/docs/src/basics/AbstractSystem.md @@ -152,7 +152,7 @@ a lower level in the system. ## Namespacing By default, unsimplified systems will namespace variables accessed via `getproperty`. -Systems created via `@mtkbuild`, or ones passed through `structural_simplify` or +Systems created via `@mtkbuild`, or ones passed through `mtkbuild` or `complete` will not perform this namespacing. However, all of these processes modify the system in a variety of ways. To toggle namespacing without transforming any other property of the system, use `toggle_namespacing`. diff --git a/docs/src/basics/Composition.md b/docs/src/basics/Composition.md index d3de71d696..ba5fe48137 100644 --- a/docs/src/basics/Composition.md +++ b/docs/src/basics/Composition.md @@ -42,7 +42,7 @@ equations(connected) # Differential(t)(decay1₊x(t)) ~ decay1₊f(t) - (decay1₊a*(decay1₊x(t))) # Differential(t)(decay2₊x(t)) ~ decay2₊f(t) - (decay2₊a*(decay2₊x(t))) -simplified_sys = structural_simplify(connected) +simplified_sys = mtkbuild(connected) equations(simplified_sys) ``` @@ -84,7 +84,7 @@ example, let's say there is a variable `x` in `unknowns` and a variable `x` in `subsys`. We can declare that these two variables are the same by specifying their equality: `x ~ subsys.x` in the `eqs` for `sys`. This algebraic relationship can then be simplified by transformations -like `structural_simplify` which will be described later. +like `mtkbuild` which will be described later. ### Numerics with Composed Models @@ -169,7 +169,7 @@ parameters(level3) In many cases, the nicest way to build a model may leave a lot of unnecessary variables. Thus one may want to remove these equations -before numerically solving. The `structural_simplify` function removes +before numerically solving. The `mtkbuild` function removes these trivial equality relationships and trivial singularity equations, i.e. equations which result in `0~0` expressions, in over-specified systems. @@ -227,7 +227,7 @@ values. The user of this model can then solve this model simply by specifying the values at the highest level: ```@example compose -sireqn_simple = structural_simplify(sir) +sireqn_simple = mtkbuild(sir) equations(sireqn_simple) ``` @@ -251,7 +251,7 @@ sol[reqn.R] ## Tearing Problem Construction Some system types (specifically `NonlinearSystem`) can be further -reduced if `structural_simplify` has already been applied to them. This is done +reduced if `mtkbuild` has already been applied to them. This is done by using the alternative problem constructors (`BlockNonlinearProblem`). In these cases, the constructor uses the knowledge of the strongly connected components calculated during the process of simplification diff --git a/docs/src/basics/Debugging.md b/docs/src/basics/Debugging.md index 6e2d471461..45384ecf9c 100644 --- a/docs/src/basics/Debugging.md +++ b/docs/src/basics/Debugging.md @@ -13,7 +13,7 @@ using ModelingToolkit: t_nounits as t, D_nounits as D eqs = [D(u1) ~ -√(u1), D(u2) ~ -√(u2), D(u3) ~ -√(u3)] defaults = [u1 => 1.0, u2 => 2.0, u3 => 3.0] @named sys = ODESystem(eqs, t; defaults) -sys = structural_simplify(sys) +sys = mtkbuild(sys) ``` This problem causes the ODE solver to crash: diff --git a/docs/src/basics/Events.md b/docs/src/basics/Events.md index 3a76f478f1..125588540b 100644 --- a/docs/src/basics/Events.md +++ b/docs/src/basics/Events.md @@ -472,7 +472,7 @@ to the system. ```@example events @named sys = ODESystem( eqs, t, [temp], params; continuous_events = [furnace_disable, furnace_enable]) -ss = structural_simplify(sys) +ss = mtkbuild(sys) prob = ODEProblem(ss, [temp => 0.0, furnace_on => true], (0.0, 10.0)) sol = solve(prob, Tsit5()) plot(sol) @@ -585,7 +585,7 @@ We can now simulate the encoder. ```@example events @named sys = ODESystem( eqs, t, [theta, omega], params; continuous_events = [qAevt, qBevt]) -ss = structural_simplify(sys) +ss = mtkbuild(sys) prob = ODEProblem(ss, [theta => 0.0], (0.0, pi)) sol = solve(prob, Tsit5(); dtmax = 0.01) sol.ps[cnt] diff --git a/docs/src/basics/FAQ.md b/docs/src/basics/FAQ.md index 10671299c6..52152b761b 100644 --- a/docs/src/basics/FAQ.md +++ b/docs/src/basics/FAQ.md @@ -28,7 +28,7 @@ are similarly undocumented. Following is the list of behaviors that should be re - `setindex!(::MTKParameters, value, ::ParameterIndex)` can be used to set the value of a parameter with the given index. - `parameter_values(sys, sym)` will return a `ParameterIndex` object if `sys` has been - `complete`d (through `structural_simplify`, `complete` or `@mtkbuild`). + `complete`d (through `mtkbuild`, `complete` or `@mtkbuild`). - `copy(::MTKParameters)` is defined and duplicates the parameter object, including the memory used by the underlying buffers. @@ -194,7 +194,7 @@ p, replace, alias = SciMLStructures.canonicalize(Tunable(), prob.p) # ERROR: ArgumentError: SymbolicUtils.BasicSymbolic{Real}[xˍt(t)] are missing from the variable map. -This error can come up after running `structural_simplify` on a system that generates dummy derivatives (i.e. variables with `ˍt`). For example, here even though all the variables are defined with initial values, the `ODEProblem` generation will throw an error that defaults are missing from the variable map. +This error can come up after running `mtkbuild` on a system that generates dummy derivatives (i.e. variables with `ˍt`). For example, here even though all the variables are defined with initial values, the `ODEProblem` generation will throw an error that defaults are missing from the variable map. ```julia using ModelingToolkit @@ -206,7 +206,7 @@ eqs = [x1 + x2 + 1 ~ 0 x1 + D(x3) + x4 + 3 ~ 0 2 * D(D(x1)) + D(D(x2)) + D(D(x3)) + D(x4) + 4 ~ 0] @named sys = ODESystem(eqs, t) -sys = structural_simplify(sys) +sys = mtkbuild(sys) prob = ODEProblem(sys, [], (0, 1)) ``` diff --git a/docs/src/basics/InputOutput.md b/docs/src/basics/InputOutput.md index 4dc5a3d50f..d6108d7cc4 100644 --- a/docs/src/basics/InputOutput.md +++ b/docs/src/basics/InputOutput.md @@ -28,10 +28,6 @@ ModelingToolkit can generate the dynamics of a system, the function ``M\dot x = This function takes a vector of variables that are to be considered inputs, i.e., part of the vector ``u``. Alongside returning the function ``f``, [`ModelingToolkit.generate_control_function`](@ref) also returns the chosen state realization of the system after simplification. This vector specifies the order of the state variables ``x``, while the user-specified vector `u` specifies the order of the input variables ``u``. -!!! note "Un-simplified system" - - This function expects `sys` to be un-simplified, i.e., `structural_simplify` or `@mtkbuild` should not be called on the system before passing it into this function. `generate_control_function` calls a special version of `structural_simplify` internally. - ### Example: The following example implements a simple first-order system with an input `u` and state `x`. The function `f` is generated using `generate_control_function`, and the function `f` is then tested with random input and state values. diff --git a/docs/src/basics/Linearization.md b/docs/src/basics/Linearization.md index 78b6d5925d..a95f2b0104 100644 --- a/docs/src/basics/Linearization.md +++ b/docs/src/basics/Linearization.md @@ -75,7 +75,7 @@ eqs = [D(x) ~ v y.u ~ x] @named duffing = ODESystem(eqs, t, systems = [y, u], defaults = [u.u => 0]) -duffing = structural_simplify(duffing, inputs = [u.u], outputs = [y.u]) +duffing = mtkbuild(duffing, inputs = [u.u], outputs = [y.u]) # pass a constant value for `x`, since it is the variable we will change in operating points linfun = linearization_function(duffing, [u.u], [y.u]; op = Dict(x => NaN)); diff --git a/docs/src/basics/MTKLanguage.md b/docs/src/basics/MTKLanguage.md index e91f2bcb67..db5ce3b723 100644 --- a/docs/src/basics/MTKLanguage.md +++ b/docs/src/basics/MTKLanguage.md @@ -535,10 +535,10 @@ This is equivalent to: ```julia @named model = CustomModel() -sys = structural_simplify(model) +sys = mtkbuild(model) ``` -Pass keyword arguments to `structural_simplify` using the following syntax: +Pass keyword arguments to `mtkbuild` using the following syntax: ```julia @mtkbuild sys=CustomModel() fully_determined=false @@ -548,5 +548,5 @@ This is equivalent to: ```julia @named model = CustomModel() -sys = structural_simplify(model; fully_determined = false) +sys = mtkbuild(model; fully_determined = false) ``` diff --git a/docs/src/basics/Precompilation.md b/docs/src/basics/Precompilation.md index 97111f0d6b..88a425710a 100644 --- a/docs/src/basics/Precompilation.md +++ b/docs/src/basics/Precompilation.md @@ -22,7 +22,7 @@ using ModelingToolkit @variables x(ModelingToolkit.t_nounits) @named sys = ODESystem([ModelingToolkit.D_nounits(x) ~ -x + 1], ModelingToolkit.t_nounits) -prob = ODEProblem(structural_simplify(sys), [x => 30.0], (0, 100), [], +prob = ODEProblem(mtkbuild(sys), [x => 30.0], (0, 100), [], eval_expression = true, eval_module = @__MODULE__) end diff --git a/docs/src/basics/Validation.md b/docs/src/basics/Validation.md index 79c5d0d214..39a51ccf68 100644 --- a/docs/src/basics/Validation.md +++ b/docs/src/basics/Validation.md @@ -112,7 +112,7 @@ ps = @parameters s=-1 [unit = u"cm"] c=c [unit = u"cm"] eqs = [D(a) ~ dummycomplex(c, s);] sys = ODESystem( eqs, t, [sts...;], [ps...;], name = :sys, checks = ~ModelingToolkit.CheckUnits) -sys_simple = structural_simplify(sys) +sys_simple = mtkbuild(sys) ``` ## `DynamicQuantities` Literals diff --git a/docs/src/comparison.md b/docs/src/comparison.md index 52d5ab2f70..0bf88b72c3 100644 --- a/docs/src/comparison.md +++ b/docs/src/comparison.md @@ -12,7 +12,7 @@ - All current Modelica compiler implementations are fixed and not extendable by the users from the Modelica language itself. For example, the Dymola compiler [shares its symbolic processing pipeline](https://www.claytex.com/tech-blog/model-translation-and-symbolic-manipulation/), - which is roughly equivalent to the `dae_index_lowering` and `structural_simplify` + which is roughly equivalent to the `dae_index_lowering` and `mtkbuild` of ModelingToolkit.jl. ModelingToolkit.jl is an open and hackable transformation system which allows users to add new non-standard transformations and control the order of application. @@ -90,7 +90,7 @@ [Dymola symbolic processing pipeline](https://www.claytex.com/tech-blog/model-translation-and-symbolic-manipulation/) with some improvements. ModelingToolkit.jl has an open transformation pipeline that allows for users to extend and reorder transformation passes, where - `structural_simplify` is an adaptation of the Modia.jl-improved alias elimination + `mtkbuild` is an adaptation of the Modia.jl-improved alias elimination and tearing algorithms. - Both Modia and ModelingToolkit generate `DAEProblem` and `ODEProblem` forms for solving with [DifferentialEquations.jl](https://docs.sciml.ai/DiffEqDocs/stable/). diff --git a/docs/src/examples/higher_order.md b/docs/src/examples/higher_order.md index fac707525f..6bdffb2ace 100644 --- a/docs/src/examples/higher_order.md +++ b/docs/src/examples/higher_order.md @@ -4,7 +4,7 @@ ModelingToolkit has a system for transformations of mathematical systems. These transformations allow for symbolically changing the representation of the model to problems that are easier to numerically solve. One simple to demonstrate transformation, is -`structural_simplify`, which does a lot of tricks, one being the +`mtkbuild`, which does a lot of tricks, one being the transformation that turns an Nth order ODE into N coupled 1st order ODEs. @@ -43,7 +43,7 @@ and this syntax extends to `N`-th order. Also, we can use `*` or `∘` to compos `Differential`s, like `Differential(t) * Differential(x)`. Now let's transform this into the `ODESystem` of first order components. -We do this by calling `structural_simplify`: +We do this by calling `mtkbuild`: Now we can directly numerically solve the lowered system. Note that, following the original problem, the solution requires knowing the diff --git a/docs/src/examples/modelingtoolkitize_index_reduction.md b/docs/src/examples/modelingtoolkitize_index_reduction.md index b19ea46701..40204c3a5f 100644 --- a/docs/src/examples/modelingtoolkitize_index_reduction.md +++ b/docs/src/examples/modelingtoolkitize_index_reduction.md @@ -29,7 +29,7 @@ p = [9.8, 1] tspan = (0, 10.0) pendulum_prob = ODEProblem(pendulum_fun!, u0, tspan, p) traced_sys = modelingtoolkitize(pendulum_prob) -pendulum_sys = structural_simplify(dae_index_lowering(traced_sys)) +pendulum_sys = mtkbuild(dae_index_lowering(traced_sys)) prob = ODEProblem(pendulum_sys, [], tspan) sol = solve(prob, Rodas5P(), abstol = 1e-8, reltol = 1e-8) plot(sol, idxs = unknowns(traced_sys)) @@ -157,7 +157,7 @@ numerical solver. Let's try that out: ```@example indexred traced_sys = modelingtoolkitize(pendulum_prob) -pendulum_sys = structural_simplify(dae_index_lowering(traced_sys)) +pendulum_sys = mtkbuild(dae_index_lowering(traced_sys)) prob = ODEProblem(pendulum_sys, Pair[], tspan) sol = solve(prob, Rodas5P()) diff --git a/docs/src/examples/spring_mass.md b/docs/src/examples/spring_mass.md index 355e5c20b2..e9c6b4f908 100644 --- a/docs/src/examples/spring_mass.md +++ b/docs/src/examples/spring_mass.md @@ -45,7 +45,7 @@ eqs = [connect_spring(spring, mass.pos, center) @named _model = ODESystem(eqs, t, [spring.x; spring.dir; mass.pos], []) @named model = compose(_model, mass, spring) -sys = structural_simplify(model) +sys = mtkbuild(model) prob = ODEProblem(sys, [], (0.0, 3.0)) sol = solve(prob, Rosenbrock23()) @@ -153,10 +153,10 @@ parameters(model) ### Simplifying and solving this system -This system can be solved directly as a DAE using [one of the DAE solvers from DifferentialEquations.jl](https://docs.sciml.ai/DiffEqDocs/stable/solvers/dae_solve/). However, we can symbolically simplify the system first beforehand. Running `structural_simplify` eliminates unnecessary variables from the model to give the leanest numerical representation of the system. +This system can be solved directly as a DAE using [one of the DAE solvers from DifferentialEquations.jl](https://docs.sciml.ai/DiffEqDocs/stable/solvers/dae_solve/). However, we can symbolically simplify the system first beforehand. Running `mtkbuild` eliminates unnecessary variables from the model to give the leanest numerical representation of the system. ```@example component -sys = structural_simplify(model) +sys = mtkbuild(model) equations(sys) ``` @@ -177,7 +177,7 @@ sol = solve(prob, Rosenbrock23()) plot(sol) ``` -What if we want the timeseries of a different variable? That information is not lost! Instead, `structural_simplify` simply changes unknown variables into `observed` variables. +What if we want the timeseries of a different variable? That information is not lost! Instead, `mtkbuild` simply changes unknown variables into `observed` variables. ```@example component observed(sys) diff --git a/docs/src/examples/tearing_parallelism.md b/docs/src/examples/tearing_parallelism.md index 9540e610bd..688b47917f 100644 --- a/docs/src/examples/tearing_parallelism.md +++ b/docs/src/examples/tearing_parallelism.md @@ -1,7 +1,7 @@ # Exposing More Parallelism By Tearing Algebraic Equations in ODESystems Sometimes it can be very non-trivial to parallelize a system. In this tutorial, -we will demonstrate how to make use of `structural_simplify` to expose more +we will demonstrate how to make use of `mtkbuild` to expose more parallelism in the solution process and parallelize the resulting simulation. ## The Component Library @@ -122,7 +122,7 @@ Now let's say we want to expose a bit more parallelism via running tearing. How do we do that? ```@example tearing -sys = structural_simplify(big_rc) +sys = mtkbuild(big_rc) ``` Done, that's it. There's no more to it. @@ -175,5 +175,5 @@ so this is better than trying to do it by hand. After performing this, you can construct the `ODEProblem` and set `parallel_form` to use the exposed parallelism in multithreaded function -constructions, but this showcases why `structural_simplify` is so important +constructions, but this showcases why `mtkbuild` is so important to that process. diff --git a/docs/src/internals.md b/docs/src/internals.md index 00b29f1a64..2db7fd8bc6 100644 --- a/docs/src/internals.md +++ b/docs/src/internals.md @@ -18,7 +18,7 @@ and are then used to generate the `observed` equation found in the variable when necessary. In this sense, there is an equivalence between observables and the variable elimination system. -The procedure for variable elimination inside [`structural_simplify`](@ref) is +The procedure for variable elimination inside [`mtkbuild`](@ref) is 1. [`ModelingToolkit.initialize_system_structure`](@ref). 2. [`ModelingToolkit.alias_elimination`](@ref). This step moves equations into `observed(sys)`. diff --git a/docs/src/systems/DiscreteSystem.md b/docs/src/systems/DiscreteSystem.md index f8a71043ab..e787b8f004 100644 --- a/docs/src/systems/DiscreteSystem.md +++ b/docs/src/systems/DiscreteSystem.md @@ -17,7 +17,7 @@ DiscreteSystem ## Transformations ```@docs; canonical=false -structural_simplify +mtkbuild ``` ## Problem Constructors diff --git a/docs/src/systems/ImplicitDiscreteSystem.md b/docs/src/systems/ImplicitDiscreteSystem.md index d69f88f106..13910f3995 100644 --- a/docs/src/systems/ImplicitDiscreteSystem.md +++ b/docs/src/systems/ImplicitDiscreteSystem.md @@ -17,7 +17,7 @@ ImplicitDiscreteSystem ## Transformations ```@docs; canonical=false -structural_simplify +mtkbuild ``` ## Problem Constructors diff --git a/docs/src/systems/JumpSystem.md b/docs/src/systems/JumpSystem.md index 5bd0d50602..2db9246f70 100644 --- a/docs/src/systems/JumpSystem.md +++ b/docs/src/systems/JumpSystem.md @@ -17,7 +17,7 @@ JumpSystem ## Transformations ```@docs; canonical=false -structural_simplify +mtkbuild ``` ## Analyses diff --git a/docs/src/systems/NonlinearSystem.md b/docs/src/systems/NonlinearSystem.md index 06d587b1b9..2a470f0820 100644 --- a/docs/src/systems/NonlinearSystem.md +++ b/docs/src/systems/NonlinearSystem.md @@ -16,7 +16,7 @@ NonlinearSystem ## Transformations ```@docs; canonical=false -structural_simplify +mtkbuild alias_elimination tearing ``` diff --git a/docs/src/systems/ODESystem.md b/docs/src/systems/ODESystem.md index 24e2952fc5..ce6149edeb 100644 --- a/docs/src/systems/ODESystem.md +++ b/docs/src/systems/ODESystem.md @@ -27,7 +27,7 @@ ODESystem ## Transformations ```@docs -structural_simplify +mtkbuild ode_order_lowering dae_index_lowering change_independent_variable diff --git a/docs/src/systems/SDESystem.md b/docs/src/systems/SDESystem.md index 5789d2d9cb..2f51528a51 100644 --- a/docs/src/systems/SDESystem.md +++ b/docs/src/systems/SDESystem.md @@ -33,7 +33,7 @@ sde = SDESystem(ode, noiseeqs) ## Transformations ```@docs; canonical=false -structural_simplify +mtkbuild alias_elimination ``` diff --git a/docs/src/tutorials/acausal_components.md b/docs/src/tutorials/acausal_components.md index 751b678dae..4e0f14fc63 100644 --- a/docs/src/tutorials/acausal_components.md +++ b/docs/src/tutorials/acausal_components.md @@ -323,7 +323,7 @@ plot(sol) By default, this plots only the unknown variables that had to be solved for. However, what if we wanted to plot the timeseries of a different variable? Do not worry, that information was not thrown away! Instead, transformations -like `structural_simplify` simply change unknown variables into observables which are +like `mtkbuild` simply change unknown variables into observables which are defined by `observed` equations. ```@example acausal diff --git a/docs/src/tutorials/attractors.md b/docs/src/tutorials/attractors.md index 317384b01a..24649d307d 100644 --- a/docs/src/tutorials/attractors.md +++ b/docs/src/tutorials/attractors.md @@ -42,7 +42,7 @@ Because our dynamical system is super simple, we will directly make an `ODESyste ```@example Attractors @named modlorenz = ODESystem(eqs, t) -ssys = structural_simplify(modlorenz) +ssys = mtkbuild(modlorenz) # The timespan given to the problem is irrelevant for DynamicalSystems.jl prob = ODEProblem(ssys, [], (0.0, 1.0), []) ``` diff --git a/docs/src/tutorials/change_independent_variable.md b/docs/src/tutorials/change_independent_variable.md index d55639a669..18b6f75bb3 100644 --- a/docs/src/tutorials/change_independent_variable.md +++ b/docs/src/tutorials/change_independent_variable.md @@ -25,7 +25,7 @@ using ModelingToolkit: t_nounits as t, D_nounits as D eqs = [D(D(y)) ~ -g, D(x) ~ v] initialization_eqs = [D(x) ~ D(y)] # 45° initial angle M1 = ODESystem(eqs, t; initialization_eqs, name = :M) -M1s = structural_simplify(M1) +M1s = mtkbuild(M1) @assert length(equations(M1s)) == 3 # hide M1s # hide ``` @@ -44,7 +44,7 @@ This transformation is well-defined for any non-zero horizontal velocity $v$, so ```@example changeivar M2 = change_independent_variable(M1, x) -M2s = structural_simplify(M2; allow_symbolic = true) +M2s = mtkbuild(M2; allow_symbolic = true) # a sanity test on the 10 x/y variables that are accessible to the user # hide @assert allequal([x, M1s.x]) # hide @assert allequal([M2.x, M2s.x]) # hide @@ -97,7 +97,7 @@ eqs = [Ω ~ r.Ω + m.Ω + Λ.Ω, D(a) ~ √(Ω) * a^2] initialization_eqs = [Λ.Ω + r.Ω + m.Ω ~ 1] M1 = ODESystem(eqs, t, [Ω, a], []; initialization_eqs, name = :M) M1 = compose(M1, r, m, Λ) -M1s = structural_simplify(M1) +M1s = mtkbuild(M1) ``` Of course, we can solve this ODE as it is: @@ -137,7 +137,7 @@ M3 = change_independent_variable(M2, b, [Da(b) ~ exp(-b), a ~ exp(b)]) We can now solve and plot the ODE in terms of $b$: ```@example changeivar -M3s = structural_simplify(M3; allow_symbolic = true) +M3s = mtkbuild(M3; allow_symbolic = true) prob = ODEProblem(M3s, [M3s.r.Ω => 5e-5, M3s.m.Ω => 0.3], (0, -15), []) sol = solve(prob, Tsit5()) @assert Symbol(sol.retcode) == :Success # surrounding text assumes the solution was successful # hide diff --git a/docs/src/tutorials/domain_connections.md b/docs/src/tutorials/domain_connections.md index d6dc2d8781..ff124ce481 100644 --- a/docs/src/tutorials/domain_connections.md +++ b/docs/src/tutorials/domain_connections.md @@ -115,10 +115,10 @@ end nothing #hide ``` -To see how the domain works, we can examine the set parameter values for each of the ports `src.port` and `vol.port`. First we assemble the system using `structural_simplify()` and then check the default value of `vol.port.ρ`, whichs points to the setter value `fluid₊ρ`. Likewise, `src.port.ρ`, will also point to the setter value `fluid₊ρ`. Therefore, there is now only 1 defined density value `fluid₊ρ` which sets the density for the connected network. +To see how the domain works, we can examine the set parameter values for each of the ports `src.port` and `vol.port`. First we assemble the system using `mtkbuild()` and then check the default value of `vol.port.ρ`, whichs points to the setter value `fluid₊ρ`. Likewise, `src.port.ρ`, will also point to the setter value `fluid₊ρ`. Therefore, there is now only 1 defined density value `fluid₊ρ` which sets the density for the connected network. ```@repl domain -sys = structural_simplify(odesys) +sys = mtkbuild(odesys) ModelingToolkit.defaults(sys)[odesys.vol.port.ρ] ``` @@ -181,7 +181,7 @@ end nothing #hide ``` -After running `structural_simplify()` on `actsys2`, the defaults will show that `act.port_a.ρ` points to `fluid_a₊ρ` and `act.port_b.ρ` points to `fluid_b₊ρ`. This is a special case, in most cases a hydraulic system will have only 1 fluid, however this simple system has 2 separate domain networks. Therefore, we can connect a single fluid to both networks. This does not interfere with the mathematical equations of the system, since no unknown variables are connected. +After running `mtkbuild()` on `actsys2`, the defaults will show that `act.port_a.ρ` points to `fluid_a₊ρ` and `act.port_b.ρ` points to `fluid_b₊ρ`. This is a special case, in most cases a hydraulic system will have only 1 fluid, however this simple system has 2 separate domain networks. Therefore, we can connect a single fluid to both networks. This does not interfere with the mathematical equations of the system, since no unknown variables are connected. ```@example domain @component function ActuatorSystem1(; name) @@ -252,7 +252,7 @@ end nothing #hide ``` -When `structural_simplify()` is applied to this system it can be seen that the defaults are missing for `res.port_b` and `vol.port`. +When `mtkbuild()` is applied to this system it can be seen that the defaults are missing for `res.port_b` and `vol.port`. ```@repl domain ModelingToolkit.defaults(ressys)[ressys.res.port_a.ρ] diff --git a/docs/src/tutorials/fmi.md b/docs/src/tutorials/fmi.md index ef00477c78..9015f91987 100644 --- a/docs/src/tutorials/fmi.md +++ b/docs/src/tutorials/fmi.md @@ -76,7 +76,7 @@ initialization semantics. We can simulate this model like any other ModelingToolkit system. ```@repl fmi -sys = structural_simplify(model) +sys = mtkbuild(model) prob = ODEProblem(sys, [sys.mass__s => 0.5, sys.mass__v => 0.0], (0.0, 5.0)) sol = solve(prob, Tsit5()) ``` @@ -104,11 +104,11 @@ constant until the next time the callback triggers. The periodic interval must b `communication_step_size` keyword argument. A smaller step size typically leads to less error but is more computationally expensive. -This model alone does not have any differential variables, and calling `structural_simplify` will lead +This model alone does not have any differential variables, and calling `mtkbuild` will lead to an `ODESystem` with no unknowns. ```@example fmi -structural_simplify(inner) +mtkbuild(inner) ``` Simulating this model will cause the OrdinaryDiffEq integrator to immediately finish, and will not diff --git a/docs/src/tutorials/initialization.md b/docs/src/tutorials/initialization.md index ba733e0bfb..8712262cbc 100644 --- a/docs/src/tutorials/initialization.md +++ b/docs/src/tutorials/initialization.md @@ -380,7 +380,7 @@ with observables, those observables are too treated as initial equations. We can resulting simplified system via the command: ```@example init -isys = structural_simplify(isys; fully_determined = false) +isys = mtkbuild(isys; fully_determined = false) ``` Note `fully_determined=false` allows for the simplification to occur when the number of equations @@ -392,7 +392,7 @@ isys = ModelingToolkit.generate_initializesystem( ``` ```@example init -isys = structural_simplify(isys; fully_determined = false) +isys = mtkbuild(isys; fully_determined = false) ``` ```@example init @@ -504,7 +504,7 @@ eqs = [D(x) ~ α * x - β * x * y z ~ x + y] @named sys = ODESystem(eqs, t) -simpsys = structural_simplify(sys) +simpsys = mtkbuild(sys) tspan = (0.0, 10.0) ``` diff --git a/docs/src/tutorials/programmatically_generating.md b/docs/src/tutorials/programmatically_generating.md index 9fc1db1834..6abba4dd3d 100644 --- a/docs/src/tutorials/programmatically_generating.md +++ b/docs/src/tutorials/programmatically_generating.md @@ -47,7 +47,7 @@ eqs = [D(x) ~ (h - x) / τ] # create an array of equations # Perform the standard transformations and mark the model complete # Note: Complete models cannot be subsystems of other models! -fol = structural_simplify(model) +fol = mtkbuild(model) prob = ODEProblem(fol, [], (0.0, 10.0), []) using OrdinaryDiffEq sol = solve(prob) diff --git a/ext/MTKBifurcationKitExt.jl b/ext/MTKBifurcationKitExt.jl index 0b9f104d9b..e0e9cbf8fe 100644 --- a/ext/MTKBifurcationKitExt.jl +++ b/ext/MTKBifurcationKitExt.jl @@ -92,7 +92,7 @@ function BifurcationKit.BifurcationProblem(nsys::NonlinearSystem, jac = true, kwargs...) if !ModelingToolkit.iscomplete(nsys) - error("A completed `NonlinearSystem` is required. Call `complete` or `structural_simplify` on the system before creating a `BifurcationProblem`") + error("A completed `NonlinearSystem` is required. Call `complete` or `mtkbuild` on the system before creating a `BifurcationProblem`") end @set! nsys.index_cache = nothing # force usage of a parameter vector instead of `MTKParameters` # Creates F and J functions. @@ -146,7 +146,7 @@ end # When input is a ODESystem. function BifurcationKit.BifurcationProblem(osys::ODESystem, args...; kwargs...) if !ModelingToolkit.iscomplete(osys) - error("A completed `ODESystem` is required. Call `complete` or `structural_simplify` on the system before creating a `BifurcationProblem`") + error("A completed `ODESystem` is required. Call `complete` or `mtkbuild` on the system before creating a `BifurcationProblem`") end nsys = NonlinearSystem([0 ~ eq.rhs for eq in full_equations(osys)], unknowns(osys), diff --git a/ext/MTKFMIExt.jl b/ext/MTKFMIExt.jl index 5cfe9a82ef..4501bbcbaf 100644 --- a/ext/MTKFMIExt.jl +++ b/ext/MTKFMIExt.jl @@ -127,7 +127,7 @@ function MTK.FMIComponent(::Val{Ver}; fmu = nothing, tolerance = 1e-6, # no differential variables __mtk_internal_u = Float64[] elseif type == :ME - # to avoid running into `structural_simplify` warnings about array variables + # to avoid running into `mtkbuild` warnings about array variables # and some unfortunate circular dependency issues, ME FMUs use an array of # symbolics instead. This is also not worse off in performance # because the former approach would allocate anyway. diff --git a/src/ModelingToolkit.jl b/src/ModelingToolkit.jl index 198a84d48b..43872763b7 100644 --- a/src/ModelingToolkit.jl +++ b/src/ModelingToolkit.jl @@ -227,7 +227,7 @@ PrecompileTools.@compile_workload begin using ModelingToolkit @variables x(ModelingToolkit.t_nounits) @named sys = ODESystem([ModelingToolkit.D_nounits(x) ~ -x], ModelingToolkit.t_nounits) - prob = ODEProblem(structural_simplify(sys), [x => 30.0], (0, 100), [], jac = true) + prob = ODEProblem(mtkbuild(sys), [x => 30.0], (0, 100), [], jac = true) @mtkmodel __testmod__ begin @constants begin c = 1.0 @@ -300,7 +300,7 @@ export Term, Sym export SymScope, LocalScope, ParentScope, GlobalScope export independent_variable, equations, controls, observed, full_equations export initialization_equations, guesses, defaults, parameter_dependencies, hierarchy -export structural_simplify, expand_connections, linearize, linearization_function, +export mtkbuild, expand_connections, linearize, linearization_function, LinearizationProblem export solve diff --git a/src/inputoutput.jl b/src/inputoutput.jl index ebd3e25af6..5030b7d0eb 100644 --- a/src/inputoutput.jl +++ b/src/inputoutput.jl @@ -201,7 +201,7 @@ function generate_control_function(sys::AbstractODESystem, inputs = unbound_inpu # Remove this when the ControlFunction gets merged. if !iscomplete(sys) - error("A completed `ODESystem` is required. Call `complete` or `structural_simplify` on the system before creating the control function.") + error("A completed `ODESystem` is required. Call `complete` or `mtkbuild` on the system before creating the control function.") end isempty(inputs) && @warn("No unbound inputs were found in system.") if disturbance_inputs !== nothing diff --git a/src/linearization.jl b/src/linearization.jl index 2e958ecc73..3bfe7eaef5 100644 --- a/src/linearization.jl +++ b/src/linearization.jl @@ -1,5 +1,5 @@ """ - lin_fun, simplified_sys = linearization_function(sys::AbstractSystem, inputs, outputs; initialize = true, initialization_solver_alg = TrustRegion(), kwargs...) + lin_fun = linearization_function(sys::AbstractSystem, inputs, outputs; initialize = true, initialization_solver_alg = TrustRegion(), kwargs...) Return a function that linearizes the system `sys`. The function [`linearize`](@ref) provides a higher-level and easier to use interface. @@ -15,7 +15,7 @@ y &= h(x, z, u) where `x` are differential unknown variables, `z` algebraic variables, `u` inputs and `y` outputs. To obtain a linear statespace representation, see [`linearize`](@ref). The input argument `variables` is a vector defining the operating point, corresponding to `unknowns(simplified_sys)` and `p` is a vector corresponding to the parameters of `simplified_sys`. Note: all variables in `inputs` have been converted to parameters in `simplified_sys`. -The `simplified_sys` has undergone [`structural_simplify`](@ref) and had any occurring input or output variables replaced with the variables provided in arguments `inputs` and `outputs`. The unknowns of this system also indicate the order of the unknowns that holds for the linearized matrices. +The `simplified_sys` has undergone [`mtkbuild`](@ref) and had any occurring input or output variables replaced with the variables provided in arguments `inputs` and `outputs`. The unknowns of this system also indicate the order of the unknowns that holds for the linearized matrices. # Arguments: @@ -29,8 +29,8 @@ The `simplified_sys` has undergone [`structural_simplify`](@ref) and had any occ See also [`linearize`](@ref) which provides a higher-level interface. """ -function linearization_function(sys::AbstractSystem, inputs, - outputs; +function linearization_function(sys::AbstractSystem, inputs = unbound_inputs(sys), + outputs = unbound_outputs(sys); initialize = true, initializealg = nothing, initialization_abstol = 1e-5, @@ -45,6 +45,9 @@ function linearization_function(sys::AbstractSystem, inputs, guesses = Dict(), warn_empty_op = true, kwargs...) + if !iscomplete(sys) + error("A completed `ODESystem` is required. Call `complete` or `mtkbuild` on the system before creating the linearization.") + end op = Dict(op) if isempty(op) && warn_empty_op @warn "An empty operating point was passed to `linearization_function`. An operating point containing the variables that will be changed in `linearize` should be provided. Disable this warning by passing `warn_empty_op = false`." @@ -481,10 +484,13 @@ y &= h(x, z, u) ``` where `x` are differential unknown variables, `z` algebraic variables, `u` inputs and `y` outputs. """ -function linearize_symbolic(sys::AbstractSystem, inputs, - outputs; allow_input_derivatives = false, +function linearize_symbolic(sys::AbstractSystem, inputs = unbound_inputs(sys), + outputs = unbound_outputs(sys); allow_input_derivatives = false, eval_expression = false, eval_module = @__MODULE__, kwargs...) + if !iscomplete(sys) + error("A completed `ODESystem` is required. Call `complete` or `mtkbuild` on the system before creating the linearization.") + end diff_idxs, alge_idxs = eq_idxs(sys) sts = unknowns(sys) t = get_iv(sys) @@ -543,7 +549,7 @@ function linearize_symbolic(sys::AbstractSystem, inputs, end end - (; A, B, C, D, f_x, f_z, g_x, g_z, f_u, g_u, h_x, h_z, h_u), sys + (; A, B, C, D, f_x, f_z, g_x, g_z, f_u, g_u, h_x, h_z, h_u) end function markio!(state, orig_inputs, inputs, outputs; check = true) @@ -710,17 +716,17 @@ function linearize(sys, lin_fun::LinearizationFunction; t = 0.0, return solve(prob; allow_input_derivatives) end -function linearize(sys, inputs, outputs; op = Dict(), t = 0.0, +function linearize(sys, inputs = unbound_inputs(sys), outputs = unbound_outputs(sys); op = Dict(), t = 0.0, allow_input_derivatives = false, zero_dummy_der = false, kwargs...) - lin_fun, ssys = linearization_function(sys, + lin_fun = linearization_function(sys, inputs, outputs; zero_dummy_der, op, kwargs...) - linearize(ssys, lin_fun; op, t, allow_input_derivatives), ssys + linearize(sys, lin_fun; op, t, allow_input_derivatives) end """ diff --git a/src/structural_transformation/StructuralTransformations.jl b/src/structural_transformation/StructuralTransformations.jl index 4adc817ef8..0b8b2eddcb 100644 --- a/src/structural_transformation/StructuralTransformations.jl +++ b/src/structural_transformation/StructuralTransformations.jl @@ -35,7 +35,7 @@ import ModelingToolkit: var_derivative!, var_derivative_graph! using Graphs using ModelingToolkit: algeqs, EquationsView, SystemStructure, TransformationState, TearingState, - structural_simplify!, + structural_simplification!, isdiffvar, isdervar, isalgvar, isdiffeq, algeqs, is_only_discrete, dervars_range, diffvars_range, algvars_range, DiffGraph, complete!, diff --git a/src/structural_transformation/pantelides.jl b/src/structural_transformation/pantelides.jl index 585c4a29d1..335c7d52cd 100644 --- a/src/structural_transformation/pantelides.jl +++ b/src/structural_transformation/pantelides.jl @@ -210,7 +210,7 @@ end dae_index_lowering(sys::ODESystem; kwargs...) -> ODESystem Perform the Pantelides algorithm to transform a higher index DAE to an index 1 -DAE. `kwargs` are forwarded to [`pantelides!`](@ref). End users are encouraged to call [`structural_simplify`](@ref) +DAE. `kwargs` are forwarded to [`pantelides!`](@ref). End users are encouraged to call [`mtkbuild`](@ref) instead, which calls this function internally. """ function dae_index_lowering(sys::ODESystem; kwargs...) diff --git a/src/structural_transformation/symbolics_tearing.jl b/src/structural_transformation/symbolics_tearing.jl index 548c7da519..7b554f97d1 100644 --- a/src/structural_transformation/symbolics_tearing.jl +++ b/src/structural_transformation/symbolics_tearing.jl @@ -1018,7 +1018,7 @@ end tearing(sys; simplify=false) Tear the nonlinear equations in system. When `simplify=true`, we simplify the -new residual equations after tearing. End users are encouraged to call [`structural_simplify`](@ref) +new residual equations after tearing. End users are encouraged to call [`mtkbuild`](@ref) instead, which calls this function internally. """ function tearing(sys::AbstractSystem, state = TearingState(sys); mm = nothing, diff --git a/src/systems/abstractsystem.jl b/src/systems/abstractsystem.jl index 025199c92b..8559151747 100644 --- a/src/systems/abstractsystem.jl +++ b/src/systems/abstractsystem.jl @@ -157,7 +157,7 @@ may be subsetted using `dvs` and `ps`. All `kwargs` are passed to the internal [`build_function`](@ref) call. The returned function can be called as `f(u, p, t)` or `f(du, u, p, t)` for time-dependent systems and `f(u, p)` or `f(du, u, p)` for time-independent systems. If `split=true` (the default) was passed to [`complete`](@ref), -[`structural_simplify`](@ref) or [`@mtkbuild`](@ref), `p` is expected to be an `MTKParameters` +[`mtkbuild`](@ref) or [`@mtkbuild`](@ref), `p` is expected to be an `MTKParameters` object. """ function generate_custom_function(sys::AbstractSystem, exprs, dvs = unknowns(sys), @@ -165,7 +165,7 @@ function generate_custom_function(sys::AbstractSystem, exprs, dvs = unknowns(sys expression = Val{true}, eval_expression = false, eval_module = @__MODULE__, cachesyms::Tuple = (), kwargs...) if !iscomplete(sys) - error("A completed system is required. Call `complete` or `structural_simplify` on the system.") + error("A completed system is required. Call `complete` or `mtkbuild` on the system.") end p = (reorder_parameters(sys, unwrap.(ps))..., cachesyms...) isscalar = !(exprs isa AbstractArray) @@ -771,7 +771,7 @@ function complete( newparams = OrderedSet() iv = has_iv(sys) ? get_iv(sys) : nothing collect_scoped_vars!(newunknowns, newparams, sys, iv; depth = -1) - # don't update unknowns to not disturb `structural_simplify` order + # don't update unknowns to not disturb `mtkbuild` order # `GlobalScope`d unknowns will be picked up and added there @set! sys.ps = unique!(vcat(get_ps(sys), collect(newparams))) @@ -1196,7 +1196,7 @@ end Denotes that a variable belongs to the root system in the hierarchy, regardless of which equations of subsystems in the hierarchy it is involved in. Variables with this scope are never namespaced and only added to the unknowns/parameters of a system when calling -`complete` or `structural_simplify`. +`complete` or `mtkbuild`. """ struct GlobalScope <: SymScope end @@ -2416,7 +2416,7 @@ macro mtkbuild(exprs...) else kwargs = (Expr(:parameters, kwargs...),) end - call_expr = Expr(:call, structural_simplify, kwargs..., name) + call_expr = Expr(:call, mtkbuild, kwargs..., name) esc(quote $named_expr $name = $call_expr @@ -2455,7 +2455,7 @@ function debug_system( functions = Set(functions) # more efficient "in" lookup end if has_systems(sys) && !isempty(get_systems(sys)) - error("debug_system(sys) only works on systems with no sub-systems! Consider flattening it with flatten(sys) or structural_simplify(sys) first.") + error("debug_system(sys) only works on systems with no sub-systems! Consider flattening it with flatten(sys) or mtkbuild(sys) first.") end if has_eqs(sys) eqs = debug_sub.(equations(sys), Ref(functions); kw...) @@ -2552,10 +2552,10 @@ end function check_array_equations_unknowns(eqs, dvs) if any(eq -> eq isa Equation && Symbolics.isarraysymbolic(eq.lhs), eqs) - throw(ArgumentError("The system has array equations. Call `structural_simplify` to handle such equations or scalarize them manually.")) + throw(ArgumentError("The system has array equations. Call `mtkbuild` to handle such equations or scalarize them manually.")) end if any(x -> Symbolics.isarraysymbolic(x), dvs) - throw(ArgumentError("The system has array unknowns. Call `structural_simplify` to handle this or scalarize them manually.")) + throw(ArgumentError("The system has array unknowns. Call `mtkbuild` to handle this or scalarize them manually.")) end end diff --git a/src/systems/diffeqs/abstractodesystem.jl b/src/systems/diffeqs/abstractodesystem.jl index d9e8b05eeb..96dbae0762 100644 --- a/src/systems/diffeqs/abstractodesystem.jl +++ b/src/systems/diffeqs/abstractodesystem.jl @@ -364,7 +364,7 @@ function DiffEqBase.ODEFunction{iip, specialize}(sys::AbstractODESystem, cse = true, kwargs...) where {iip, specialize} if !iscomplete(sys) - error("A completed system is required. Call `complete` or `structural_simplify` on the system before creating an `ODEFunction`") + error("A completed system is required. Call `complete` or `mtkbuild` on the system before creating an `ODEFunction`") end f_gen = generate_function(sys, dvs, ps; expression = Val{true}, expression_module = eval_module, checkbounds = checkbounds, cse, @@ -469,7 +469,7 @@ function DiffEqBase.DAEFunction{iip}(sys::AbstractODESystem, dvs = unknowns(sys) cse = true, kwargs...) where {iip} if !iscomplete(sys) - error("A completed system is required. Call `complete` or `structural_simplify` on the system before creating a `DAEFunction`") + error("A completed system is required. Call `complete` or `mtkbuild` on the system before creating a `DAEFunction`") end f_gen = generate_function(sys, dvs, ps; implicit_dae = true, expression = Val{true}, cse, @@ -529,7 +529,7 @@ function DiffEqBase.DDEFunction{iip}(sys::AbstractODESystem, dvs = unknowns(sys) cse = true, kwargs...) where {iip} if !iscomplete(sys) - error("A completed system is required. Call `complete` or `structural_simplify` on the system before creating an `DDEFunction`") + error("A completed system is required. Call `complete` or `mtkbuild` on the system before creating an `DDEFunction`") end f_gen = generate_function(sys, dvs, ps; isdde = true, expression = Val{true}, @@ -554,7 +554,7 @@ function DiffEqBase.SDDEFunction{iip}(sys::AbstractODESystem, dvs = unknowns(sys cse = true, kwargs...) where {iip} if !iscomplete(sys) - error("A completed system is required. Call `complete` or `structural_simplify` on the system before creating an `SDDEFunction`") + error("A completed system is required. Call `complete` or `mtkbuild` on the system before creating an `SDDEFunction`") end f_gen = generate_function(sys, dvs, ps; isdde = true, expression = Val{true}, @@ -598,7 +598,7 @@ function ODEFunctionExpr{iip, specialize}(sys::AbstractODESystem, dvs = unknowns observedfun_exp = nothing, kwargs...) where {iip, specialize} if !iscomplete(sys) - error("A completed system is required. Call `complete` or `structural_simplify` on the system before creating an `ODEFunctionExpr`") + error("A completed system is required. Call `complete` or `mtkbuild` on the system before creating an `ODEFunctionExpr`") end f_oop, f_iip = generate_function(sys, dvs, ps; expression = Val{true}, kwargs...) @@ -688,7 +688,7 @@ function DAEFunctionExpr{iip}(sys::AbstractODESystem, dvs = unknowns(sys), sparse = false, simplify = false, kwargs...) where {iip} if !iscomplete(sys) - error("A completed system is required. Call `complete` or `structural_simplify` on the system before creating an `DAEFunctionExpr`") + error("A completed system is required. Call `complete` or `mtkbuild` on the system before creating an `DAEFunctionExpr`") end f_oop, f_iip = generate_function(sys, dvs, ps; expression = Val{true}, implicit_dae = true, kwargs...) @@ -784,7 +784,7 @@ function DiffEqBase.ODEProblem{iip, specialize}(sys::AbstractODESystem, u0map = eval_module = @__MODULE__, kwargs...) where {iip, specialize} if !iscomplete(sys) - error("A completed system is required. Call `complete` or `structural_simplify` on the system before creating an `ODEProblem`") + error("A completed system is required. Call `complete` or `mtkbuild` on the system before creating an `ODEProblem`") end if !isnothing(get_constraintsystem(sys)) @@ -900,7 +900,7 @@ function SciMLBase.BVProblem{iip, specialize}(sys::AbstractODESystem, u0map = [] cse = true, kwargs...) where {iip, specialize} if !iscomplete(sys) - error("A completed system is required. Call `complete` or `structural_simplify` on the system before creating an `BVProblem`") + error("A completed system is required. Call `complete` or `mtkbuild` on the system before creating an `BVProblem`") end !isnothing(callback) && error("BVP solvers do not support callbacks.") @@ -1014,7 +1014,7 @@ function DiffEqBase.DAEProblem{iip}(sys::AbstractODESystem, du0map, u0map, tspan warn_initialize_determined = true, check_length = true, eval_expression = false, eval_module = @__MODULE__, kwargs...) where {iip} if !iscomplete(sys) - error("A completed system is required. Call `complete` or `structural_simplify` on the system before creating a `DAEProblem`.") + error("A completed system is required. Call `complete` or `mtkbuild` on the system before creating a `DAEProblem`.") end if !isempty(get_costs(sys)) && !allow_cost @@ -1066,7 +1066,7 @@ function DiffEqBase.DDEProblem{iip}(sys::AbstractODESystem, u0map = [], cse = true, kwargs...) where {iip} if !iscomplete(sys) - error("A completed system is required. Call `complete` or `structural_simplify` on the system before creating a `DDEProblem`") + error("A completed system is required. Call `complete` or `mtkbuild` on the system before creating a `DDEProblem`") end f, u0, p = process_SciMLProblem(DDEFunction{iip}, sys, u0map, parammap; t = tspan !== nothing ? tspan[1] : tspan, @@ -1106,7 +1106,7 @@ function DiffEqBase.SDDEProblem{iip}(sys::AbstractODESystem, u0map = [], cse = true, kwargs...) where {iip} if !iscomplete(sys) - error("A completed system is required. Call `complete` or `structural_simplify` on the system before creating a `SDDEProblem`") + error("A completed system is required. Call `complete` or `mtkbuild` on the system before creating a `SDDEProblem`") end f, u0, p = process_SciMLProblem(SDDEFunction{iip}, sys, u0map, parammap; t = tspan !== nothing ? tspan[1] : tspan, @@ -1167,7 +1167,7 @@ function ODEProblemExpr{iip}(sys::AbstractODESystem, u0map, tspan, parammap = DiffEqBase.NullParameters(); check_length = true, kwargs...) where {iip} if !iscomplete(sys) - error("A completed system is required. Call `complete` or `structural_simplify` on the system before creating a `ODEProblemExpr`") + error("A completed system is required. Call `complete` or `mtkbuild` on the system before creating a `ODEProblemExpr`") end f, u0, p = process_SciMLProblem( ODEFunctionExpr{iip}, sys, u0map, parammap; check_length, @@ -1214,7 +1214,7 @@ function DAEProblemExpr{iip}(sys::AbstractODESystem, du0map, u0map, tspan, parammap = DiffEqBase.NullParameters(); check_length = true, kwargs...) where {iip} if !iscomplete(sys) - error("A completed system is required. Call `complete` or `structural_simplify` on the system before creating a `DAEProblemExpr`") + error("A completed system is required. Call `complete` or `mtkbuild` on the system before creating a `DAEProblemExpr`") end f, du0, u0, p = process_SciMLProblem(DAEFunctionExpr{iip}, sys, u0map, parammap; t = tspan !== nothing ? tspan[1] : tspan, @@ -1266,7 +1266,7 @@ function DiffEqBase.SteadyStateProblem{iip}(sys::AbstractODESystem, u0map, parammap = SciMLBase.NullParameters(); check_length = true, kwargs...) where {iip} if !iscomplete(sys) - error("A completed system is required. Call `complete` or `structural_simplify` on the system before creating a `SteadyStateProblem`") + error("A completed system is required. Call `complete` or `mtkbuild` on the system before creating a `SteadyStateProblem`") end f, u0, p = process_SciMLProblem(ODEFunction{iip}, sys, u0map, parammap; steady_state = true, @@ -1298,7 +1298,7 @@ function SteadyStateProblemExpr{iip}(sys::AbstractODESystem, u0map, check_length = true, kwargs...) where {iip} if !iscomplete(sys) - error("A completed system is required. Call `complete` or `structural_simplify` on the system before creating a `SteadyStateProblemExpr`") + error("A completed system is required. Call `complete` or `mtkbuild` on the system before creating a `SteadyStateProblemExpr`") end f, u0, p = process_SciMLProblem(ODEFunctionExpr{iip}, sys, u0map, parammap; steady_state = true, @@ -1449,7 +1449,7 @@ function InitializationProblem{iip, specialize}(sys::AbstractSystem, algebraic_only = false, kwargs...) where {iip, specialize} if !iscomplete(sys) - error("A completed system is required. Call `complete` or `structural_simplify` on the system before creating an `ODEProblem`") + error("A completed system is required. Call `complete` or `mtkbuild` on the system before creating an `ODEProblem`") end if isempty(u0map) && get_initializesystem(sys) !== nothing isys = get_initializesystem(sys; initialization_eqs, check_units) @@ -1474,7 +1474,7 @@ function InitializationProblem{iip, specialize}(sys::AbstractSystem, end if simplify_system - isys = structural_simplify(isys; fully_determined) + isys = mtkbuild(isys; fully_determined) end ts = get_tearing_state(isys) diff --git a/src/systems/diffeqs/basic_transformations.jl b/src/systems/diffeqs/basic_transformations.jl index d41e948d64..e448b23dfb 100644 --- a/src/systems/diffeqs/basic_transformations.jl +++ b/src/systems/diffeqs/basic_transformations.jl @@ -74,7 +74,7 @@ Any extra equations `eqs` involving the new and old independent variables will b # Usage before structural simplification The variable change must take place before structural simplification. -In following calls to `structural_simplify`, consider passing `allow_symbolic = true` to avoid undesired constraint equations between between dummy variables. +In following calls to `mtkbuild`, consider passing `allow_symbolic = true` to avoid undesired constraint equations between between dummy variables. # Usage with non-autonomous systems @@ -99,7 +99,7 @@ julia> @named M = ODESystem([D(D(y)) ~ -9.81, D(D(x)) ~ 0.0], t); julia> M = change_independent_variable(M, x); -julia> M = structural_simplify(M; allow_symbolic = true); +julia> M = mtkbuild(M; allow_symbolic = true); julia> unknowns(M) 3-element Vector{SymbolicUtils.BasicSymbolic{Real}}: diff --git a/src/systems/diffeqs/odesystem.jl b/src/systems/diffeqs/odesystem.jl index 50655d0074..cfabaf2f2e 100644 --- a/src/systems/diffeqs/odesystem.jl +++ b/src/systems/diffeqs/odesystem.jl @@ -575,7 +575,7 @@ function build_explicit_observed_function(sys, ts; if inputs === nothing inputs = () else - ps = setdiff(ps, inputs) # Inputs have been converted to parameters by io_preprocessing, remove those from the parameter list + ps = setdiff(ps, inputs) # Inputs have been converted to parameters, remove those from the parameter list inputs = (inputs,) end if disturbance_inputs !== nothing @@ -658,7 +658,7 @@ end # We have a stand-alone function to convert a `NonlinearSystem` or `ODESystem` # to an `ODESystem` to connect systems, and we later can reply on -# `structural_simplify` to convert `ODESystem`s to `NonlinearSystem`s. +# `mtkbuild` to convert `ODESystem`s to `NonlinearSystem`s. """ $(TYPEDSIGNATURES) diff --git a/src/systems/diffeqs/sdesystem.jl b/src/systems/diffeqs/sdesystem.jl index c5299c28be..4ce6b9605c 100644 --- a/src/systems/diffeqs/sdesystem.jl +++ b/src/systems/diffeqs/sdesystem.jl @@ -600,7 +600,7 @@ function DiffEqBase.SDEFunction{iip, specialize}(sys::SDESystem, dvs = unknowns( checkbounds = false, initialization_data = nothing, cse = true, kwargs...) where {iip, specialize} if !iscomplete(sys) - error("A completed `SDESystem` is required. Call `complete` or `structural_simplify` on the system before creating an `SDEFunction`") + error("A completed `SDESystem` is required. Call `complete` or `mtkbuild` on the system before creating an `SDEFunction`") end dvs = scalarize.(dvs) @@ -720,7 +720,7 @@ function SDEFunctionExpr{iip}(sys::SDESystem, dvs = unknowns(sys), sparse = false, linenumbers = false, kwargs...) where {iip} if !iscomplete(sys) - error("A completed `SDESystem` is required. Call `complete` or `structural_simplify` on the system before creating an `SDEFunctionExpr`") + error("A completed `SDESystem` is required. Call `complete` or `mtkbuild` on the system before creating an `SDEFunctionExpr`") end idx = iip ? 2 : 1 f = generate_function(sys, dvs, ps; expression = Val{true}, kwargs...)[idx] @@ -788,7 +788,7 @@ function DiffEqBase.SDEProblem{iip, specialize}( sparsenoise = nothing, check_length = true, callback = nothing, kwargs...) where {iip, specialize} if !iscomplete(sys) - error("A completed `SDESystem` is required. Call `complete` or `structural_simplify` on the system before creating an `SDEProblem`") + error("A completed `SDESystem` is required. Call `complete` or `mtkbuild` on the system before creating an `SDEProblem`") end f, u0, p = process_SciMLProblem( @@ -824,7 +824,7 @@ end function DiffEqBase.SDEProblem(sys::ODESystem, args...; kwargs...) if any(ModelingToolkit.isbrownian, unknowns(sys)) - error("SDESystem constructed by defining Brownian variables with @brownian must be simplified by calling `structural_simplify` before a SDEProblem can be constructed.") + error("SDESystem constructed by defining Brownian variables with @brownian must be simplified by calling `mtkbuild` before a SDEProblem can be constructed.") else error("Cannot construct SDEProblem from a normal ODESystem.") end @@ -886,7 +886,7 @@ function SDEProblemExpr{iip}(sys::SDESystem, u0map, tspan, sparsenoise = nothing, check_length = true, kwargs...) where {iip} if !iscomplete(sys) - error("A completed `SDESystem` is required. Call `complete` or `structural_simplify` on the system before creating an `SDEProblemExpr`") + error("A completed `SDESystem` is required. Call `complete` or `mtkbuild` on the system before creating an `SDEProblemExpr`") end f, u0, p = process_SciMLProblem( SDEFunctionExpr{iip}, sys, u0map, parammap; check_length, diff --git a/src/systems/discrete_system/discrete_system.jl b/src/systems/discrete_system/discrete_system.jl index 075aa27e4d..fa2e9f89ec 100644 --- a/src/systems/discrete_system/discrete_system.jl +++ b/src/systems/discrete_system/discrete_system.jl @@ -311,7 +311,7 @@ function SciMLBase.DiscreteProblem( kwargs... ) if !iscomplete(sys) - error("A completed `DiscreteSystem` is required. Call `complete` or `structural_simplify` on the system before creating a `DiscreteProblem`") + error("A completed `DiscreteSystem` is required. Call `complete` or `mtkbuild` on the system before creating a `DiscreteProblem`") end dvs = unknowns(sys) ps = parameters(sys) @@ -363,7 +363,7 @@ function SciMLBase.DiscreteFunction{iip, specialize}( analytic = nothing, cse = true, kwargs...) where {iip, specialize} if !iscomplete(sys) - error("A completed `DiscreteSystem` is required. Call `complete` or `structural_simplify` on the system before creating a `DiscreteProblem`") + error("A completed `DiscreteSystem` is required. Call `complete` or `mtkbuild` on the system before creating a `DiscreteProblem`") end f_gen = generate_function(sys, dvs, ps; expression = Val{true}, expression_module = eval_module, cse, kwargs...) diff --git a/src/systems/discrete_system/implicit_discrete_system.jl b/src/systems/discrete_system/implicit_discrete_system.jl index 3956c089d4..6440d5cce9 100644 --- a/src/systems/discrete_system/implicit_discrete_system.jl +++ b/src/systems/discrete_system/implicit_discrete_system.jl @@ -330,7 +330,7 @@ function SciMLBase.ImplicitDiscreteProblem( kwargs... ) if !iscomplete(sys) - error("A completed `ImplicitDiscreteSystem` is required. Call `complete` or `structural_simplify` on the system before creating a `ImplicitDiscreteProblem`.") + error("A completed `ImplicitDiscreteSystem` is required. Call `complete` or `mtkbuild` on the system before creating a `ImplicitDiscreteProblem`.") end dvs = unknowns(sys) ps = parameters(sys) @@ -372,7 +372,7 @@ function SciMLBase.ImplicitDiscreteFunction{iip, specialize}( analytic = nothing, cse = true, kwargs...) where {iip, specialize} if !iscomplete(sys) - error("A completed `ImplicitDiscreteSystem` is required. Call `complete` or `structural_simplify` on the system before creating a `ImplicitDiscreteProblem`") + error("A completed `ImplicitDiscreteSystem` is required. Call `complete` or `mtkbuild` on the system before creating a `ImplicitDiscreteProblem`") end f_gen = generate_function(sys, dvs, ps; expression = Val{true}, expression_module = eval_module, cse, kwargs...) diff --git a/src/systems/index_cache.jl b/src/systems/index_cache.jl index d0b687c212..8d051f4b2c 100644 --- a/src/systems/index_cache.jl +++ b/src/systems/index_cache.jl @@ -653,10 +653,10 @@ See also: [`MTKParameters`](@ref), [`tunable_parameters`](@ref), [`reorder_dimen function reorder_dimension_by_tunables!( dest::AbstractArray, sys::AbstractSystem, arr::AbstractArray, syms; dim = 1) if !iscomplete(sys) - throw(ArgumentError("A completed system is required. Call `complete` or `structural_simplify` on the system.")) + throw(ArgumentError("A completed system is required. Call `complete` or `mtkbuild` on the system.")) end if !has_index_cache(sys) || (ic = get_index_cache(sys)) === nothing - throw(ArgumentError("The system does not have an index cache. Call `complete` or `structural_simplify` on the system with `split = true`.")) + throw(ArgumentError("The system does not have an index cache. Call `complete` or `mtkbuild` on the system with `split = true`.")) end if size(dest) != size(arr) throw(ArgumentError("Source and destination arrays must have the same size. Got source array with size $(size(arr)) and destination with size $(size(dest)).")) diff --git a/src/systems/jumps/jumpsystem.jl b/src/systems/jumps/jumpsystem.jl index 06f5e1b623..8a5997d235 100644 --- a/src/systems/jumps/jumpsystem.jl +++ b/src/systems/jumps/jumpsystem.jl @@ -401,7 +401,7 @@ function DiffEqBase.DiscreteProblem(sys::JumpSystem, u0map, tspan::Union{Tuple, cse = true, kwargs...) if !iscomplete(sys) - error("A completed `JumpSystem` is required. Call `complete` or `structural_simplify` on the system before creating a `DiscreteProblem`") + error("A completed `JumpSystem` is required. Call `complete` or `mtkbuild` on the system before creating a `DiscreteProblem`") end if has_equations(sys) || (!isempty(continuous_events(sys))) @@ -446,7 +446,7 @@ function DiscreteProblemExpr{iip}(sys::JumpSystem, u0map, tspan::Union{Tuple, No parammap = DiffEqBase.NullParameters(); kwargs...) where {iip} if !iscomplete(sys) - error("A completed `JumpSystem` is required. Call `complete` or `structural_simplify` on the system before creating a `DiscreteProblemExpr`") + error("A completed `JumpSystem` is required. Call `complete` or `mtkbuild` on the system before creating a `DiscreteProblemExpr`") end _, u0, p = process_SciMLProblem(EmptySciMLFunction, sys, u0map, parammap; @@ -492,7 +492,7 @@ function DiffEqBase.ODEProblem(sys::JumpSystem, u0map, tspan::Union{Tuple, Nothi eval_module = @__MODULE__, cse = true, kwargs...) if !iscomplete(sys) - error("A completed `JumpSystem` is required. Call `complete` or `structural_simplify` on the system before creating a `DiscreteProblem`") + error("A completed `JumpSystem` is required. Call `complete` or `mtkbuild` on the system before creating a `DiscreteProblem`") end # forward everything to be an ODESystem but the jumps and discrete events @@ -535,7 +535,7 @@ function JumpProcesses.JumpProblem(js::JumpSystem, prob, aggregator = JumpProcesses.NullAggregator(); callback = nothing, eval_expression = false, eval_module = @__MODULE__, kwargs...) if !iscomplete(js) - error("A completed `JumpSystem` is required. Call `complete` or `structural_simplify` on the system before creating a `JumpProblem`") + error("A completed `JumpSystem` is required. Call `complete` or `mtkbuild` on the system before creating a `JumpProblem`") end unknowntoid = Dict(value(unknown) => i for (i, unknown) in enumerate(unknowns(js))) eqs = equations(js) diff --git a/src/systems/nonlinear/homotopy_continuation.jl b/src/systems/nonlinear/homotopy_continuation.jl index 9a77779103..796fce0c2d 100644 --- a/src/systems/nonlinear/homotopy_continuation.jl +++ b/src/systems/nonlinear/homotopy_continuation.jl @@ -492,7 +492,7 @@ function SciMLBase.HomotopyNonlinearFunction{iip, specialize}( p = nothing, fraction_cancel_fn = SymbolicUtils.simplify_fractions, cse = true, kwargs...) where {iip, specialize} if !iscomplete(sys) - error("A completed `NonlinearSystem` is required. Call `complete` or `structural_simplify` on the system before creating a `HomotopyContinuationFunction`") + error("A completed `NonlinearSystem` is required. Call `complete` or `mtkbuild` on the system before creating a `HomotopyContinuationFunction`") end transformation = PolynomialTransformation(sys) if transformation isa NotPolynomialError @@ -553,7 +553,7 @@ function HomotopyContinuationProblem{iip, spec}( sys::NonlinearSystem, u0map, pmap = SciMLBase.NullParameters(); kwargs...) where {iip, spec} if !iscomplete(sys) - error("A completed `NonlinearSystem` is required. Call `complete` or `structural_simplify` on the system before creating a `HomotopyContinuationProblem`") + error("A completed `NonlinearSystem` is required. Call `complete` or `mtkbuild` on the system before creating a `HomotopyContinuationProblem`") end f, u0, p = process_SciMLProblem( HomotopyNonlinearFunction{iip, spec}, sys, u0map, pmap; kwargs...) diff --git a/src/systems/nonlinear/nonlinearsystem.jl b/src/systems/nonlinear/nonlinearsystem.jl index 7146fb6b5e..a5e0779813 100644 --- a/src/systems/nonlinear/nonlinearsystem.jl +++ b/src/systems/nonlinear/nonlinearsystem.jl @@ -383,7 +383,7 @@ function SciMLBase.NonlinearFunction{iip}(sys::NonlinearSystem, dvs = unknowns(s initialization_data = nothing, cse = true, kwargs...) where {iip} if !iscomplete(sys) - error("A completed `NonlinearSystem` is required. Call `complete` or `structural_simplify` on the system before creating a `NonlinearFunction`") + error("A completed `NonlinearSystem` is required. Call `complete` or `mtkbuild` on the system before creating a `NonlinearFunction`") end f_gen = generate_function(sys, dvs, ps; expression = Val{true}, cse, kwargs...) f_oop, f_iip = eval_or_rgf.(f_gen; eval_expression, eval_module) @@ -430,7 +430,7 @@ function SciMLBase.IntervalNonlinearFunction( p = nothing, eval_expression = false, eval_module = @__MODULE__, initialization_data = nothing, kwargs...) if !iscomplete(sys) - error("A completed `NonlinearSystem` is required. Call `complete` or `structural_simplify` on the system before creating a `IntervalNonlinearFunction`") + error("A completed `NonlinearSystem` is required. Call `complete` or `mtkbuild` on the system before creating a `IntervalNonlinearFunction`") end if !isone(length(dvs)) || !isone(length(equations(sys))) error("`IntervalNonlinearFunction` only supports systems with a single equation and a single unknown.") @@ -472,7 +472,7 @@ function NonlinearFunctionExpr{iip}(sys::NonlinearSystem, dvs = unknowns(sys), sparse = false, simplify = false, kwargs...) where {iip} if !iscomplete(sys) - error("A completed `NonlinearSystem` is required. Call `complete` or `structural_simplify` on the system before creating a `NonlinearFunctionExpr`") + error("A completed `NonlinearSystem` is required. Call `complete` or `mtkbuild` on the system before creating a `NonlinearFunctionExpr`") end f_oop, f_iip = generate_function(sys, dvs, ps; expression = Val{true}, kwargs...) f = :($(GeneratedFunctionWrapper{(2, 2, is_split(sys))})($f_oop, $f_iip)) @@ -521,7 +521,7 @@ function IntervalNonlinearFunctionExpr( sys::NonlinearSystem, dvs = unknowns(sys), ps = parameters(sys), u0 = nothing; p = nothing, linenumbers = false, kwargs...) if !iscomplete(sys) - error("A completed `NonlinearSystem` is required. Call `complete` or `structural_simplify` on the system before creating a `IntervalNonlinearFunctionExpr`") + error("A completed `NonlinearSystem` is required. Call `complete` or `mtkbuild` on the system before creating a `IntervalNonlinearFunctionExpr`") end if !isone(length(dvs)) || !isone(length(equations(sys))) error("`IntervalNonlinearFunctionExpr` only supports systems with a single equation and a single unknown.") @@ -558,7 +558,7 @@ function DiffEqBase.NonlinearProblem{iip}(sys::NonlinearSystem, u0map, parammap = DiffEqBase.NullParameters(); check_length = true, kwargs...) where {iip} if !iscomplete(sys) - error("A completed `NonlinearSystem` is required. Call `complete` or `structural_simplify` on the system before creating a `NonlinearProblem`") + error("A completed `NonlinearSystem` is required. Call `complete` or `mtkbuild` on the system before creating a `NonlinearProblem`") end f, u0, p = process_SciMLProblem(NonlinearFunction{iip}, sys, u0map, parammap; check_length, kwargs...) @@ -592,7 +592,7 @@ function DiffEqBase.NonlinearLeastSquaresProblem{iip}(sys::NonlinearSystem, u0ma parammap = DiffEqBase.NullParameters(); check_length = false, kwargs...) where {iip} if !iscomplete(sys) - error("A completed `NonlinearSystem` is required. Call `complete` or `structural_simplify` on the system before creating a `NonlinearLeastSquaresProblem`") + error("A completed `NonlinearSystem` is required. Call `complete` or `mtkbuild` on the system before creating a `NonlinearLeastSquaresProblem`") end f, u0, p = process_SciMLProblem(NonlinearFunction{iip}, sys, u0map, parammap; check_length, kwargs...) @@ -676,11 +676,11 @@ function SciMLBase.SCCNonlinearProblem{iip}(sys::NonlinearSystem, u0map, parammap = SciMLBase.NullParameters(); eval_expression = false, eval_module = @__MODULE__, cse = true, kwargs...) where {iip} if !iscomplete(sys) || get_tearing_state(sys) === nothing - error("A simplified `NonlinearSystem` is required. Call `structural_simplify` on the system before creating an `SCCNonlinearProblem`.") + error("A simplified `NonlinearSystem` is required. Call `mtkbuild` on the system before creating an `SCCNonlinearProblem`.") end if !is_split(sys) - error("The system has been simplified with `split = false`. `SCCNonlinearProblem` is not compatible with this system. Pass `split = true` to `structural_simplify` to use `SCCNonlinearProblem`.") + error("The system has been simplified with `split = false`. `SCCNonlinearProblem` is not compatible with this system. Pass `split = true` to `mtkbuild` to use `SCCNonlinearProblem`.") end ts = get_tearing_state(sys) @@ -857,7 +857,7 @@ symbolically calculating numerical enhancements. function DiffEqBase.IntervalNonlinearProblem(sys::NonlinearSystem, uspan::NTuple{2}, parammap = SciMLBase.NullParameters(); kwargs...) if !iscomplete(sys) - error("A completed `NonlinearSystem` is required. Call `complete` or `structural_simplify` on the system before creating a `IntervalNonlinearProblem`") + error("A completed `NonlinearSystem` is required. Call `complete` or `mtkbuild` on the system before creating a `IntervalNonlinearProblem`") end if !isone(length(unknowns(sys))) || !isone(length(equations(sys))) error("`IntervalNonlinearProblem` only supports with a single equation and a single unknown.") @@ -893,7 +893,7 @@ function NonlinearProblemExpr{iip}(sys::NonlinearSystem, u0map, check_length = true, kwargs...) where {iip} if !iscomplete(sys) - error("A completed `NonlinearSystem` is required. Call `complete` or `structural_simplify` on the system before creating a `NonlinearProblemExpr`") + error("A completed `NonlinearSystem` is required. Call `complete` or `mtkbuild` on the system before creating a `NonlinearProblemExpr`") end f, u0, p = process_SciMLProblem(NonlinearFunctionExpr{iip}, sys, u0map, parammap; check_length, kwargs...) @@ -933,7 +933,7 @@ function NonlinearLeastSquaresProblemExpr{iip}(sys::NonlinearSystem, u0map, check_length = false, kwargs...) where {iip} if !iscomplete(sys) - error("A completed `NonlinearSystem` is required. Call `complete` or `structural_simplify` on the system before creating a `NonlinearProblemExpr`") + error("A completed `NonlinearSystem` is required. Call `complete` or `mtkbuild` on the system before creating a `NonlinearProblemExpr`") end f, u0, p = process_SciMLProblem(NonlinearFunctionExpr{iip}, sys, u0map, parammap; check_length, kwargs...) @@ -958,7 +958,7 @@ numerical enhancements. function IntervalNonlinearProblemExpr(sys::NonlinearSystem, uspan::NTuple{2}, parammap = SciMLBase.NullParameters(); kwargs...) if !iscomplete(sys) - error("A completed `NonlinearSystem` is required. Call `complete` or `structural_simplify` on the system before creating a `IntervalNonlinearProblemExpr`") + error("A completed `NonlinearSystem` is required. Call `complete` or `mtkbuild` on the system before creating a `IntervalNonlinearProblemExpr`") end if !isone(length(unknowns(sys))) || !isone(length(equations(sys))) error("`IntervalNonlinearProblemExpr` only supports with a single equation and a single unknown.") diff --git a/src/systems/optimization/optimizationsystem.jl b/src/systems/optimization/optimizationsystem.jl index bfe15b62d7..b7cc541ed7 100644 --- a/src/systems/optimization/optimizationsystem.jl +++ b/src/systems/optimization/optimizationsystem.jl @@ -303,7 +303,7 @@ function DiffEqBase.OptimizationProblem{iip}(sys::OptimizationSystem, u0map, checks = true, cse = true, kwargs...) where {iip} if !iscomplete(sys) - error("A completed `OptimizationSystem` is required. Call `complete` or `structural_simplify` on the system before creating a `OptimizationProblem`") + error("A completed `OptimizationSystem` is required. Call `complete` or `mtkbuild` on the system before creating a `OptimizationProblem`") end if haskey(kwargs, :lcons) || haskey(kwargs, :ucons) Base.depwarn( @@ -544,7 +544,7 @@ function OptimizationProblemExpr{iip}(sys::OptimizationSystem, u0map, eval_expression = false, eval_module = @__MODULE__, kwargs...) where {iip} if !iscomplete(sys) - error("A completed `OptimizationSystem` is required. Call `complete` or `structural_simplify` on the system before creating a `OptimizationProblemExpr`") + error("A completed `OptimizationSystem` is required. Call `complete` or `mtkbuild` on the system before creating a `OptimizationProblemExpr`") end if haskey(kwargs, :lcons) || haskey(kwargs, :ucons) Base.depwarn( @@ -726,7 +726,7 @@ function OptimizationProblemExpr{iip}(sys::OptimizationSystem, u0map, end end -function structural_simplify(sys::OptimizationSystem; split = true, kwargs...) +function mtkbuild(sys::OptimizationSystem; split = true, kwargs...) sys = flatten(sys) cons = constraints(sys) econs = Equation[] @@ -739,7 +739,7 @@ function structural_simplify(sys::OptimizationSystem; split = true, kwargs...) end end nlsys = NonlinearSystem(econs, unknowns(sys), parameters(sys); name = :___tmp_nlsystem) - snlsys = structural_simplify(nlsys; fully_determined = false, kwargs...) + snlsys = mtkbuild(nlsys; fully_determined = false, kwargs...) obs = observed(snlsys) subs = Dict(eq.lhs => eq.rhs for eq in observed(snlsys)) seqs = equations(snlsys) diff --git a/src/systems/parameter_buffer.jl b/src/systems/parameter_buffer.jl index 6142c95776..fa2bce9289 100644 --- a/src/systems/parameter_buffer.jl +++ b/src/systems/parameter_buffer.jl @@ -23,7 +23,7 @@ dependent systems. It is only required if the symbolic expressions also use the variable of the system. This requires that `complete` has been called on the system (usually via -`structural_simplify` or `@mtkbuild`) and the keyword `split = true` was passed (which is +`mtkbuild` or `@mtkbuild`) and the keyword `split = true` was passed (which is the default behavior). """ function MTKParameters( diff --git a/src/systems/systems.jl b/src/systems/systems.jl index e27916d994..ac96ad9c2a 100644 --- a/src/systems/systems.jl +++ b/src/systems/systems.jl @@ -33,7 +33,7 @@ function mtkbuild( disturbance_inputs = nothing, kwargs...) isscheduled(sys) && throw(RepeatedStructuralSimplificationError()) - newsys′ = __structural_simplify(sys; simplify, + newsys′ = __structural_simplification(sys; simplify, allow_symbolic, allow_parameter, conservative, fully_determined, inputs, outputs, disturbance_inputs, kwargs...) @@ -65,15 +65,15 @@ function mtkbuild( end end -function __structural_simplify(sys::JumpSystem, args...; kwargs...) +function __structural_simplification(sys::JumpSystem, args...; kwargs...) return sys end -function __structural_simplify(sys::SDESystem, args...; kwargs...) - return __structural_simplify(ODESystem(sys), args...; kwargs...) +function __structural_simplification(sys::SDESystem, args...; kwargs...) + return __structural_simplification(ODESystem(sys), args...; kwargs...) end -function __structural_simplify(sys::AbstractSystem; simplify = false, +function __structural_simplification(sys::AbstractSystem; simplify = false, inputs = nothing, outputs = nothing, disturbance_inputs = nothing, kwargs...) @@ -94,7 +94,7 @@ function __structural_simplify(sys::AbstractSystem; simplify = false, end end if isempty(brown_vars) - return structural_simplify!(state; simplify, inputs, outputs, disturbance_inputs, kwargs...) + return structural_simplification!(state; simplify, inputs, outputs, disturbance_inputs, kwargs...) else Is = Int[] Js = Int[] @@ -127,7 +127,7 @@ function __structural_simplify(sys::AbstractSystem; simplify = false, if !iszero(new_idxs[i]) && invview(var_to_diff)[i] === nothing] # TODO: IO is not handled. - ode_sys = structural_simplify(sys; simplify, inputs, outputs, disturbance_inputs, kwargs...) + ode_sys = structural_simplification(sys; simplify, inputs, outputs, disturbance_inputs, kwargs...) eqs = equations(ode_sys) sorted_g_rows = zeros(Num, length(eqs), size(g, 2)) for (i, eq) in enumerate(eqs) @@ -170,7 +170,7 @@ end """ $(TYPEDSIGNATURES) -Given a system that has been simplified via `structural_simplify`, return a `Dict` mapping +Given a system that has been simplified via `mtkbuild`, return a `Dict` mapping variables of the system to equations that are used to solve for them. This includes observed variables. @@ -186,7 +186,7 @@ function map_variables_to_equations(sys::AbstractSystem; rename_dummy_derivative end ts = get_tearing_state(sys) if ts === nothing - throw(ArgumentError("`map_variables_to_equations` requires a simplified system. Call `structural_simplify` on the system before calling this function.")) + throw(ArgumentError("`map_variables_to_equations` requires a simplified system. Call `mtkbuild` on the system before calling this function.")) end dummy_sub = Dict() diff --git a/src/systems/systemstructure.jl b/src/systems/systemstructure.jl index aa544c8db1..03ced79c73 100644 --- a/src/systems/systemstructure.jl +++ b/src/systems/systemstructure.jl @@ -24,7 +24,7 @@ function quick_cancel_expr(expr) kws...))(expr) end -export SystemStructure, TransformationState, TearingState, structural_simplify! +export SystemStructure, TransformationState, TearingState, structural_simplification! export isdiffvar, isdervar, isalgvar, isdiffeq, algeqs, is_only_discrete export dervars_range, diffvars_range, algvars_range export DiffGraph, complete! @@ -657,7 +657,7 @@ function Base.show(io::IO, mime::MIME"text/plain", ms::MatchedSystemStructure) printstyled(io, " SelectedState") end -function structural_simplify!(state::TearingState; simplify = false, +function structural_simplification!(state::TearingState; simplify = false, check_consistency = true, fully_determined = true, warn_initialize_determined = true, inputs = nothing, outputs = nothing, disturbance_inputs = nothing, @@ -670,7 +670,7 @@ function structural_simplify!(state::TearingState; simplify = false, Dict(default_toterm.(state.fullvars) .=> ci.var_domain)) tss, clocked_inputs, continuous_id, id_to_clock = ModelingToolkit.split_system(ci) cont_inputs = [inputs; clocked_inputs[continuous_id]] - sys = _structural_simplify!(tss[continuous_id]; simplify, + sys = _structural_simplification!(tss[continuous_id]; simplify, check_consistency, fully_determined, cont_inputs, outputs, disturbance_inputs, kwargs...) @@ -689,7 +689,7 @@ function structural_simplify!(state::TearingState; simplify = false, continue end disc_inputs = [inputs; clocked_inputs[i]] - ss, = _structural_simplify!(state; simplify, check_consistency, + ss, = _structural_simplification!(state; simplify, check_consistency, disc_inputs, outputs, disturbance_inputs, fully_determined, kwargs...) append!(appended_parameters, inputs[i], unknowns(ss)) @@ -707,14 +707,14 @@ function structural_simplify!(state::TearingState; simplify = false, for sym in get_ps(sys)] @set! sys.ps = ps else - sys = _structural_simplify!(state; simplify, check_consistency, + sys = _structural_simplification!(state; simplify, check_consistency, inputs, outputs, disturbance_inputs, fully_determined, kwargs...) end return sys end -function _structural_simplify!(state::TearingState; simplify = false, +function _structural_simplification!(state::TearingState; simplify = false, check_consistency = true, fully_determined = true, warn_initialize_determined = false, dummy_derivative = true, inputs = nothing, outputs = nothing, diff --git a/src/utils.jl b/src/utils.jl index 2b3cbedab0..d1645783eb 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -159,7 +159,7 @@ function check_lhs(eq::Equation, op, dvs::Set) v = unwrap(eq.lhs) _iszero(v) && return (operation(v) isa op && only(arguments(v)) in dvs) && return - error("$v is not a valid LHS. Please run structural_simplify before simulation.") + error("$v is not a valid LHS. Please run mtkbuild before simulation.") end check_lhs(eqs, op, dvs::Set) = for eq in eqs @@ -323,7 +323,7 @@ Throw error when difference/derivative operation occurs in the R.H.S. optext = "derivative" end msg = "The $optext variable must be isolated to the left-hand " * - "side of the equation like `$opvar ~ ...`. You may want to use `structural_simplify` or the DAE form.\nGot $eq." + "side of the equation like `$opvar ~ ...`. You may want to use `mtkbuild` or the DAE form.\nGot $eq." throw(InvalidSystemException(msg)) end @@ -359,10 +359,10 @@ function check_operator_variables(eqs, op::T) where {T} is_tmp_fine = iszero(nd) end is_tmp_fine || - error("The LHS cannot contain nondifferentiated variables. Please run `structural_simplify` or use the DAE form.\nGot $eq") + error("The LHS cannot contain nondifferentiated variables. Please run `mtkbuild` or use the DAE form.\nGot $eq") for v in tmp v in ops && - error("The LHS operator must be unique. Please run `structural_simplify` or use the DAE form. $v appears in LHS more than once.") + error("The LHS operator must be unique. Please run `mtkbuild` or use the DAE form. $v appears in LHS more than once.") push!(ops, v) end empty!(tmp) diff --git a/test/accessor_functions.jl b/test/accessor_functions.jl index 7ce477155b..3f5a22f34c 100644 --- a/test/accessor_functions.jl +++ b/test/accessor_functions.jl @@ -68,10 +68,10 @@ let 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) + sys_bot_ss = mtkbuild(sys_bot) + sys_mid2_ss = mtkbuild(sys_mid2) + sys_mid1_ss = mtkbuild(sys_mid1) + sys_top_ss = mtkbuild(sys_top) # Checks `parameters1. @test all_sets_equal(parameters.([sys_bot, sys_bot_comp, sys_bot_ss])..., [d, p_bot]) @@ -98,7 +98,7 @@ let @test all(sym_issubset(parameters_toplevel(sys), get_ps(sys)) for sys in [sys_bot, sys_mid2, sys_mid1, sys_top]) - # Checks `unknowns`. O(t) is eliminated by `structural_simplify` and + # Checks `unknowns`. O(t) is eliminated by `mtkbuild` 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]) diff --git a/test/analysis_points.jl b/test/analysis_points.jl index e36afc02f7..e52ac9e1a7 100644 --- a/test/analysis_points.jl +++ b/test/analysis_points.jl @@ -67,7 +67,7 @@ sys = ODESystem(eqs, t, systems = [P, C], name = :hej) nonamespace_sys = toggle_namespacing(nested_sys, false) @testset "simplifies and solves" begin - ssys = structural_simplify(sys) + ssys = mtkbuild(sys) prob = ODEProblem(ssys, [P.x => 1], (0, 10)) sol = solve(prob, Rodas5()) @test norm(sol.u[1]) >= 1 diff --git a/test/basic_transformations.jl b/test/basic_transformations.jl index 183feca6d2..f677e10066 100644 --- a/test/basic_transformations.jl +++ b/test/basic_transformations.jl @@ -50,8 +50,8 @@ end M1 = ODESystem(eqs, t; initialization_eqs, name = :M) M2 = change_independent_variable(M1, s) - M1 = structural_simplify(M1; allow_symbolic = true) - M2 = structural_simplify(M2; allow_symbolic = true) + M1 = mtkbuild(M1; allow_symbolic = true) + M2 = mtkbuild(M2; allow_symbolic = true) prob1 = ODEProblem(M1, [M1.s => 1.0], (1.0, 4.0), []) prob2 = ODEProblem(M2, [], (1.0, 2.0), []) sol1 = solve(prob1, Tsit5(); reltol = 1e-10, abstol = 1e-10) @@ -100,9 +100,9 @@ end extraeqs = [Differential(M2.a)(b) ~ exp(-b), M2.a ~ exp(b)] M3 = change_independent_variable(M2, b, extraeqs) - M1 = structural_simplify(M1) - M2 = structural_simplify(M2; allow_symbolic = true) - M3 = structural_simplify(M3; allow_symbolic = true) + M1 = mtkbuild(M1) + M2 = mtkbuild(M2; allow_symbolic = true) + M3 = mtkbuild(M3; allow_symbolic = true) @test length(unknowns(M3)) == length(unknowns(M2)) == length(unknowns(M1)) - 1 end @@ -120,7 +120,7 @@ end @parameters g=9.81 v # gravitational acceleration and constant horizontal velocity Mt = ODESystem([D(D(y)) ~ -g, D(x) ~ v], t; name = :M) # gives (x, y) as function of t, ... Mx = change_independent_variable(Mt, x; add_old_diff = true) # ... but we want y as a function of x - Mx = structural_simplify(Mx; allow_symbolic = true) + Mx = mtkbuild(Mx; allow_symbolic = true) Dx = Differential(Mx.x) u0 = [Mx.y => 0.0, Dx(Mx.y) => 1.0, Mx.t => 0.0] p = [v => 10.0] @@ -134,7 +134,7 @@ end @parameters g = 9.81 # gravitational acceleration Mt = ODESystem([D(D(y)) ~ -g, D(D(x)) ~ 0], t; name = :M) # gives (x, y) as function of t, ... Mx = change_independent_variable(Mt, x; add_old_diff = true) # ... but we want y as a function of x - Mx = structural_simplify(Mx; allow_symbolic = true) + Mx = mtkbuild(Mx; allow_symbolic = true) Dx = Differential(Mx.x) u0 = [Mx.y => 0.0, Dx(Mx.y) => 1.0, Mx.t => 0.0, Mx.xˍt => 10.0] prob = ODEProblem(Mx, u0, (0.0, 20.0), []) # 1 = dy/dx = (dy/dt)/(dx/dt) means equal initial horizontal and vertical velocities @@ -152,7 +152,7 @@ end ] M1 = ODESystem(eqs, t; name = :M) M2 = change_independent_variable(M1, x; add_old_diff = true) - @test_nowarn structural_simplify(M2) + @test_nowarn mtkbuild(M2) # Compare to pen-and-paper result @variables x xˍt(x) xˍt(x) y(x) t(x) @@ -198,7 +198,7 @@ end ]) _f = LinearInterpolation([1.0, 1.0], [-100.0, +100.0]) # constant value 1 - M2s = structural_simplify(M2; allow_symbolic = true) + M2s = mtkbuild(M2; allow_symbolic = true) prob = ODEProblem(M2s, [M2s.y => 0.0], (1.0, 4.0), [fc => _f, f => _f]) sol = solve(prob, Tsit5(); abstol = 1e-5) @test isapprox(sol(4.0, idxs = M2.y), 12.0; atol = 1e-5) # Anal solution is D(y) ~ 12 => y(t) ~ 12*t + C => y(x) ~ 12*√(x) + C. With y(x=1)=0 => 12*(√(x)-1), so y(x=4) ~ 12 @@ -207,7 +207,7 @@ end @testset "Change independent variable (errors)" begin @variables x(t) y z(y) w(t) v(t) M = ODESystem([D(x) ~ 1, v ~ x], t; name = :M) - Ms = structural_simplify(M) + Ms = mtkbuild(M) @test_throws "structurally simplified" change_independent_variable(Ms, y) @test_throws "not a function of" change_independent_variable(M, y) @test_throws "not a function of" change_independent_variable(M, z) diff --git a/test/clock.jl b/test/clock.jl index c6051a52a8..296afa899d 100644 --- a/test/clock.jl +++ b/test/clock.jl @@ -65,10 +65,10 @@ By inference: ci, varmap = infer_clocks(sys) eqmap = ci.eq_domain tss, inputs, continuous_id = ModelingToolkit.split_system(deepcopy(ci)) -sss, = ModelingToolkit._structural_simplify!( +sss, = ModelingToolkit._mtkbuild!( deepcopy(tss[continuous_id]), (inputs[continuous_id], ())) @test equations(sss) == [D(x) ~ u - x] -sss, = ModelingToolkit._structural_simplify!(deepcopy(tss[1]), (inputs[1], ())) +sss, = ModelingToolkit._mtkbuild!(deepcopy(tss[1]), (inputs[1], ())) @test isempty(equations(sss)) d = Clock(dt) k = ShiftIndex(d) @@ -115,7 +115,7 @@ eqs = [yd ~ Sample(dt)(y) D(x) ~ -x + u y ~ x] @named sys = ODESystem(eqs, t) -@test_throws ModelingToolkit.HybridSystemNotSupportedException ss=structural_simplify(sys); +@test_throws ModelingToolkit.HybridSystemNotSupportedException ss=mtkbuild(sys); @test_skip begin Tf = 1.0 @@ -128,7 +128,7 @@ eqs = [yd ~ Sample(dt)(y) [kp => 1.0; ud(k - 1) => 2.1; ud(k - 2) => 2.0]) # recreate problem to empty saved values sol = solve(prob, Tsit5(), kwargshandle = KeywordArgSilent) - ss_nosplit = structural_simplify(sys; split = false) + ss_nosplit = mtkbuild(sys; split = false) prob_nosplit = ODEProblem(ss_nosplit, [x => 0.1], (0.0, Tf), [kp => 1.0; ud(k - 1) => 2.1; ud(k - 2) => 2.0]) int = init(prob_nosplit, Tsit5(); kwargshandle = KeywordArgSilent) @@ -294,8 +294,8 @@ eqs = [yd ~ Sample(dt)(y) @test varmap[y] == ContinuousClock() @test varmap[u] == ContinuousClock() - ss = structural_simplify(cl) - ss_nosplit = structural_simplify(cl; split = false) + ss = mtkbuild(cl) + ss_nosplit = mtkbuild(cl; split = false) if VERSION >= v"1.7" prob = ODEProblem(ss, [x => 0.0], (0.0, 1.0), [kp => 1.0]) @@ -426,7 +426,7 @@ eqs = [yd ~ Sample(dt)(y) @test varmap[_model.feedback.output.u] == d @test varmap[_model.feedback.input2.u] == d - ssys = structural_simplify(model) + ssys = mtkbuild(model) Tf = 0.2 timevec = 0:(d.dt):Tf diff --git a/test/code_generation.jl b/test/code_generation.jl index cf3d660b81..83d324cce3 100644 --- a/test/code_generation.jl +++ b/test/code_generation.jl @@ -70,7 +70,7 @@ end @parameters p[1:2] (f::Function)(..) @named sys = ODESystem( [D(x[0]) ~ p[1] * x[0] + x[2], D(x[1]) ~ p[2] * f(x) + x[2]], t) - sys, = structural_simplify(sys, ([x[2]], [])) + sys, = mtkbuild(sys, ([x[2]], [])) @test is_parameter(sys, x[2]) prob = ODEProblem(sys, [x[0] => 1.0, x[1] => 1.0], (0.0, 1.0), [p => ones(2), f => sum, x[2] => 2.0]) diff --git a/test/components.jl b/test/components.jl index 43c0d1acf8..b697a79787 100644 --- a/test/components.jl +++ b/test/components.jl @@ -48,9 +48,9 @@ include("common/rc_model.jl") completed_rc_model = complete(rc_model) @test isequal(completed_rc_model.resistor.n.i, resistor.n.i) @test ModelingToolkit.n_expanded_connection_equations(capacitor) == 2 - @test length(equations(structural_simplify(rc_model, allow_parameter = false))) == 2 - sys = structural_simplify(rc_model) - @test_throws ModelingToolkit.RepeatedStructuralSimplificationError structural_simplify(sys) + @test length(equations(mtkbuild(rc_model, allow_parameter = false))) == 2 + sys = mtkbuild(rc_model) + @test_throws ModelingToolkit.RepeatedStructuralSimplificationError mtkbuild(sys) @test length(equations(sys)) == 1 check_contract(sys) @test !isempty(ModelingToolkit.defaults(sys)) @@ -61,7 +61,7 @@ include("common/rc_model.jl") end @testset "Outer/inner connections" begin - sys = structural_simplify(rc_model) + sys = mtkbuild(rc_model) prob = ODEProblem(sys, [sys.capacitor.v => 0.0], (0.0, 10.0)) sol = solve(prob, Rodas4()) @@ -91,7 +91,7 @@ end @named sys_inner_outer = compose(sys′, [ground, shape, source, rc_comp]) @test_nowarn show(IOBuffer(), MIME"text/plain"(), sys_inner_outer) expand_connections(sys_inner_outer, debug = true) - sys_inner_outer = structural_simplify(sys_inner_outer) + sys_inner_outer = mtkbuild(sys_inner_outer) @test !isempty(ModelingToolkit.defaults(sys_inner_outer)) u0 = [rc_comp.capacitor.v => 0.0] prob = ODEProblem(sys_inner_outer, u0, (0, 10.0), sparse = true) @@ -113,7 +113,7 @@ end include("common/serial_inductor.jl") @testset "Serial inductor" begin - sys = structural_simplify(ll_model) + sys = mtkbuild(ll_model) @test length(equations(sys)) == 2 u0 = unknowns(sys) .=> 0 @test_nowarn ODEProblem( @@ -122,7 +122,7 @@ include("common/serial_inductor.jl") sol = solve(prob, DFBDF()) @test sol.retcode == SciMLBase.ReturnCode.Success - sys2 = structural_simplify(ll2_model) + sys2 = mtkbuild(ll2_model) @test length(equations(sys2)) == 3 u0 = unknowns(sys) .=> 0 prob = ODEProblem(sys, u0, (0, 10.0)) @@ -181,7 +181,7 @@ function Circuit(; name) end @named foo = Circuit() -@test structural_simplify(foo) isa ModelingToolkit.AbstractSystem +@test mtkbuild(foo) isa ModelingToolkit.AbstractSystem # BLT tests @testset "BLT ordering" begin @@ -242,7 +242,7 @@ end @named _rc_model = ODESystem(rc_eqs, t) @named rc_model = compose(_rc_model, [resistor, capacitor, ground]) - sys = structural_simplify(rc_model) + sys = mtkbuild(rc_model) prob = ODEProblem(sys, [sys.c1.v => 0.0], (0, 10.0)) sol = solve(prob, Tsit5()) end @@ -288,7 +288,7 @@ end end @named outer = Outer() - simp = structural_simplify(outer) + simp = mtkbuild(outer) @test sort(propertynames(outer)) == [:inner, :t, :x] @test propertynames(simp) == propertynames(outer) @@ -305,7 +305,7 @@ end @test_throws ArgumentError outer.inner₊p end -@testset "`getproperty` on `structural_simplify(complete(sys))`" begin +@testset "`getproperty` on `mtkbuild(complete(sys))`" begin @mtkmodel Foo begin @variables begin x(t) @@ -321,7 +321,7 @@ end end @named bar = Bar() cbar = complete(bar) - ss = structural_simplify(cbar) + ss = mtkbuild(cbar) @test isequal(cbar.foo.x, ss.foo.x) end diff --git a/test/constants.jl b/test/constants.jl index f2c4fdaa86..3e48c83f59 100644 --- a/test/constants.jl +++ b/test/constants.jl @@ -17,12 +17,12 @@ sol = solve(prob, Tsit5()) newsys = MT.eliminate_constants(sys) @test isequal(equations(newsys), [D(x) ~ 1]) -# Test structural_simplify substitutions & observed values +# Test mtkbuild substitutions & observed values eqs = [D(x) ~ 1, w ~ a] @named sys = ODESystem(eqs, t) # Now eliminate the constants first -simp = structural_simplify(sys) +simp = mtkbuild(sys) @test equations(simp) == [D(x) ~ 1.0] #Constant with units @@ -34,7 +34,7 @@ UMT.get_unit(β) D = Differential(t) eqs = [D(x) ~ β] @named sys = ODESystem(eqs, t) -simp = structural_simplify(sys) +simp = mtkbuild(sys) @test isempty(MT.collect_constants(nothing)) diff --git a/test/dde.jl b/test/dde.jl index c7561e6c24..e94e4aa5d0 100644 --- a/test/dde.jl +++ b/test/dde.jl @@ -119,11 +119,11 @@ eqs = [osc1.jcn ~ osc2.delx, @test ModelingToolkit.is_dde(coupledOsc2) @test !is_markovian(coupledOsc2) for coupledOsc in [coupledOsc, coupledOsc2] - local sys = structural_simplify(coupledOsc) + local sys = mtkbuild(coupledOsc) @test length(equations(sys)) == 4 @test length(unknowns(sys)) == 4 end -sys = structural_simplify(coupledOsc) +sys = mtkbuild(coupledOsc) prob = DDEProblem(sys, [], (0.0, 10.0); constant_lags = [sys.osc1.τ, sys.osc2.τ]) sol = solve(prob, MethodOfSteps(Tsit5())) obsfn = ModelingToolkit.build_explicit_observed_function( @@ -178,7 +178,7 @@ end eqs = [D(x(t)) ~ -w * x(t - τ)] @named sys = System(eqs, t) - sys = structural_simplify(sys) + sys = mtkbuild(sys) prob = DDEProblem(sys, [], @@ -191,7 +191,7 @@ end @brownian r eqs = [D(x(t)) ~ -w * x(t - τ) + r] @named sys = System(eqs, t) - sys = structural_simplify(sys) + sys = mtkbuild(sys) prob = SDDEProblem(sys, [], (0.0, 10.0), diff --git a/test/debugging.jl b/test/debugging.jl index a55684737c..3a3cbd5d88 100644 --- a/test/debugging.jl +++ b/test/debugging.jl @@ -6,8 +6,8 @@ using ModelingToolkit: t_nounits as t, D_nounits as D, ASSERTION_LOG_VARIABLE @brownian a @named inner_ode = ODESystem(D(x) ~ -sqrt(x), t; assertions = [(x > 0) => "ohno"]) @named inner_sde = System([D(x) ~ -sqrt(x) + a], t; assertions = [(x > 0) => "ohno"]) -sys_ode = structural_simplify(inner_ode) -sys_sde = structural_simplify(inner_sde) +sys_ode = mtkbuild(inner_ode) +sys_sde = mtkbuild(inner_sde) @testset "assertions are present in generated `f`" begin @testset "$(typeof(sys))" for (Problem, sys, alg) in [ diff --git a/test/discrete_system.jl b/test/discrete_system.jl index b0e2481e56..b183fdca62 100644 --- a/test/discrete_system.jl +++ b/test/discrete_system.jl @@ -31,7 +31,7 @@ eqs = [S ~ S(k - 1) - infection * h, # System @named sys = DiscreteSystem(eqs, t, [S, I, R], [c, nsteps, δt, β, γ]) -syss = structural_simplify(sys) +syss = mtkbuild(sys) @test syss == syss df = DiscreteFunction(syss) @@ -254,7 +254,7 @@ end @variables x(t) y(t) k = ShiftIndex(t) @named sys = DiscreteSystem([x ~ x^2 + y^2, y ~ x(k - 1) + y(k - 1)], t) -@test_throws ["algebraic equations", "ImplicitDiscreteSystem"] structural_simplify(sys) +@test_throws ["algebraic equations", "ImplicitDiscreteSystem"] mtkbuild(sys) @testset "Passing `nothing` to `u0`" begin @variables x(t) = 1 diff --git a/test/domain_connectors.jl b/test/domain_connectors.jl index 9a43d2938f..736abfc81f 100644 --- a/test/domain_connectors.jl +++ b/test/domain_connectors.jl @@ -148,7 +148,7 @@ esys = ModelingToolkit.expand_connections(odesys) csys = complete(odesys) -sys = structural_simplify(odesys) +sys = mtkbuild(odesys) @test length(equations(sys)) == length(unknowns(sys)) sys_defs = ModelingToolkit.defaults(sys) diff --git a/test/downstream/analysis_points.jl b/test/downstream/analysis_points.jl index 29b9aad512..fddadd20c4 100644 --- a/test/downstream/analysis_points.jl +++ b/test/downstream/analysis_points.jl @@ -60,7 +60,7 @@ import ControlSystemsBase as CS filt.xd => 0.0 ]) - sys = structural_simplify(closed_loop) + sys = mtkbuild(closed_loop) prob = ODEProblem(sys, unknowns(sys) .=> 0.0, (0.0, 4.0)) sol = solve(prob, Rodas5P(), reltol = 1e-6, abstol = 1e-9) @@ -100,8 +100,8 @@ end connect(F.output, sys_inner.add.input1)] sys_outer = ODESystem(eqs, t, systems = [F, sys_inner, r], name = :outer) - # test first that the structural_simplify works correctly - ssys = structural_simplify(sys_outer) + # test first that the mtkbuild works correctly + ssys = mtkbuild(sys_outer) prob = ODEProblem(ssys, Pair[], (0, 10)) @test_nowarn solve(prob, Rodas5()) @@ -136,8 +136,8 @@ end connect(F.output, sys_inner.add.input1)] sys_outer = ODESystem(eqs, t, systems = [F, sys_inner, r], name = :outer) - # test first that the structural_simplify works correctly - ssys = structural_simplify(sys_outer) + # test first that the mtkbuild works correctly + ssys = mtkbuild(sys_outer) prob = ODEProblem(ssys, Pair[], (0, 10)) @test_nowarn solve(prob, Rodas5()) @@ -172,8 +172,8 @@ end connect(F.output, sys_inner.add.input1)] sys_outer = ODESystem(eqs, t, systems = [F, sys_inner, r], name = :outer) - # test first that the structural_simplify works correctly - ssys = structural_simplify(sys_outer) + # test first that the mtkbuild works correctly + ssys = mtkbuild(sys_outer) prob = ODEProblem(ssys, Pair[], (0, 10)) @test_nowarn solve(prob, Rodas5()) @@ -363,7 +363,7 @@ end sys_normal = normal_test_system() -prob = ODEProblem(structural_simplify(sys_normal), [], (0.0, 10.0)) +prob = ODEProblem(mtkbuild(sys_normal), [], (0.0, 10.0)) @test SciMLBase.successful_retcode(solve(prob, Rodas5P())) matrices_normal, _ = get_sensitivity(sys_normal, sys_normal.normal_inner.ap) @@ -384,7 +384,7 @@ matrices_normal, _ = get_sensitivity(sys_normal, sys_normal.normal_inner.ap) connect(inner.back.output, :ap, inner.F1.input)] @named sys = ODESystem(eqs2, t; systems = [inner, step]) - prob = ODEProblem(structural_simplify(sys), [], (0.0, 10.0)) + prob = ODEProblem(mtkbuild(sys), [], (0.0, 10.0)) @test SciMLBase.successful_retcode(solve(prob, Rodas5P())) matrices, _ = get_sensitivity(sys, sys.ap) @@ -408,7 +408,7 @@ end connect(inner.back.output.u, :ap, inner.F1.input.u)] @named sys = ODESystem(eqs2, t; systems = [inner, step]) - prob = ODEProblem(structural_simplify(sys), [], (0.0, 10.0)) + prob = ODEProblem(mtkbuild(sys), [], (0.0, 10.0)) @test SciMLBase.successful_retcode(solve(prob, Rodas5P())) matrices, _ = get_sensitivity(sys, sys.ap) @@ -432,7 +432,7 @@ end connect(inner.back.output.u, :ap, inner.F1.input.u)] @named sys = ODESystem(eqs2, t; systems = [inner, step]) - prob = ODEProblem(structural_simplify(sys), [], (0.0, 10.0)) + prob = ODEProblem(mtkbuild(sys), [], (0.0, 10.0)) @test SciMLBase.successful_retcode(solve(prob, Rodas5P())) matrices, _ = get_sensitivity(sys, sys.ap) diff --git a/test/downstream/inversemodel.jl b/test/downstream/inversemodel.jl index 32d5ee87ec..0251c43e38 100644 --- a/test/downstream/inversemodel.jl +++ b/test/downstream/inversemodel.jl @@ -110,7 +110,7 @@ end end end; @named model = InverseControlledTank() -ssys = structural_simplify(model) +ssys = mtkbuild(model) cm = complete(model) op = Dict( diff --git a/test/downstream/linearize.jl b/test/downstream/linearize.jl index 16df29f834..45574ec8c4 100644 --- a/test/downstream/linearize.jl +++ b/test/downstream/linearize.jl @@ -13,11 +13,12 @@ eqs = [u ~ kp * (r - y) y ~ x] @named sys = ODESystem(eqs, t) +sys = mtkbuild(sys, inputs = [r], outputs = [y]) -lsys, ssys = linearize(sys, [r], [y]) +lsys = linearize(sys, [r], [y]) lprob = LinearizationProblem(sys, [r], [y]) lsys2 = solve(lprob) -lsys3, _ = linearize(sys, [r], [y]; autodiff = AutoFiniteDiff()) +lsys3 = linearize(sys, [r], [y]; autodiff = AutoFiniteDiff()) @test lsys.A[] == lsys2.A[] == lsys3.A[] == -2 @test lsys.B[] == lsys2.B[] == lsys3.B[] == 1 @@ -86,11 +87,12 @@ connections = [f.y ~ c.r # filtered reference to controller reference p.y ~ c.y] @named cl = ODESystem(connections, t, systems = [f, c, p]) +cl = mtkbuild(cl, inputs = [f.u], outputs = [p.x]) -lsys0, ssys = linearize(cl, [f.u], [p.x]) +lsys0, ssys = linearize(cl) desired_order = [f.x, p.x] lsys = ModelingToolkit.reorder_unknowns(lsys0, unknowns(ssys), desired_order) -lsys1, ssys = linearize(cl, [f.u], [p.x]; autodiff = AutoFiniteDiff()) +lsys1, ssys = linearize(cl; autodiff = AutoFiniteDiff()) lsys2 = ModelingToolkit.reorder_unknowns(lsys1, unknowns(ssys), desired_order) @test lsys.A == lsys2.A == [-2 0; 1 -2] @@ -199,7 +201,7 @@ lsys, ssys = linearize(sat, [u], [y]) # @test substitute(lsyss.D, ModelingToolkit.defaults(sat)) == lsys.D # outside the linear region the derivative is 0 -lsys, ssys = linearize(sat, [u], [y]; op = Dict(u => 2)) +lsys = linearize(sat, [u], [y]; op = Dict(u => 2)) @test isempty(lsys.A) # there are no differential variables in this system @test isempty(lsys.B) @test isempty(lsys.C) @@ -265,8 +267,9 @@ closed_loop = ODESystem(connections, t, systems = [model, pid, filt, sensor, r, filt.x => 0.0, filt.xd => 0.0 ]) +closed_loop = mtkbuild(closed_loop, inputs = :r, outputs = :y) -@test_nowarn linearize(closed_loop, :r, :y; warn_empty_op = false) +@test_nowarn linearize(closed_loop; warn_empty_op = false) # https://discourse.julialang.org/t/mtk-change-in-linearize/115760/3 @mtkmodel Tank_noi begin @@ -296,6 +299,7 @@ end @named tank_noi = Tank_noi() @unpack md_i, h, m = tank_noi +tank_noi = mtkbuild(tank_noi, inputs = [md_i], outputs = [h]) m_ss = 2.4000000003229878 @test_nowarn linearize(tank_noi, [md_i], [h]; op = Dict(m => m_ss, md_i => 2)) @@ -304,13 +308,14 @@ m_ss = 2.4000000003229878 @parameters p = 1.0 eqs = [D(x) ~ p * u, x ~ y] @named sys = ODESystem(eqs, t) +sys = mtkbuild(sys, inputs = [u]) -matrices1, _ = linearize(sys, [u], []; op = Dict(x => 2.0)) -matrices2, _ = linearize(sys, [u], []; op = Dict(y => 2.0)) +matrices1 = linearize(sys; op = Dict(x => 2.0)) +matrices2 = linearize(sys; op = Dict(y => 2.0)) @test matrices1 == matrices2 # Ensure parameter values passed as `Dict` are respected -linfun, _ = linearization_function(sys, [u], []; op = Dict(x => 2.0)) +linfun = linearization_function(sys, [u], []; op = Dict(x => 2.0)) matrices = linfun([1.0], Dict(p => 3.0), 1.0) # this would be 1 if the parameter value isn't respected @test matrices.f_u[] == 3.0 @@ -326,7 +331,7 @@ end @parameters p eqs = [0 ~ x * log(y) - p] @named sys = ODESystem(eqs, t; defaults = [p => 1.0]) - sys = complete(sys) + sys = mtkbuild(sys, inputs = [x]) @test_throws ModelingToolkit.MissingVariablesError linearize( sys, [x], []; op = Dict(x => 1.0), allow_input_derivatives = true) @test_nowarn linearize( @@ -337,6 +342,7 @@ end @testset "Symbolic values for parameters in `linearize`" begin @named tank_noi = Tank_noi() @unpack md_i, h, m, ρ, A, K = tank_noi + tank_noi = mtkbuild(tank_noi, inputs = [md_i], outputs = [h]) m_ss = 2.4000000003229878 @test_nowarn linearize( tank_noi, [md_i], [h]; op = Dict(m => m_ss, md_i => 2, ρ => A / K, A => 5)) @@ -345,6 +351,7 @@ end @testset "Warn on empty operating point" begin @named tank_noi = Tank_noi() @unpack md_i, h, m = tank_noi + tank_noi = mtkbuild(tank_noi, inputs = [md_i], outputs = [h]) m_ss = 2.4000000003229878 @test_warn ["empty operating point", "warn_empty_op"] linearize( tank_noi, [md_i], [h]; p = [md_i => 1.0]) diff --git a/test/downstream/test_disturbance_model.jl b/test/downstream/test_disturbance_model.jl index 97276437e2..937000d28a 100644 --- a/test/downstream/test_disturbance_model.jl +++ b/test/downstream/test_disturbance_model.jl @@ -55,7 +55,7 @@ end end @named model = ModelWithInputs() # Model with load disturbance -ssys = structural_simplify(model) +ssys = mtkbuild(model) prob = ODEProblem(ssys, [], (0.0, 10.0)) sol = solve(prob, Tsit5()) # plot(sol) @@ -94,10 +94,10 @@ dist(; name) = ODESystem(1 / s; name) end @named model_with_disturbance = SystemModelWithDisturbanceModel() -# ssys = structural_simplify(open_loop(model_with_disturbance, :d)) # Open loop worked, but it's a bit awkward that we have to use it here +# ssys = mtkbuild(open_loop(model_with_disturbance, :d)) # Open loop worked, but it's a bit awkward that we have to use it here # lsys2 = named_ss(model_with_disturbance, [:u, :d1], # [P.inertia1.phi, P.inertia2.phi, P.inertia1.w, P.inertia2.w]) -ssys = structural_simplify(model_with_disturbance) +ssys = mtkbuild(model_with_disturbance) prob = ODEProblem(ssys, [], (0.0, 10.0)) sol = solve(prob, Tsit5()) @test SciMLBase.successful_retcode(sol) @@ -137,10 +137,10 @@ dist3(; name) = ODESystem(ss(1 + 10 / s, balance = false); name) end @named model_with_disturbance = SystemModelWithDisturbanceModel() -# ssys = structural_simplify(open_loop(model_with_disturbance, :d)) # Open loop worked, but it's a bit awkward that we have to use it here +# ssys = mtkbuild(open_loop(model_with_disturbance, :d)) # Open loop worked, but it's a bit awkward that we have to use it here # lsys3 = named_ss(model_with_disturbance, [:u, :d1], # [P.inertia1.phi, P.inertia2.phi, P.inertia1.w, P.inertia2.w]) -ssys = structural_simplify(model_with_disturbance) +ssys = mtkbuild(model_with_disturbance) prob = ODEProblem(ssys, [], (0.0, 10.0)) sol = solve(prob, Tsit5()) @test SciMLBase.successful_retcode(sol) diff --git a/test/dq_units.jl b/test/dq_units.jl index 3c59c479c1..cd5396b10d 100644 --- a/test/dq_units.jl +++ b/test/dq_units.jl @@ -113,24 +113,24 @@ noiseeqs = [0.1us"W" 0.1us"W" eqs = [D(L) ~ v, V ~ L^3] @named sys = ODESystem(eqs, t) -sys_simple = structural_simplify(sys) +sys_simple = mtkbuild(sys) eqs = [D(V) ~ r, V ~ L^3] @named sys = ODESystem(eqs, t) -sys_simple = structural_simplify(sys) +sys_simple = mtkbuild(sys) @variables V [unit = u"m"^3] L [unit = u"m"] @parameters v [unit = u"m/s"] r [unit = u"m"^3 / u"s"] eqs = [V ~ r * t, V ~ L^3] @named sys = NonlinearSystem(eqs, [V, L], [t, r]) -sys_simple = structural_simplify(sys) +sys_simple = mtkbuild(sys) eqs = [L ~ v * t, V ~ L^3] @named sys = NonlinearSystem(eqs, [V, L], [t, r]) -sys_simple = structural_simplify(sys) +sys_simple = mtkbuild(sys) #Jump System @parameters β [unit = u"(mol^2*s)^-1"] γ [unit = u"(mol*s)^-1"] jumpmol [ diff --git a/test/error_handling.jl b/test/error_handling.jl index d5605e3cbc..7a7b67a0db 100644 --- a/test/error_handling.jl +++ b/test/error_handling.jl @@ -43,7 +43,7 @@ rc_eqs = [connect(source.p, resistor.p) connect(capacitor.n, source.n)] @named rc_model = ODESystem(rc_eqs, t, systems = [resistor, capacitor, source]) -@test_throws ModelingToolkit.ExtraVariablesSystemException structural_simplify(rc_model) +@test_throws ModelingToolkit.ExtraVariablesSystemException mtkbuild(rc_model) @named source2 = OverdefinedConstantVoltage(V = V, I = V / R) rc_eqs2 = [connect(source2.p, resistor.p) @@ -51,4 +51,4 @@ rc_eqs2 = [connect(source2.p, resistor.p) connect(capacitor.n, source2.n)] @named rc_model2 = ODESystem(rc_eqs2, t, systems = [resistor, capacitor, source2]) -@test_throws ModelingToolkit.ExtraEquationsSystemException structural_simplify(rc_model2) +@test_throws ModelingToolkit.ExtraEquationsSystemException mtkbuild(rc_model2) diff --git a/test/extensions/ad.jl b/test/extensions/ad.jl index adaf6117c6..69b14d15ec 100644 --- a/test/extensions/ad.jl +++ b/test/extensions/ad.jl @@ -46,7 +46,7 @@ end Th0 => (4 / 11)^(1 / 3) * Tγ0, Tγ0 => (15 / π^2 * ργ0 * (2 * h)^2 / 7)^(1 / 4) / 5 ]) - sys = structural_simplify(sys) + sys = mtkbuild(sys) function x_at_0(θ) prob = ODEProblem(sys, [sys.x => 1.0], (0.0, 1.0), [sys.ργ0 => θ[1], sys.h => θ[2]]) @@ -111,7 +111,7 @@ fwd, back = ChainRulesCore.rrule(remake_buffer, sys, ps, idxs, vals) eqs = [D(D(y)) ~ -9.81] initialization_eqs = [y^2 ~ 0] # initialize y = 0 in a way that builds an initialization problem @named sys = ODESystem(eqs, t; initialization_eqs) - sys = structural_simplify(sys) + sys = mtkbuild(sys) # Find initial throw velocity that reaches exactly 10 m after 1 s dprob0 = ODEProblem(sys, [D(y) => NaN], (0.0, 1.0), []; guesses = [y => 0.0]) diff --git a/test/extensions/bifurcationkit.jl b/test/extensions/bifurcationkit.jl index 629edf46a6..101efb202b 100644 --- a/test/extensions/bifurcationkit.jl +++ b/test/extensions/bifurcationkit.jl @@ -97,14 +97,14 @@ end # Checks that default parameter values are accounted for. # Checks that observables (that depend on other observables, as in this case) are accounted for. let - # Creates model, and uses `structural_simplify` to generate observables. + # Creates model, and uses `mtkbuild` to generate observables. @parameters μ p=2 @variables x(t) y(t) z(t) eqs = [0 ~ μ - x^3 + 2x^2, 0 ~ p * μ - y, 0 ~ y - z] @named nsys = NonlinearSystem(eqs, [x, y, z], [μ, p]) - nsys = structural_simplify(nsys) + nsys = mtkbuild(nsys) # Creates BifurcationProblem. bif_par = μ diff --git a/test/funcaffect.jl b/test/funcaffect.jl index 05543c9161..c7c5ad80d7 100644 --- a/test/funcaffect.jl +++ b/test/funcaffect.jl @@ -131,7 +131,7 @@ s1 = compose( ODESystem(Equation[], t, [], [], name = :s1, discrete_events = 1.0 => (affect4!, [resistor.v], [], [], ctx)), resistor) -s2 = structural_simplify(s1) +s2 = mtkbuild(s1) prob = ODEProblem(s2, [resistor.v => 10.0], (0, 2.01)) sol = solve(prob, Tsit5()) @test ctx[1] == 2 @@ -152,7 +152,7 @@ end rc_model = extend(rc_model, event_sys) # rc_model = compose(rc_model, [resistor, capacitor, source, ground]) -sys = structural_simplify(rc_model) +sys = mtkbuild(rc_model) u0 = [capacitor.v => 0.0 capacitor.p.i => 0.0 resistor.v => 0.0] @@ -199,7 +199,7 @@ rc_eqs2 = [connect(shape.output, source.V) @named rc_model2 = ODESystem(rc_eqs2, t) rc_model2 = compose(rc_model2, [resistor, capacitor2, shape, source, ground]) -sys2 = structural_simplify(rc_model2) +sys2 = mtkbuild(rc_model2) u0 = [capacitor2.v => 0.0 capacitor2.p.i => 0.0 resistor.v => 0.0] @@ -288,7 +288,7 @@ end [y ~ zr] => (bb_affect!, [v], [], [], nothing) ]) -bb_sys = structural_simplify(bb_model) +bb_sys = mtkbuild(bb_model) @test only(ModelingToolkit.affects(ModelingToolkit.continuous_events(bb_sys))) isa ModelingToolkit.FunctionalAffect diff --git a/test/guess_propagation.jl b/test/guess_propagation.jl index 738e930adc..ae188ba2df 100644 --- a/test/guess_propagation.jl +++ b/test/guess_propagation.jl @@ -11,7 +11,7 @@ eqs = [D(x) ~ 1 initialization_eqs = [1 ~ exp(1 + x)] @named sys = ODESystem(eqs, t; initialization_eqs) -sys = complete(structural_simplify(sys)) +sys = complete(mtkbuild(sys)) tspan = (0.0, 0.2) prob = ODEProblem(sys, [], tspan, []) @@ -28,7 +28,7 @@ eqs = [D(x) ~ 1 initialization_eqs = [1 ~ exp(1 + x)] @named sys = ODESystem(eqs, t; initialization_eqs) -sys = complete(structural_simplify(sys)) +sys = complete(mtkbuild(sys)) tspan = (0.0, 0.2) prob = ODEProblem(sys, [], tspan, []) @@ -46,7 +46,7 @@ eqs = [D(x) ~ a] initialization_eqs = [1 ~ exp(1 + x)] @named sys = ODESystem(eqs, t; initialization_eqs) -sys = complete(structural_simplify(sys)) +sys = complete(mtkbuild(sys)) tspan = (0.0, 0.2) prob = ODEProblem(sys, [], tspan, []) @@ -66,7 +66,7 @@ eqs = [D(x) ~ a, initialization_eqs = [1 ~ exp(1 + x)] @named sys = ODESystem(eqs, t; initialization_eqs) -sys = complete(structural_simplify(sys)) +sys = complete(mtkbuild(sys)) tspan = (0.0, 0.2) prob = ODEProblem(sys, [], tspan, []) diff --git a/test/hierarchical_initialization_eqs.jl b/test/hierarchical_initialization_eqs.jl index 1e3109a66e..022c5c4431 100644 --- a/test/hierarchical_initialization_eqs.jl +++ b/test/hierarchical_initialization_eqs.jl @@ -140,7 +140,7 @@ syslist = ModelingToolkit.get_systems(model) @test length(ModelingToolkit.initialization_equations(model)) == 2 u0 = [] -prob = ODEProblem(structural_simplify(model), u0, (0.0, 10.0)) +prob = ODEProblem(mtkbuild(model), u0, (0.0, 10.0)) sol = solve(prob, Rodas5P()) @test length(sol.u[end]) == 2 @test length(equations(prob.f.initializeprob.f.sys)) == 0 diff --git a/test/if_lifting.jl b/test/if_lifting.jl index 9c58e676d0..b6f5ac3387 100644 --- a/test/if_lifting.jl +++ b/test/if_lifting.jl @@ -13,9 +13,9 @@ using ModelingToolkit: t_nounits as t, D_nounits as D, IfLifting, no_if_lift end end @named sys = SimpleAbs() - ss1 = structural_simplify(sys) + ss1 = mtkbuild(sys) @test length(equations(ss1)) == 1 - ss2 = structural_simplify(sys, additional_passes = [IfLifting]) + ss2 = mtkbuild(sys, additional_passes = [IfLifting]) @test length(equations(ss2)) == 1 @test length(parameters(ss2)) == 1 @test operation(only(equations(ss2)).rhs) === ifelse @@ -71,7 +71,7 @@ end end @named sys = BigModel() - ss = structural_simplify(sys, additional_passes = [IfLifting]) + ss = mtkbuild(sys, additional_passes = [IfLifting]) ps = parameters(ss) @test length(ps) == 9 diff --git a/test/initializationsystem.jl b/test/initializationsystem.jl index 5c36fcba3e..2d90940983 100644 --- a/test/initializationsystem.jl +++ b/test/initializationsystem.jl @@ -415,7 +415,7 @@ sol = solve(prob, Tsit5()) D(z) ~ x * y - β * z] @named sys = ODESystem(eqs, t) - sys = structural_simplify(sys) + sys = mtkbuild(sys) u0 = [D(x) => 2.0, x => 1.0, @@ -444,7 +444,7 @@ eqs = [D(x) ~ α * x - β * x * y z ~ x + y] @named sys = ODESystem(eqs, t) -simpsys = structural_simplify(sys) +simpsys = mtkbuild(sys) tspan = (0.0, 10.0) prob = ODEProblem(simpsys, [D(x) => 0.0, y => 0.0], tspan, guesses = [x => 0.0]) @@ -477,7 +477,7 @@ prob = ODEProblem(pend, [x => 1], (0.0, 1.5), [g => 1], guesses = [λ => 0, y => 1], initialization_eqs = [y ~ 1]) unsimp = generate_initializesystem(pend; u0map = [x => 1], initialization_eqs = [y ~ 1]) -sys = structural_simplify(unsimp; fully_determined = false) +sys = mtkbuild(unsimp; fully_determined = false) @test length(equations(sys)) in (3, 4) # could be either depending on tearing # Extend two systems with initialization equations and guesses @@ -493,7 +493,7 @@ sys = extend(sysx, sysy) @testset "Error on missing defaults" begin @variables x(t) y(t) @named sys = ODESystem([x^2 + y^2 ~ 25, D(x) ~ 1], t) - ssys = structural_simplify(sys) + ssys = mtkbuild(sys) @test_throws ModelingToolkit.MissingVariablesError ODEProblem( ssys, [x => 3], (0, 1), []) # y should have a guess end @@ -504,7 +504,7 @@ end # system 1 should solve to x = 1 ics1 = [x => 1] - sys1 = ODESystem([D(x) ~ 0], t; defaults = ics1, name = :sys1) |> structural_simplify + sys1 = ODESystem([D(x) ~ 0], t; defaults = ics1, name = :sys1) |> mtkbuild prob1 = ODEProblem(sys1, [], (0.0, 1.0), []) sol1 = solve(prob1, Tsit5()) @test all(sol1[x] .== 1) @@ -513,7 +513,7 @@ end sys2 = extend( sys1, ODESystem([D(y) ~ 0], t; initialization_eqs = [y ~ 2], name = :sys2) - ) |> structural_simplify + ) |> mtkbuild ics2 = unknowns(sys1) .=> 2 # should be equivalent to "ics2 = [x => 2]" prob2 = ODEProblem(sys2, ics2, (0.0, 1.0), []; fully_determined = true) sol2 = solve(prob2, Tsit5()) @@ -525,12 +525,12 @@ end @variables x(t) sys = ODESystem( [D(D(x)) ~ 0], t; initialization_eqs = [x ~ 0, D(x) ~ 1], name = :sys) |> - structural_simplify + mtkbuild @test_nowarn ODEProblem(sys, [], (0.0, 1.0), []) sys = ODESystem( [D(D(x)) ~ 0], t; initialization_eqs = [x ~ 0, D(D(x)) ~ 0], name = :sys) |> - structural_simplify + mtkbuild @test_nowarn ODEProblem(sys, [D(x) => 1.0], (0.0, 1.0), []) end @@ -541,7 +541,7 @@ end sys = ODESystem( [D(D(x)) ~ 0], t; initialization_eqs = [D(x)^2 ~ 1, x ~ 0], guesses = [D(x) => sign], name = :sys - ) |> structural_simplify + ) |> mtkbuild prob = ODEProblem(sys, [], (0.0, 1.0), []) sol = solve(prob, Tsit5()) @test sol(1.0, idxs = sys.x) ≈ sign # system with D(x(0)) = ±1 should solve to x(1) = ±1 @@ -582,7 +582,7 @@ sol = solve(oprob_2nd_order_2, Rosenbrock23()) # retcode: Success @testset "Vector in initial conditions" begin @variables x(t)[1:5] y(t)[1:5] @named sys = ODESystem([D(x) ~ x, D(y) ~ y], t; initialization_eqs = [y ~ -x]) - sys = structural_simplify(sys) + sys = mtkbuild(sys) prob = ODEProblem(sys, [sys.x => ones(5)], (0.0, 1.0), []) sol = solve(prob, Tsit5(), reltol = 1e-4) @test all(sol(1.0, idxs = sys.x) .≈ +exp(1)) && all(sol(1.0, idxs = sys.y) .≈ -exp(1)) @@ -1145,7 +1145,7 @@ end end model = dc_motor() - sys = structural_simplify(model) + sys = mtkbuild(model) prob = ODEProblem(sys, [sys.L1.i => 0.0], (0, 6.0)) @@ -1250,7 +1250,7 @@ end @parameters a = 1 @named sys = ODESystem([D(x) ~ 0, D(y) ~ x + a], t; initialization_eqs = [y ~ a]) - ssys = structural_simplify(sys) + ssys = mtkbuild(sys) prob = ODEProblem(ssys, [], (0, 1), []) @test SciMLBase.successful_retcode(solve(prob)) @@ -1355,7 +1355,7 @@ end continuous_events = [ [y ~ 0.5] => (stop!, [y], [], [], nothing) ]) - sys = structural_simplify(sys) + sys = mtkbuild(sys) prob0 = ODEProblem(sys, [x => NaN], (0.0, 1.0), []) # final_x(x0) is equivalent to x0 + 0.5 diff --git a/test/input_output_handling.jl b/test/input_output_handling.jl index 693a00b9ad..0a211de9de 100644 --- a/test/input_output_handling.jl +++ b/test/input_output_handling.jl @@ -7,10 +7,10 @@ using ModelingToolkit: t_nounits as t, D_nounits as D @variables xx(t) some_input(t) [input = true] eqs = [D(xx) ~ some_input] @named model = ODESystem(eqs, t) -@test_throws ExtraVariablesSystemException structural_simplify(model, ((), ())) +@test_throws ExtraVariablesSystemException mtkbuild(model, ((), ())) if VERSION >= v"1.8" err = "In particular, the unset input(s) are:\n some_input(t)" - @test_throws err structural_simplify(model, ((), ())) + @test_throws err mtkbuild(model, ((), ())) end # Test input handling @@ -50,7 +50,7 @@ end @test !is_bound(sys31, sys1.v[2]) # simplification turns input variables into parameters -ssys, _ = structural_simplify(sys, ([u], [])) +ssys, _ = mtkbuild(sys, ([u], [])) @test ModelingToolkit.isparameter(unbound_inputs(ssys)[]) @test !is_bound(ssys, u) @test u ∈ Set(unbound_inputs(ssys)) @@ -88,7 +88,7 @@ fsys4 = flatten(sys4) @variables x(t) y(t) [output = true] @test isoutput(y) @named sys = ODESystem([D(x) ~ -x, y ~ x], t) # both y and x are unbound -syss = structural_simplify(sys) # This makes y an observed variable +syss = mtkbuild(sys) # This makes y an observed variable @named sys2 = ODESystem([D(x) ~ -sys.x, y ~ sys.y], t, systems = [sys]) @@ -106,7 +106,7 @@ syss = structural_simplify(sys) # This makes y an observed variable @test isequal(unbound_outputs(sys2), [y]) @test isequal(bound_outputs(sys2), [sys.y]) -syss = structural_simplify(sys2) +syss = mtkbuild(sys2) @test !is_bound(syss, y) @test !is_bound(syss, x) @@ -281,7 +281,7 @@ i = findfirst(isequal(u[1]), out) @variables x(t) u(t) [input = true] eqs = [D(x) ~ u] @named sys = ODESystem(eqs, t) -@test_nowarn structural_simplify(sys, ([u], [])) +@test_nowarn mtkbuild(sys, ([u], [])) #= ## Disturbance input handling @@ -366,7 +366,7 @@ eqs = [D(y₁) ~ -k₁ * y₁ + k₃ * y₂ * y₃ + u1 @named sys = ODESystem(eqs, t) m_inputs = [u[1], u[2]] m_outputs = [y₂] -sys_simp, input_idxs = structural_simplify(sys, (; inputs = m_inputs, outputs = m_outputs)) +sys_simp, input_idxs = mtkbuild(sys, (; inputs = m_inputs, outputs = m_outputs)) @test isequal(unknowns(sys_simp), collect(x[1:2])) @test length(input_idxs) == 2 @@ -384,12 +384,12 @@ sys_simp, input_idxs = structural_simplify(sys, (; inputs = m_inputs, outputs = ], t, systems = [int, gain, c, fb]) -sys = structural_simplify(model) +sys = mtkbuild(model) @test length(unknowns(sys)) == length(equations(sys)) == 1 ## Disturbance models when plant has multiple inputs using ModelingToolkit, LinearAlgebra -using ModelingToolkit: DisturbanceModel, io_preprocessing, get_iv, get_disturbance_system +using ModelingToolkit: DisturbanceModel, get_iv, get_disturbance_system using ModelingToolkitStandardLibrary.Blocks A, C = [randn(2, 2) for i in 1:2] B = [1.0 0; 0 1.0] diff --git a/test/jumpsystem.jl b/test/jumpsystem.jl index 6c96055270..488b9396f6 100644 --- a/test/jumpsystem.jl +++ b/test/jumpsystem.jl @@ -269,7 +269,7 @@ dp4 = DiscreteProblem(js4, u0, tspan) @test_nowarn jp3 = JumpProblem(js3, dp3, Direct()) @test_nowarn jp4 = JumpProblem(js4, dp4, Direct()) -# Ensure `structural_simplify` (and `@mtkbuild`) works on JumpSystem (by doing nothing) +# Ensure `mtkbuild` (and `@mtkbuild`) works on JumpSystem (by doing nothing) # Issue#2558 @parameters k @variables X(t) diff --git a/test/modelingtoolkitize.jl b/test/modelingtoolkitize.jl index db99cc91a4..4ce25602d2 100644 --- a/test/modelingtoolkitize.jl +++ b/test/modelingtoolkitize.jl @@ -440,7 +440,7 @@ prob = NonlinearLeastSquaresProblem( NonlinearFunction(nlls!, resid_prototype = zeros(3)), u0) sys = modelingtoolkitize(prob) @test length(equations(sys)) == 3 -@test length(equations(structural_simplify(sys; fully_determined = false))) == 0 +@test length(equations(mtkbuild(sys; fully_determined = false))) == 0 @testset "`modelingtoolkitize(::SDEProblem)` sets defaults" begin function sdeg!(du, u, p, t) diff --git a/test/mtkparameters.jl b/test/mtkparameters.jl index 55de0768e0..10bda707d2 100644 --- a/test/mtkparameters.jl +++ b/test/mtkparameters.jl @@ -170,7 +170,7 @@ function level1() eqs = [D(x) ~ p1 * x - p2 * x * y D(y) ~ -p3 * y + p4 * x * y] - sys = structural_simplify(complete(ODESystem( + sys = mtkbuild(complete(ODESystem( eqs, t, tspan = (0, 3.0), name = :sys, parameter_dependencies = [y0 => 2p4]))) prob = ODEProblem{true, SciMLBase.FullSpecialize}(sys) end @@ -184,7 +184,7 @@ function level2() eqs = [D(x) ~ p1 * x - p23[1] * x * y D(y) ~ -p23[2] * y + p4 * x * y] - sys = structural_simplify(complete(ODESystem( + sys = mtkbuild(complete(ODESystem( eqs, t, tspan = (0, 3.0), name = :sys, parameter_dependencies = [y0 => 2p4]))) prob = ODEProblem{true, SciMLBase.FullSpecialize}(sys) end @@ -198,7 +198,7 @@ function level3() eqs = [D(x) ~ p1 * x - p23[1] * x * y D(y) ~ -p23[2] * y + p4 * x * y] - sys = structural_simplify(complete(ODESystem( + sys = mtkbuild(complete(ODESystem( eqs, t, tspan = (0, 3.0), name = :sys, parameter_dependencies = [y0 => 2p4]))) prob = ODEProblem{true, SciMLBase.FullSpecialize}(sys) end diff --git a/test/nonlinearsystem.jl b/test/nonlinearsystem.jl index a315371141..06f0122724 100644 --- a/test/nonlinearsystem.jl +++ b/test/nonlinearsystem.jl @@ -121,7 +121,7 @@ using OrdinaryDiffEq D = Differential(t) @named subsys = convert_system(ODESystem, lorenz1, t) @named sys = ODESystem([D(subsys.x) ~ subsys.x + subsys.x], t, systems = [subsys]) -sys = structural_simplify(sys) +sys = mtkbuild(sys) u0 = [subsys.x => 1, subsys.z => 2.0, subsys.y => 1.0] prob = ODEProblem(sys, u0, (0, 1.0), [subsys.σ => 1, subsys.ρ => 2, subsys.β => 3]) sol = solve(prob, FBDF(), reltol = 1e-7, abstol = 1e-7) @@ -198,7 +198,7 @@ eq = [v1 ~ sin(2pi * t * h) v2 ~ i2 i1 ~ i2] @named sys = ODESystem(eq, t) -@test length(equations(structural_simplify(sys))) == 0 +@test length(equations(mtkbuild(sys))) == 0 @testset "Issue: 1504" begin @variables u[1:4] @@ -257,7 +257,7 @@ end @named ns = NonlinearSystem(eqs, [x, y, z], []) ns = complete(ns) vs = [unknowns(ns); parameters(ns)] - ss_mtk = structural_simplify(ns) + ss_mtk = mtkbuild(ns) prob = NonlinearProblem(ss_mtk, vs .=> 1.0) sol = solve(prob) @test_nowarn sol[unknowns(ns)] @@ -277,16 +277,16 @@ sys = @test_nowarn NonlinearSystem(alg_eqs; name = :name) @parameters u3 u4 eqs = [u3 ~ u1 + u2, u4 ~ 2 * (u1 + u2), u3 + u4 ~ 3 * (u1 + u2)] @named ns = NonlinearSystem(eqs, [u1, u2], [u3, u4]) -sys = structural_simplify(ns; fully_determined = false) +sys = mtkbuild(ns; fully_determined = false) @test length(unknowns(sys)) == 1 # Conservative @variables X(t) alg_eqs = [1 ~ 2X] @named ns = NonlinearSystem(alg_eqs) -sys = structural_simplify(ns) +sys = mtkbuild(ns) @test length(equations(sys)) == 0 -sys = structural_simplify(ns; conservative = true) +sys = mtkbuild(ns; conservative = true) @test length(equations(sys)) == 1 # https://github.com/SciML/ModelingToolkit.jl/issues/2858 @@ -338,7 +338,7 @@ end -1 1/2 -1] b = [1, -2, 0] @named sys = NonlinearSystem(A * x ~ b, [x], []) - sys = structural_simplify(sys) + sys = mtkbuild(sys) prob = NonlinearProblem(sys, unknowns(sys) .=> 0.0) sol = solve(prob) @test all(sol[x] .≈ A \ b) @@ -349,8 +349,8 @@ end @parameters p @named sys = NonlinearSystem([x ~ 1, x^2 - p ~ 0]) for sys in [ - structural_simplify(sys, fully_determined = false), - structural_simplify(sys, fully_determined = false, split = false) + mtkbuild(sys, fully_determined = false), + mtkbuild(sys, fully_determined = false, split = false) ] @test length(equations(sys)) == 1 @test length(unknowns(sys)) == 0 @@ -424,7 +424,7 @@ end @test ModelingToolkit.iscomplete(nlsys) @test ModelingToolkit.is_split(nlsys) - sys3 = structural_simplify(sys) + sys3 = mtkbuild(sys) nlsys = NonlinearSystem(sys3) @test length(equations(nlsys)) == length(ModelingToolkit.observed(nlsys)) == 1 diff --git a/test/odesystem.jl b/test/odesystem.jl index f5fef1fd6f..27a1a3432e 100644 --- a/test/odesystem.jl +++ b/test/odesystem.jl @@ -478,7 +478,7 @@ end let @variables x(t)[1:3, 1:3] @named sys = ODESystem(D.(x) .~ x, t) - @test_nowarn structural_simplify(sys) + @test_nowarn mtkbuild(sys) end # Array vars @@ -489,7 +489,7 @@ ps = @parameters p[1:3] = [1, 2, 3] eqs = [collect(D.(x) .~ x) D(y) ~ norm(collect(x)) * y - x[1]] @named sys = ODESystem(eqs, t, sts, ps) -sys = structural_simplify(sys) +sys = mtkbuild(sys) @test isequal(@nonamespace(sys.x), x) @test isequal(@nonamespace(sys.y), y) @test isequal(@nonamespace(sys.p), p) @@ -661,7 +661,7 @@ let D(x[2]) ~ -x[1] - 0.5 * x[2] + k y ~ 0.9 * x[1] + x[2]] @named sys = ODESystem(eqs, t, vcat(x, [y]), [k], defaults = Dict(x .=> 0)) - sys = structural_simplify(sys) + sys = mtkbuild(sys) u0 = [0.5, 0] du0 = 0 .* copy(u0) @@ -743,7 +743,7 @@ let 0 ~ q / C - R * F] @named sys = ODESystem(eqs, t) - @test length(equations(structural_simplify(sys))) == 2 + @test length(equations(mtkbuild(sys))) == 2 end let @@ -778,12 +778,12 @@ let @named sys1 = ODESystem(eqs, t) @named sys2 = ODESystem(eqs2, t) @named sys3 = ODESystem(eqs3, t) - ssys3 = structural_simplify(sys3) + ssys3 = mtkbuild(sys3) @named sys4 = ODESystem(eqs4, t) @test ModelingToolkit.isisomorphic(sys1, sys2) @test !ModelingToolkit.isisomorphic(sys1, sys3) - @test ModelingToolkit.isisomorphic(sys1, ssys3) # I don't call structural_simplify in isisomorphic + @test ModelingToolkit.isisomorphic(sys1, ssys3) # I don't call mtkbuild in isisomorphic @test !ModelingToolkit.isisomorphic(sys1, sys4) # 1281 @@ -801,7 +801,7 @@ let spm ~ 0 sph ~ a] @named sys = ODESystem(eqs, t, vars, pars) - @test_throws ModelingToolkit.ExtraEquationsSystemException structural_simplify(sys) + @test_throws ModelingToolkit.ExtraEquationsSystemException mtkbuild(sys) end # 1561 @@ -825,9 +825,9 @@ let ps = [] @named sys = ODESystem(eqs, t, u, ps) - @test_nowarn simpsys = structural_simplify(sys) + @test_nowarn simpsys = mtkbuild(sys) - sys = structural_simplify(sys) + sys = mtkbuild(sys) u0 = ModelingToolkit.missing_variable_defaults(sys) u0_expected = Pair[s => 0.0 for s in unknowns(sys)] @@ -913,7 +913,7 @@ let @named connected = ODESystem(connections, t) @named sys_con = compose(connected, sys, ctrl) - sys_simp = structural_simplify(sys_con) + sys_simp = mtkbuild(sys_con) true_eqs = [D(sys.x) ~ sys.v D(sys.v) ~ ctrl.kv * sys.v + ctrl.kx * sys.x] @test issetequal(full_equations(sys_simp), true_eqs) @@ -924,7 +924,7 @@ let @variables y(t) = 1 @parameters pp = -1 @named sys4 = ODESystem([D(x) ~ -y; D(y) ~ 1 + pp * y + x], t) - sys4s = structural_simplify(sys4) + sys4s = mtkbuild(sys4) prob = ODEProblem(sys4s, [x => 1.0, D(x) => 1.0], (0, 1.0)) @test string.(unknowns(prob.f.sys)) == ["x(t)", "y(t)"] @test string.(parameters(prob.f.sys)) == ["pp"] @@ -963,7 +963,7 @@ let @parameters pp = -1 der = Differential(t) @named sys4 = ODESystem([der(x) ~ -y; der(y) ~ 1 + pp * y + x], t) - sys4s = structural_simplify(sys4) + sys4s = mtkbuild(sys4) prob = ODEProblem(sys4s, [x => 1.0, D(x) => 1.0], (0, 1.0)) @test !isnothing(prob.f.sys) end @@ -999,7 +999,7 @@ let # Issue https://github.com/SciML/ModelingToolkit.jl/issues/2322 sys = ODESystem(eqs, t; name = :kjshdf) - sys_simp = structural_simplify(sys) + sys_simp = mtkbuild(sys) @test a ∈ keys(ModelingToolkit.defaults(sys_simp)) @@ -1140,7 +1140,7 @@ orig_vars = unknowns(sys) @named outer = ODESystem( [D(y) ~ sys.x + t, 0 ~ t + y - sys.x * y], t, [y], []; systems = [sys]) @test ModelingToolkit.guesses(outer)[sys.x] == 1.0 -outer = structural_simplify(outer) +outer = mtkbuild(outer) @test ModelingToolkit.get_guesses(outer)[sys.x] == 1.0 prob = ODEProblem(outer, [outer.y => 2.0], (0.0, 10.0)) int = init(prob, Rodas4()) @@ -1176,7 +1176,7 @@ end @testset "Non-1-indexed variable array (issue #2670)" begin @variables x(t)[0:1] # 0-indexed variable array @named sys = ODESystem([x[0] ~ 0.0, D(x[1]) ~ x[0]], t, [x], []) - @test_nowarn sys = structural_simplify(sys) + @test_nowarn sys = mtkbuild(sys) @test equations(sys) == [D(x[1]) ~ 0.0] end @@ -1190,7 +1190,7 @@ end @testset "ForwardDiff through ODEProblem constructor" begin @parameters P @variables x(t) - sys = structural_simplify(ODESystem([D(x) ~ P], t, [x], [P]; name = :sys)) + sys = mtkbuild(ODESystem([D(x) ~ P], t, [x], [P]; name = :sys)) function x_at_1(P) prob = ODEProblem(sys, [x => P], (0.0, 1.0), [sys.P => P]) @@ -1203,7 +1203,7 @@ end @testset "Inplace observed functions" begin @parameters P @variables x(t) - sys = structural_simplify(ODESystem([D(x) ~ P], t, [x], [P]; name = :sys)) + sys = mtkbuild(ODESystem([D(x) ~ P], t, [x], [P]; name = :sys)) obsfn = ModelingToolkit.build_explicit_observed_function( sys, [x + 1, x + P, x + t], return_inplace = true)[2] ps = ModelingToolkit.MTKParameters(sys, [P => 2.0]) @@ -1240,7 +1240,7 @@ end initialization_eqs = [x ~ T] guesses = [x => 0.0] @named sys2 = ODESystem(eqs, T; initialization_eqs, guesses) - prob2 = ODEProblem(structural_simplify(sys2), [], (1.0, 2.0), []) + prob2 = ODEProblem(mtkbuild(sys2), [], (1.0, 2.0), []) sol2 = solve(prob2) @test all(sol2[x] .== 1.0) end @@ -1265,7 +1265,7 @@ end eqs = [D(x) ~ 0, y ~ x, D(z) ~ 0] defaults = [x => 1, z => y] @named sys = ODESystem(eqs, t; defaults) - ssys = structural_simplify(sys) + ssys = mtkbuild(sys) prob = ODEProblem(ssys, [], (0.0, 1.0), []) @test prob[x] == prob[y] == prob[z] == 1.0 @@ -1274,7 +1274,7 @@ end eqs = [D(x) ~ 0, y ~ y0 / x, D(z) ~ y] defaults = [y0 => 1, x => 1, z => y] @named sys = ODESystem(eqs, t; defaults) - ssys = structural_simplify(sys) + ssys = mtkbuild(sys) prob = ODEProblem(ssys, [], (0.0, 1.0), []) @test prob[x] == prob[y] == prob[z] == 1.0 end @@ -1285,11 +1285,11 @@ end @named sys = ODESystem( [D(u) ~ (sum(u) + sum(x) + sum(p) + sum(o)) * x, o ~ prod(u) * x], t, [u..., x..., o...], [p...]) - sys1, = structural_simplify(sys, ([x...], [])) + sys1, = mtkbuild(sys, ([x...], [])) fn1, = ModelingToolkit.generate_function(sys1; expression = Val{false}) ps = MTKParameters(sys1, [x => 2ones(2), p => 3ones(2, 2)]) @test_nowarn fn1(ones(4), ps, 4.0) - sys2, = structural_simplify(sys, ([x...], []); split = false) + sys2, = mtkbuild(sys, ([x...], []); split = false) fn2, = ModelingToolkit.generate_function(sys2; expression = Val{false}) ps = zeros(8) setp(sys2, x)(ps, 2ones(2)) @@ -1373,10 +1373,10 @@ end @named outersys = ODESystem( [D(innersys.y) ~ innersys.y + p4], t; parameter_dependencies = [p4 ~ 3p3], defaults = [p3 => 3.0, p4 => 9.0], guesses = [p4 => 10.0], systems = [innersys]) - @test_nowarn structural_simplify(outersys) + @test_nowarn mtkbuild(outersys) @parameters p5 sys2 = substitute(outersys, [p4 => p5]) - @test_nowarn structural_simplify(sys2) + @test_nowarn mtkbuild(sys2) @test length(equations(sys2)) == 2 @test length(parameters(sys2)) == 2 @test length(full_parameters(sys2)) == 4 @@ -1398,7 +1398,7 @@ end o[2] ~ sum(p) * sum(x)] @named sys = ODESystem(eqs, t, [u..., x..., o], [p...]) - sys1, = structural_simplify(sys, ([x...], [o...]), split = false) + sys1, = mtkbuild(sys, ([x...], [o...]), split = false) @test_nowarn ModelingToolkit.build_explicit_observed_function(sys1, u; inputs = [x...]) @@ -1436,7 +1436,7 @@ end @test_nowarn ODEProblem(sys, [], (0.0, 1.0)) else @test_throws [ - r"array (equations|unknowns)", "structural_simplify", "scalarize"] ODEProblem( + r"array (equations|unknowns)", "mtkbuild", "scalarize"] ODEProblem( sys, [], (0.0, 1.0)) end end @@ -1449,7 +1449,7 @@ end @test_nowarn ODEProblem(sys, [], (0.0, 1.0)) else @test_throws [ - r"array (equations|unknowns)", "structural_simplify", "scalarize"] ODEProblem( + r"array (equations|unknowns)", "mtkbuild", "scalarize"] ODEProblem( sys, [], (0.0, 1.0)) end end @@ -1632,7 +1632,7 @@ end @test lowered_vars == expected_vars end -@testset "dae_order_lowering test with structural_simplify" begin +@testset "dae_order_lowering test with mtkbuild" begin @variables x(t) y(t) z(t) @parameters M b k eqs = [ @@ -1647,7 +1647,7 @@ end default_p = [M => 1.0, b => 1.0, k => 1.0] @named dae_sys = ODESystem(eqs, t, [x, y, z], ps; defaults = [default_u0; default_p]) - simplified_dae_sys = structural_simplify(dae_sys) + simplified_dae_sys = mtkbuild(dae_sys) lowered_dae_sys = dae_order_lowering(simplified_dae_sys) lowered_dae_sys = complete(lowered_dae_sys) diff --git a/test/optimizationsystem.jl b/test/optimizationsystem.jl index 2ec9516721..9d6053a45b 100644 --- a/test/optimizationsystem.jl +++ b/test/optimizationsystem.jl @@ -85,7 +85,7 @@ end z ~ y - x^2 z^2 + y^2 ≲ 1.0] @named sys = OptimizationSystem(loss, [x, y, z], [a, b], constraints = cons) - sys = structural_simplify(sys) + sys = mtkbuild(sys) prob = OptimizationProblem(sys, [x => 0.0, y => 0.0, z => 0.0], [a => 1.0, b => 1.0], grad = true, hess = true, cons_j = true, cons_h = true) sol = solve(prob, IPNewton()) diff --git a/test/parameter_dependencies.jl b/test/parameter_dependencies.jl index 31881e1ca8..beffacc83f 100644 --- a/test/parameter_dependencies.jl +++ b/test/parameter_dependencies.jl @@ -170,7 +170,7 @@ end # (https://github.com/SciML/ModelingToolkit.jl/pull/2978) @inferred ModelingToolkit.parameter_dependencies(sys1) - sys = structural_simplify(sys1) + sys = mtkbuild(sys1) prob = ODEProblem(sys, [], (0.0, 1.0)) sol = solve(prob) @@ -192,7 +192,7 @@ end eqs = [D(y) ~ i(t) + p] @named model = ODESystem(eqs, t, [y], [p, i]; parameter_dependencies = [i ~ CallableFoo(p)]) - sys = structural_simplify(model) + sys = mtkbuild(model) prob = ODEProblem(sys, [], (0.0, 1.0)) sol = solve(prob, Tsit5()) diff --git a/test/reduction.jl b/test/reduction.jl index fa9029a652..1f0028fc6e 100644 --- a/test/reduction.jl +++ b/test/reduction.jl @@ -30,7 +30,7 @@ eqs = [D(x) ~ σ * (y - x) lorenz1 = ODESystem(eqs, t, name = :lorenz1) -lorenz1_aliased = structural_simplify(lorenz1) +lorenz1_aliased = mtkbuild(lorenz1) io = IOBuffer(); show(io, MIME("text/plain"), lorenz1_aliased); str = String(take!(io)); @@ -74,8 +74,8 @@ __x = x # Reduced Flattened System -reduced_system = structural_simplify(connected) -reduced_system2 = structural_simplify(tearing_substitution(structural_simplify(tearing_substitution(structural_simplify(connected))))) +reduced_system = mtkbuild(connected) +reduced_system2 = mtkbuild(tearing_substitution(mtkbuild(tearing_substitution(mtkbuild(connected))))) @test isempty(setdiff(unknowns(reduced_system), unknowns(reduced_system2))) @test isequal(equations(tearing_substitution(reduced_system)), equations(reduced_system2)) @@ -133,7 +133,7 @@ let pc.y_c ~ ol.y] @named connected = ODESystem(connections, t, systems = [ol, pc]) @test equations(connected) isa Vector{Equation} - reduced_sys = structural_simplify(connected) + reduced_sys = mtkbuild(connected) ref_eqs = [D(ol.x) ~ ol.a * ol.x + ol.b * ol.u 0 ~ pc.k_P * ol.y - ol.u] #@test ref_eqs == equations(reduced_sys) @@ -144,7 +144,7 @@ let @variables x(t) @named sys = ODESystem([0 ~ D(x) + x], t, [x], []) #@test_throws ModelingToolkit.InvalidSystemException ODEProblem(sys, [1.0], (0, 10.0)) - sys = structural_simplify(sys) + sys = mtkbuild(sys) #@test_nowarn ODEProblem(sys, [1.0], (0, 10.0)) end @@ -155,7 +155,7 @@ eqs = [u1 ~ u2 u3 ~ u1 + u2 + p u3 ~ hypot(u1, u2) * p] @named sys = NonlinearSystem(eqs, [u1, u2, u3], [p]) -reducedsys = structural_simplify(sys) +reducedsys = mtkbuild(sys) @test length(observed(reducedsys)) == 2 u0 = [u2 => 1] @@ -175,7 +175,7 @@ N = 5 A = reshape(1:(N^2), N, N) eqs = xs ~ A * xs @named sys′ = NonlinearSystem(eqs, [xs], []) -sys = structural_simplify(sys′) +sys = mtkbuild(sys′) @test length(equations(sys)) == 3 && length(observed(sys)) == 2 # issue 958 @@ -189,7 +189,7 @@ eqs = [D(E) ~ k₋₁ * C - k₁ * E * S E₀ ~ E + C] @named sys = ODESystem(eqs, t, [E, C, S, P], [k₁, k₂, k₋₁, E₀]) -@test_throws ModelingToolkit.ExtraEquationsSystemException structural_simplify(sys) +@test_throws ModelingToolkit.ExtraEquationsSystemException mtkbuild(sys) # Example 5 from Pantelides' original paper params = collect(@parameters y1(t) y2(t)) @@ -198,7 +198,7 @@ eqs = [0 ~ x + sin(u1 + u2) D(x) ~ x + y1 cos(x) ~ sin(y2)] @named sys = ODESystem(eqs, t, sts, params) -@test_throws ModelingToolkit.InvalidSystemException structural_simplify(sys) +@test_throws ModelingToolkit.InvalidSystemException mtkbuild(sys) # issue #963 @variables v47(t) v57(t) v66(t) v25(t) i74(t) i75(t) i64(t) i71(t) v1(t) v2(t) @@ -215,7 +215,7 @@ eq = [v47 ~ v1 0 ~ i64 + i71] @named sys0 = ODESystem(eq, t) -sys = structural_simplify(sys0) +sys = mtkbuild(sys0) @test length(equations(sys)) == 1 eq = equations(tearing_substitution(sys))[1] vv = only(unknowns(sys)) @@ -233,7 +233,7 @@ eqs = [D(x) ~ σ * (y - x) u ~ z + a] lorenz1 = ODESystem(eqs, t, name = :lorenz1) -lorenz1_reduced, _ = structural_simplify(lorenz1, ([z], [])) +lorenz1_reduced, _ = mtkbuild(lorenz1, ([z], [])) @test z in Set(parameters(lorenz1_reduced)) # #2064 @@ -242,7 +242,7 @@ eqs = [D(x) ~ x D(y) ~ y D(z) ~ t] @named model = ODESystem(eqs, t) -sys = structural_simplify(model) +sys = mtkbuild(model) Js = ModelingToolkit.jacobian_sparsity(sys) @test size(Js) == (3, 3) @test Js == Diagonal([1, 1, 0]) @@ -275,7 +275,7 @@ new_sys = alias_elimination(sys) eqs = [x ~ 0 D(x) ~ x + y] @named sys = ODESystem(eqs, t, [x, y], []) -ss = structural_simplify(sys) +ss = mtkbuild(sys) @test isempty(equations(ss)) @test sort(string.(observed(ss))) == ["x(t) ~ 0.0" "xˍt(t) ~ 0.0" @@ -285,5 +285,5 @@ eqs = [D(D(x)) ~ -x] @named sys = ODESystem(eqs, t, [x], []) ss = alias_elimination(sys) @test length(equations(ss)) == length(unknowns(ss)) == 1 -ss = structural_simplify(sys) +ss = mtkbuild(sys) @test length(equations(ss)) == length(unknowns(ss)) == 2 diff --git a/test/scc_nonlinear_problem.jl b/test/scc_nonlinear_problem.jl index b2b326d090..ecd5af5f8a 100644 --- a/test/scc_nonlinear_problem.jl +++ b/test/scc_nonlinear_problem.jl @@ -22,9 +22,9 @@ using ModelingToolkit: t_nounits as t, D_nounits as D eqs = 0 .~ eqs @named model = NonlinearSystem(eqs) @test_throws ["simplified", "required"] SCCNonlinearProblem(model, []) - _model = structural_simplify(model; split = false) + _model = mtkbuild(model; split = false) @test_throws ["not compatible"] SCCNonlinearProblem(_model, []) - model = structural_simplify(model) + model = mtkbuild(model) prob = NonlinearProblem(model, [u => zeros(8)]) sccprob = SCCNonlinearProblem(model, [u => zeros(8)]) sol1 = solve(prob, NewtonRaphson()) diff --git a/test/sciml_problem_inputs.jl b/test/sciml_problem_inputs.jl index bfa560cda3..671cf8832e 100644 --- a/test/sciml_problem_inputs.jl +++ b/test/sciml_problem_inputs.jl @@ -37,7 +37,7 @@ begin MassActionJump(k2, [Z => 1], [Y => 1, Z => -1]) ] - # Create systems (without structural_simplify, since that might modify systems to affect intended tests). + # Create systems (without mtkbuild, since that might modify systems to affect intended tests). osys = complete(ODESystem(diff_eqs, t; name = :osys)) ssys = complete(SDESystem( diff_eqs, noise_eqs, t, [X, Y, Z], [kp, kd, k1, k2]; name = :ssys)) diff --git a/test/sdesystem.jl b/test/sdesystem.jl index b031a2f5ab..05d424efba 100644 --- a/test/sdesystem.jl +++ b/test/sdesystem.jl @@ -596,7 +596,7 @@ eqs = [D(x) ~ σ * (y - x) + x * β, D(y) ~ x * (ρ - z) - y + y * β + x * η, D(z) ~ x * y - β * z + (x * z) * β] @named sys1 = System(eqs, tt) -sys1 = structural_simplify(sys1) +sys1 = mtkbuild(sys1) drift_eqs = [D(x) ~ σ * (y - x), D(y) ~ x * (ρ - z) - y, @@ -794,13 +794,13 @@ end input ~ 0.0] sys = System(eqs, t, sts, ps; name = :name) - sys = structural_simplify(sys) + sys = mtkbuild(sys) @test ModelingToolkit.get_noiseeqs(sys) ≈ [1.0] prob = SDEProblem(sys, [], (0.0, 1.0), []) @test_nowarn solve(prob, RKMil()) end -@testset "Observed variables retained after `structural_simplify`" begin +@testset "Observed variables retained after `mtkbuild`" begin @variables x(t) y(t) z(t) @brownian a @mtkbuild sys = System([D(x) ~ x + a, D(y) ~ y + a, z ~ x + y], t) @@ -859,7 +859,7 @@ end end end -@testset "`structural_simplify(::SDESystem)`" begin +@testset "`mtkbuild(::SDESystem)`" begin @variables x(t) y(t) @mtkbuild sys = SDESystem( [D(x) ~ x, y ~ 2x], [x, 0], t, [x, y], []; is_scalar_noise = true) @@ -947,7 +947,7 @@ end @test ssys1 !== ssys2 end -@testset "Error when constructing SDESystem without `structural_simplify`" begin +@testset "Error when constructing SDESystem without `mtkbuild`" begin @parameters σ ρ β @variables x(tt) y(tt) z(tt) @brownian a @@ -961,8 +961,8 @@ end u0map = [x => 1.0, y => 0.0, z => 0.0] parammap = [σ => 10.0, β => 26.0, ρ => 2.33] - @test_throws ErrorException("SDESystem constructed by defining Brownian variables with @brownian must be simplified by calling `structural_simplify` before a SDEProblem can be constructed.") SDEProblem( + @test_throws ErrorException("SDESystem constructed by defining Brownian variables with @brownian must be simplified by calling `mtkbuild` before a SDEProblem can be constructed.") SDEProblem( de, u0map, (0.0, 100.0), parammap) - de = structural_simplify(de) + de = mtkbuild(de) @test SDEProblem(de, u0map, (0.0, 100.0), parammap) isa SDEProblem end diff --git a/test/split_parameters.jl b/test/split_parameters.jl index 18fdb49a48..74286e2843 100644 --- a/test/split_parameters.jl +++ b/test/split_parameters.jl @@ -82,7 +82,7 @@ eqs = [y ~ src.output.u @named sys = ODESystem(eqs, t, vars, []; systems = [int, src]) s = complete(sys) -sys = structural_simplify(sys) +sys = mtkbuild(sys) prob = ODEProblem( sys, [], (0.0, t_end), [s.src.interpolator => Interpolator(x, dt)]; tofloat = false) @@ -108,7 +108,7 @@ eqs = [D(y) ~ dy * a ddy ~ sin(t) * c] @named model = ODESystem(eqs, t, vars, pars) -sys = structural_simplify(model; split = false) +sys = mtkbuild(model; split = false) tspan = (0.0, t_end) prob = ODEProblem(sys, [], tspan, []; build_initializeprob = false) diff --git a/test/state_selection.jl b/test/state_selection.jl index a8d3e57773..f0b5a89e59 100644 --- a/test/state_selection.jl +++ b/test/state_selection.jl @@ -118,7 +118,7 @@ let @named system = System(L = 10) @unpack supply_pipe, return_pipe = system - sys = structural_simplify(system) + sys = mtkbuild(system) u0 = [ sys.supply_pipe.v => 0.1, sys.return_pipe.v => 0.1, D(supply_pipe.v) => 0.0, D(return_pipe.fluid_port_a.m) => 0.0, @@ -169,7 +169,7 @@ let @named trans = ODESystem(eqs, t) - sys = structural_simplify(trans) + sys = mtkbuild(trans) n = 3 u = 0 * ones(n) @@ -274,7 +274,7 @@ let # solution ------------------------------------------------------------------- @named catapult = ODESystem(eqs, t, vars, params, defaults = defs) - sys = structural_simplify(catapult) + sys = mtkbuild(catapult) prob = ODEProblem(sys, [], (0.0, 0.1), [l_2f => 0.55, damp => 1e7]; jac = true) @test solve(prob, Rodas4()).retcode == ReturnCode.Success end diff --git a/test/static_arrays.jl b/test/static_arrays.jl index 61177e5ab2..e7807694a4 100644 --- a/test/static_arrays.jl +++ b/test/static_arrays.jl @@ -9,7 +9,7 @@ eqs = [D(D(x)) ~ σ * (y - x), D(z) ~ x * y - β * z] @named sys = ODESystem(eqs, t) -sys = structural_simplify(sys) +sys = mtkbuild(sys) u0 = @SVector [D(x) => 2.0, x => 1.0, diff --git a/test/stream_connectors.jl b/test/stream_connectors.jl index 834ebce1a7..e76a9fa4b4 100644 --- a/test/stream_connectors.jl +++ b/test/stream_connectors.jl @@ -134,7 +134,7 @@ eqns = [domain_connect(fluid, n1m1.port_a) @named n1m1Test = ODESystem(eqns, t, [], []; systems = [fluid, n1m1, pipe, sink]) -@test_nowarn structural_simplify(n1m1Test) +@test_nowarn mtkbuild(n1m1Test) @unpack source, port_a = n1m1 ssort(eqs) = sort(eqs, by = string) @test ssort(equations(expand_connections(n1m1))) == ssort([0 ~ port_a.m_flow @@ -205,7 +205,7 @@ eqns = [connect(n1m2.port_a, sink1.port) @named sys = ODESystem(eqns, t) @named n1m2Test = compose(sys, n1m2, sink1, sink2) -@test_nowarn structural_simplify(n1m2Test) +@test_nowarn mtkbuild(n1m2Test) @named n1m2 = N1M2() @named pipe1 = AdiabaticStraightPipe() @@ -220,7 +220,7 @@ eqns = [connect(n1m2.port_a, pipe1.port_a) @named sys = ODESystem(eqns, t) @named n1m2AltTest = compose(sys, n1m2, pipe1, pipe2, sink1, sink2) -@test_nowarn structural_simplify(n1m2AltTest) +@test_nowarn mtkbuild(n1m2AltTest) # N2M2 model and test code. function N2M2(; name, @@ -249,7 +249,7 @@ eqns = [connect(source.port, n2m2.port_a) @named sys = ODESystem(eqns, t) @named n2m2Test = compose(sys, n2m2, source, sink) -@test_nowarn structural_simplify(n2m2Test) +@test_nowarn mtkbuild(n2m2Test) # stream var @named sp1 = TwoPhaseFluidPort() @@ -472,7 +472,7 @@ csys = complete(two_fluid_system) @test Symbol(sys_defs[csys.volume_a.H.rho]) == Symbol(csys.fluid_a.rho) @test Symbol(sys_defs[csys.volume_b.H.rho]) == Symbol(csys.fluid_b.rho) -@test_nowarn structural_simplify(two_fluid_system) +@test_nowarn mtkbuild(two_fluid_system) function OneFluidSystem(; name) pars = [] @@ -510,4 +510,4 @@ csys = complete(one_fluid_system) @test Symbol(sys_defs[csys.volume_a.H.rho]) == Symbol(csys.fluid.rho) @test Symbol(sys_defs[csys.volume_b.H.rho]) == Symbol(csys.fluid.rho) -@test_nowarn structural_simplify(one_fluid_system) +@test_nowarn mtkbuild(one_fluid_system) diff --git a/test/structural_transformation/index_reduction.jl b/test/structural_transformation/index_reduction.jl index f9d6037022..a3634e7a28 100644 --- a/test/structural_transformation/index_reduction.jl +++ b/test/structural_transformation/index_reduction.jl @@ -133,7 +133,7 @@ let pss_pendulum = partial_state_selection(pendulum) @test_broken length(equations(pss_pendulum)) == 3 end -let sys = structural_simplify(pendulum2) +let sys = mtkbuild(pendulum2) @test length(equations(sys)) == 5 @test length(unknowns(sys)) == 5 @@ -160,7 +160,7 @@ let D(D(y)) ~ λ * y - g x^2 + y^2 ~ 1] @named pend = ODESystem(eqs, t) - sys = complete(structural_simplify(pend; dummy_derivative = false)) + sys = complete(mtkbuild(pend; dummy_derivative = false)) prob = ODEProblem( sys, [x => 1, y => 0, D(x) => 0.0], (0.0, 10.0), [g => 1], guesses = [λ => 0.0]) sol = solve(prob, Rodas5P()) diff --git a/test/structural_transformation/tearing.jl b/test/structural_transformation/tearing.jl index e9cd92ec94..9e83c117e7 100644 --- a/test/structural_transformation/tearing.jl +++ b/test/structural_transformation/tearing.jl @@ -148,7 +148,7 @@ eqs = [D(x) ~ z * h 0 ~ x - y 0 ~ sin(z) + y - p * t] @named daesys = ODESystem(eqs, t) -newdaesys = structural_simplify(daesys) +newdaesys = mtkbuild(daesys) @test equations(newdaesys) == [D(x) ~ z; 0 ~ y + sin(z) - p * t] @test equations(tearing_substitution(newdaesys)) == [D(x) ~ z; 0 ~ x + sin(z) - p * t] @test isequal(unknowns(newdaesys), [x, z]) @@ -165,7 +165,7 @@ prob.f(du, u, pr, tt) # test the initial guess is respected @named sys = ODESystem(eqs, t, defaults = Dict(z => NaN)) -infprob = ODEProblem(structural_simplify(sys), [x => 1.0], (0, 1.0), [p => 0.2]) +infprob = ODEProblem(mtkbuild(sys), [x => 1.0], (0, 1.0), [p => 0.2]) infprob.f(du, infprob.u0, pr, tt) @test any(isnan, du) @@ -196,7 +196,7 @@ calculate_tgrad(ms_model) u0 = [mass.s => 0.0 mass.v => 1.0] -sys = structural_simplify(ms_model) +sys = mtkbuild(ms_model) @test ModelingToolkit.get_jac(sys)[] === ModelingToolkit.EMPTY_JAC @test ModelingToolkit.get_tgrad(sys)[] === ModelingToolkit.EMPTY_TGRAD prob_complex = ODEProblem(sys, u0, (0, 1.0)) diff --git a/test/structural_transformation/utils.jl b/test/structural_transformation/utils.jl index b5335ad6b1..347756caba 100644 --- a/test/structural_transformation/utils.jl +++ b/test/structural_transformation/utils.jl @@ -122,14 +122,14 @@ end @named sys = ODESystem( [D(x) ~ z[1] + z[2] + foo(z)[1], y[1] ~ 2t, y[2] ~ 3t, z ~ foo(y)], t) - sys1 = structural_simplify(sys; cse_hack = false) + sys1 = mtkbuild(sys; cse_hack = false) @test length(observed(sys1)) == 6 @test !any(observed(sys1)) do eq iscall(eq.rhs) && operation(eq.rhs) == StructuralTransformations.getindex_wrapper end - sys2 = structural_simplify(sys; array_hack = false) + sys2 = mtkbuild(sys; array_hack = false) @test length(observed(sys2)) == 5 @test !any(observed(sys2)) do eq iscall(eq.rhs) && operation(eq.rhs) == StructuralTransformations.change_origin @@ -143,14 +143,14 @@ end @named sys = ODESystem( [D(x) ~ z[1] + z[2] + foo(z)[1] + w, y[1] ~ 2t, y[2] ~ 3t, z ~ foo(y)], t) - sys1 = structural_simplify(sys; cse_hack = false, fully_determined = false) + sys1 = mtkbuild(sys; cse_hack = false, fully_determined = false) @test length(observed(sys1)) == 6 @test !any(observed(sys1)) do eq iscall(eq.rhs) && operation(eq.rhs) == StructuralTransformations.getindex_wrapper end - sys2 = structural_simplify(sys; array_hack = false, fully_determined = false) + sys2 = mtkbuild(sys; array_hack = false, fully_determined = false) @test length(observed(sys2)) == 5 @test !any(observed(sys2)) do eq iscall(eq.rhs) && operation(eq.rhs) == StructuralTransformations.change_origin @@ -163,7 +163,7 @@ end @named sys = ODESystem([D(x) ~ x, y ~ x + t], t) value = Ref(0) pass(sys; kwargs...) = (value[] += 1; return sys) - structural_simplify(sys; additional_passes = [pass]) + mtkbuild(sys; additional_passes = [pass]) @test value[] == 1 end diff --git a/test/substitute_component.jl b/test/substitute_component.jl index 9fb254136b..ad458d34cd 100644 --- a/test/substitute_component.jl +++ b/test/substitute_component.jl @@ -59,8 +59,8 @@ end @named reference = RC() - sys1 = structural_simplify(rcsys) - sys2 = structural_simplify(reference) + sys1 = mtkbuild(rcsys) + sys2 = mtkbuild(reference) @test isequal(unknowns(sys1), unknowns(sys2)) @test isequal(equations(sys1), equations(sys2)) diff --git a/test/symbolic_events.jl b/test/symbolic_events.jl index 9099d32d14..cc576ce0be 100644 --- a/test/symbolic_events.jl +++ b/test/symbolic_events.jl @@ -445,7 +445,7 @@ affect = [v ~ -v] @test getfield(ball, :continuous_events)[] == SymbolicContinuousCallback(Equation[x ~ 0], Equation[v ~ -v]) -ball = structural_simplify(ball) +ball = mtkbuild(ball) @test length(ModelingToolkit.continuous_events(ball)) == 1 @@ -468,8 +468,8 @@ continuous_events = [[x ~ 0] => [vx ~ -vx] D(vy) ~ -0.01vy], t; continuous_events) _ball = ball -ball = structural_simplify(_ball) -ball_nosplit = structural_simplify(_ball; split = false) +ball = mtkbuild(_ball) +ball_nosplit = mtkbuild(_ball; split = false) tspan = (0.0, 5.0) prob = ODEProblem(ball, Pair[], tspan) @@ -511,8 +511,8 @@ continuous_events = [ D(vx) ~ -1 D(vy) ~ 0], t; continuous_events) -ball_nosplit = structural_simplify(ball) -ball = structural_simplify(ball) +ball_nosplit = mtkbuild(ball) +ball = mtkbuild(ball) tspan = (0.0, 5.0) prob = ODEProblem(ball, Pair[], tspan) @@ -537,7 +537,7 @@ eq = [vs ~ sin(2pi * t) D(vmeasured) ~ 0.0] ev = [sin(20pi * t) ~ 0.0] => [vmeasured ~ v] @named sys = ODESystem(eq, t, continuous_events = ev) -sys = structural_simplify(sys) +sys = mtkbuild(sys) prob = ODEProblem(sys, zeros(2), (0.0, 5.1)) sol = solve(prob, Tsit5()) @test all(minimum((0:0.05:5) .- sol.t', dims = 2) .< 0.0001) # test that the solver stepped every 0.05s as dictated by event @@ -584,7 +584,7 @@ function Model(u, d = 0) @named model = compose(_model, mass1, mass2, sd) end model = Model(sin(30t)) -sys = structural_simplify(model) +sys = mtkbuild(model) @test isempty(ModelingToolkit.continuous_events(sys)) let @@ -823,7 +823,7 @@ let eqs = [oscce.F ~ 0] @named eqs_sys = ODESystem(eqs, t) @named oneosc_ce = compose(eqs_sys, oscce) - oneosc_ce_simpl = structural_simplify(oneosc_ce) + oneosc_ce_simpl = mtkbuild(oneosc_ce) prob = ODEProblem(oneosc_ce_simpl, [], (0.0, 2.0), []) sol = solve(prob, Tsit5(), saveat = 0.1) @@ -845,7 +845,7 @@ end evt2 = ModelingToolkit.SymbolicContinuousCallback( [c2 ~ 0], (record_crossings, [c2 => :v], [], [], cr2)) @named trigsys = ODESystem(eqs, t; continuous_events = [evt1, evt2]) - trigsys_ss = structural_simplify(trigsys) + trigsys_ss = mtkbuild(trigsys) prob = ODEProblem(trigsys_ss, [], (0.0, 2π)) sol = solve(prob, Tsit5()) required_crossings_c1 = [π / 2, 3 * π / 2] @@ -867,7 +867,7 @@ end [c2 ~ 0], (record_crossings, [c2 => :v], [], [], cr2p); affect_neg = (record_crossings, [c2 => :v], [], [], cr2n)) @named trigsys = ODESystem(eqs, t; continuous_events = [evt1, evt2]) - trigsys_ss = structural_simplify(trigsys) + trigsys_ss = mtkbuild(trigsys) prob = ODEProblem(trigsys_ss, [], (0.0, 2π)) sol = solve(prob, Tsit5(); dtmax = 0.01) c1_pc = filter((<=)(0) ∘ sin, required_crossings_c1) @@ -891,7 +891,7 @@ end evt2 = ModelingToolkit.SymbolicContinuousCallback( [c2 ~ 0], (record_crossings, [c2 => :v], [], [], cr2p); affect_neg = nothing) @named trigsys = ODESystem(eqs, t; continuous_events = [evt1, evt2]) - trigsys_ss = structural_simplify(trigsys) + trigsys_ss = mtkbuild(trigsys) prob = ODEProblem(trigsys_ss, [], (0.0, 2π)) sol = solve(prob, Tsit5(); dtmax = 0.01) @test maximum(abs.(c1_pc .- first.(cr1p))) < 1e-5 @@ -910,7 +910,7 @@ end [c2 ~ 0], (record_crossings, [c2 => :v], [], [], cr2p); affect_neg = (record_crossings, [c2 => :v], [], [], cr2n)) @named trigsys = ODESystem(eqs, t; continuous_events = [evt1, evt2]) - trigsys_ss = structural_simplify(trigsys) + trigsys_ss = mtkbuild(trigsys) prob = ODEProblem(trigsys_ss, [], (0.0, 2π)) sol = solve(prob, Tsit5(); dtmax = 0.01) c1_pc = filter((<=)(0) ∘ sin, required_crossings_c1) @@ -934,7 +934,7 @@ end [c2 ~ 0], (record_crossings, [c2 => :v], [], [], cr2); rootfind = SciMLBase.RightRootFind) @named trigsys = ODESystem(eqs, t; continuous_events = [evt1, evt2]) - trigsys_ss = structural_simplify(trigsys) + trigsys_ss = mtkbuild(trigsys) prob = ODEProblem(trigsys_ss, [], (0.0, 2π)) sol = solve(prob, Tsit5(); dtmax = 0.01) required_crossings_c1 = [π / 2, 3 * π / 2] @@ -954,7 +954,7 @@ end [c2 ~ 0], (record_crossings, [c2 => :v], [], [], cr2); rootfind = SciMLBase.RightRootFind) @named trigsys = ODESystem(eqs, t; continuous_events = [evt1, evt2]) - trigsys_ss = structural_simplify(trigsys) + trigsys_ss = mtkbuild(trigsys) prob = ODEProblem(trigsys_ss, [], (0.0, 2π)) sol = solve(prob, Tsit5()) @test maximum(abs.(first.(cr1) .- required_crossings_c1)) < 1e-4 @@ -972,7 +972,7 @@ end [c2 ~ 0], (record_crossings, [c2 => :v], [], [], cr2); rootfind = SciMLBase.RightRootFind) @named trigsys = ODESystem(eqs, t; continuous_events = [evt2, evt1]) - trigsys_ss = structural_simplify(trigsys) + trigsys_ss = mtkbuild(trigsys) prob = ODEProblem(trigsys_ss, [], (0.0, 2π)) sol = solve(prob, Tsit5()) @test maximum(abs.(first.(cr1) .- required_crossings_c1)) < 1e-4 @@ -1102,7 +1102,7 @@ end end) @named sys = ODESystem( eqs, t, [temp], params; continuous_events = [furnace_off, furnace_enable]) - ss = structural_simplify(sys) + ss = mtkbuild(sys) prob = ODEProblem(ss, [temp => 0.0, furnace_on => true], (0.0, 100.0)) sol = solve(prob, Tsit5(); dtmax = 0.01) @test all(sol[temp][sol.t .> 1.0] .<= 0.79) && all(sol[temp][sol.t .> 1.0] .>= 0.49) @@ -1122,7 +1122,7 @@ end end) @named sys = ODESystem( eqs, t, [temp], params; continuous_events = [furnace_off, furnace_enable]) - ss = structural_simplify(sys) + ss = mtkbuild(sys) prob = ODEProblem(ss, [temp => 0.0, furnace_on => true], (0.0, 100.0)) sol = solve(prob, Tsit5(); dtmax = 0.01) @test all(sol[temp][sol.t .> 1.0] .<= 0.79) && all(sol[temp][sol.t .> 1.0] .>= 0.49) @@ -1143,7 +1143,7 @@ end @set! x.furnace_on = false end) @named sys = ODESystem(eqs, t, [temp], params; continuous_events = [furnace_off]) - ss = structural_simplify(sys) + ss = mtkbuild(sys) @test_logs (:warn, "The symbols Any[:furnace_on] are declared as both observed and modified; this is a code smell because it becomes easy to confuse them and assign/not assign a value.") prob=ODEProblem( ss, [temp => 0.0, furnace_on => true], (0.0, 100.0)) @@ -1160,7 +1160,7 @@ end end) @named sys = ODESystem( eqs, t, [temp, tempsq], params; continuous_events = [furnace_off]) - ss = structural_simplify(sys) + ss = mtkbuild(sys) @test_throws "refers to missing variable(s)" prob=ODEProblem( ss, [temp => 0.0, furnace_on => true], (0.0, 100.0)) @@ -1173,7 +1173,7 @@ end end) @named sys = ODESystem( eqs, t, [temp, tempsq], params; continuous_events = [furnace_off]) - ss = structural_simplify(sys) + ss = mtkbuild(sys) @test_throws "refers to missing variable(s)" prob=ODEProblem( ss, [temp => 0.0, furnace_on => true], (0.0, 100.0)) @@ -1185,7 +1185,7 @@ end end) @named sys = ODESystem( eqs, t, [temp, tempsq], params; continuous_events = [furnace_off]) - ss = structural_simplify(sys) + ss = mtkbuild(sys) prob = ODEProblem( ss, [temp => 0.0, furnace_on => true], (0.0, 100.0)) @test_throws "Tried to write back to" solve(prob, Tsit5()) @@ -1245,7 +1245,7 @@ end end; rootfind = SciMLBase.RightRootFind) @named sys = ODESystem( eqs, t, [theta, omega], params; continuous_events = [qAevt, qBevt]) - ss = structural_simplify(sys) + ss = mtkbuild(sys) prob = ODEProblem(ss, [theta => 1e-5], (0.0, pi)) sol = solve(prob, Tsit5(); dtmax = 0.01) @test getp(sol, cnt)(sol) == 198 # we get 2 pulses per phase cycle (cos 0 crossing) and we go to 100 cycles; we miss a few due to the initial state @@ -1415,13 +1415,13 @@ end @named wd1 = weird1(0.021) @named wd2 = weird2(0.021) - sys1 = structural_simplify(ODESystem([], t; name = :parent, + sys1 = mtkbuild(ODESystem([], t; name = :parent, discrete_events = [0.01 => ModelingToolkit.ImperativeAffect( modified = (; θs = reduce(vcat, [[wd1.θ]])), ctx = [1]) do m, o, c, i @set! m.θs[1] = c[] += 1 end], systems = [wd1])) - sys2 = structural_simplify(ODESystem([], t; name = :parent, + sys2 = mtkbuild(ODESystem([], t; name = :parent, discrete_events = [0.01 => ModelingToolkit.ImperativeAffect( modified = (; θs = reduce(vcat, [[wd2.θ]])), ctx = [1]) do m, o, c, i @set! m.θs[1] = c[] += 1 diff --git a/test/symbolic_indexing_interface.jl b/test/symbolic_indexing_interface.jl index 8b3da5fd72..39ea2e84c2 100644 --- a/test/symbolic_indexing_interface.jl +++ b/test/symbolic_indexing_interface.jl @@ -220,7 +220,7 @@ end @variables x(t) y(t) z(t) @parameters a @named sys = ODESystem([D(x) ~ a * x, y ~ 2x, z ~ 0.0], t) - sys = structural_simplify(sys, split = false) + sys = mtkbuild(sys, split = false) for sym in [x, y, z, x + y, x + a, y / x] @test only(get_all_timeseries_indexes(sys, sym)) == ContinuousTimeseries() end diff --git a/test/units.jl b/test/units.jl index ff0cd42ac3..d9a29547a9 100644 --- a/test/units.jl +++ b/test/units.jl @@ -140,24 +140,24 @@ D = Differential(t) eqs = [D(L) ~ v, V ~ L^3] @named sys = ODESystem(eqs, t) -sys_simple = structural_simplify(sys) +sys_simple = mtkbuild(sys) eqs = [D(V) ~ r, V ~ L^3] @named sys = ODESystem(eqs, t) -sys_simple = structural_simplify(sys) +sys_simple = mtkbuild(sys) @variables V [unit = u"m"^3] L [unit = u"m"] @parameters v [unit = u"m/s"] r [unit = u"m"^3 / u"s"] t [unit = u"s"] eqs = [V ~ r * t, V ~ L^3] @named sys = NonlinearSystem(eqs, [V, L], [t, r]) -sys_simple = structural_simplify(sys) +sys_simple = mtkbuild(sys) eqs = [L ~ v * t, V ~ L^3] @named sys = NonlinearSystem(eqs, [V, L], [t, r]) -sys_simple = structural_simplify(sys) +sys_simple = mtkbuild(sys) #Jump System @parameters β [unit = u"(mol^2*s)^-1"] γ [unit = u"(mol*s)^-1"] t [unit = u"s"] jumpmol [ diff --git a/test/variable_scope.jl b/test/variable_scope.jl index 59647bf441..1df75c0bf1 100644 --- a/test/variable_scope.jl +++ b/test/variable_scope.jl @@ -125,7 +125,7 @@ defs = ModelingToolkit.defaults(bar) sys4 = complete(sys3) @test length(unknowns(sys4)) == 3 @test length(parameters(sys4)) == 4 - sys5 = structural_simplify(sys3) + sys5 = mtkbuild(sys3) @test length(unknowns(sys5)) == 4 @test any(isequal(x4), unknowns(sys5)) @test length(parameters(sys5)) == 4 From 8cbbb16deb0280116ad36bfd2602b54b65ccde22 Mon Sep 17 00:00:00 2001 From: vyudu Date: Mon, 21 Apr 2025 23:44:47 -0400 Subject: [PATCH 013/122] fix: fix linearization tests --- src/inputoutput.jl | 16 +++++++--------- src/linearization.jl | 6 +++--- src/systems/analysis_points.jl | 4 ++++ src/systems/diffeqs/odesystem.jl | 10 +++++----- src/systems/systems.jl | 8 ++++---- src/systems/systemstructure.jl | 12 ++++++------ test/downstream/linearize.jl | 32 +++++++++++++++++--------------- 7 files changed, 46 insertions(+), 42 deletions(-) diff --git a/src/inputoutput.jl b/src/inputoutput.jl index 5030b7d0eb..89d27cb729 100644 --- a/src/inputoutput.jl +++ b/src/inputoutput.jl @@ -163,7 +163,7 @@ has_var(ex, x) = x ∈ Set(get_variables(ex)) (f_oop, f_ip), x_sym, p_sym, io_sys = generate_control_function( sys::AbstractODESystem, inputs = unbound_inputs(sys), - disturbance_inputs = nothing; + disturbance_inputs = Any[]; implicit_dae = false, simplify = false, ) @@ -289,7 +289,7 @@ function inputs_to_parameters!(state::TransformationState, inputsyms) push!(new_fullvars, v) end end - ninputs == 0 && return (state, 1:0) + ninputs == 0 && return state nvars = ndsts(graph) - ninputs new_graph = BipartiteGraph(nsrcs(graph), nvars, Val(false)) @@ -318,14 +318,13 @@ function inputs_to_parameters!(state::TransformationState, inputsyms) @set! sys.unknowns = setdiff(unknowns(sys), keys(input_to_parameters)) ps = parameters(sys) - if io !== nothing - inputs, = io + if inputsyms !== nothing # Change order of new parameters to correspond to user-provided order in argument `inputs` d = Dict{Any, Int}() for (i, inp) in enumerate(new_parameters) d[inp] = i end - permutation = [d[i] for i in inputs] + permutation = [d[i] for i in inputsyms] new_parameters = new_parameters[permutation] end @@ -334,8 +333,7 @@ function inputs_to_parameters!(state::TransformationState, inputsyms) @set! state.sys = sys @set! state.fullvars = new_fullvars @set! state.structure = structure - base_params = length(ps) - return state, (base_params + 1):(base_params + length(new_parameters)) # (1:length(new_parameters)) .+ base_params + return state end """ @@ -361,7 +359,7 @@ function get_disturbance_system(dist::DisturbanceModel{<:ODESystem}) end """ - (f_oop, f_ip), augmented_sys, dvs, p = add_input_disturbance(sys, dist::DisturbanceModel, inputs = nothing) + (f_oop, f_ip), augmented_sys, dvs, p = add_input_disturbance(sys, dist::DisturbanceModel, inputs = Any[]) Add a model of an unmeasured disturbance to `sys`. The disturbance model is an instance of [`DisturbanceModel`](@ref). @@ -410,7 +408,7 @@ model_outputs = [model.inertia1.w, model.inertia2.w, model.inertia1.phi, model.i `f_oop` will have an extra state corresponding to the integrator in the disturbance model. This state will not be affected by any input, but will affect the dynamics from where it enters, in this case it will affect additively from `model.torque.tau.u`. """ -function add_input_disturbance(sys, dist::DisturbanceModel, inputs = nothing; kwargs...) +function add_input_disturbance(sys, dist::DisturbanceModel, inputs = Any[]; kwargs...) t = get_iv(sys) @variables d(t)=0 [disturbance = true] @variables u(t)=0 [input = true] # New system input diff --git a/src/linearization.jl b/src/linearization.jl index 3bfe7eaef5..cbc538a881 100644 --- a/src/linearization.jl +++ b/src/linearization.jl @@ -120,7 +120,7 @@ function linearization_function(sys::AbstractSystem, inputs = unbound_inputs(sys end lin_fun = LinearizationFunction( - diff_idxs, alge_idxs, length(unknowns(sys)), + diff_idxs, alge_idxs, inputs, length(unknowns(sys)), prob, h, u0 === nothing ? nothing : similar(u0), uf_jac, h_jac, pf_jac, hp_jac, initializealg, initialization_kwargs) return lin_fun @@ -397,7 +397,7 @@ Construct a `LinearizationProblem` for linearizing the system `sys` with the giv All other keyword arguments are forwarded to `linearization_function`. """ function LinearizationProblem(sys::AbstractSystem, inputs, outputs; t = 0.0, kwargs...) - linfun, _ = linearization_function(sys, inputs, outputs; kwargs...) + linfun = linearization_function(sys, inputs, outputs; kwargs...) return LinearizationProblem(linfun, t) end @@ -764,7 +764,7 @@ Permute the state representation of `sys` obtained from [`linearize`](@ref) so t Example: ``` -lsys, ssys = linearize(pid, [reference.u, measurement.u], [ctr_output.u]) +lsys = linearize(pid, [reference.u, measurement.u], [ctr_output.u]) desired_order = [int.x, der.x] # Unknowns that are present in unknowns(ssys) lsys = ModelingToolkit.reorder_unknowns(lsys, unknowns(ssys), desired_order) ``` diff --git a/src/systems/analysis_points.jl b/src/systems/analysis_points.jl index 0d1a2830cf..ce2e778b93 100644 --- a/src/systems/analysis_points.jl +++ b/src/systems/analysis_points.jl @@ -596,9 +596,13 @@ Add an input without an additional output variable. PerturbOutput(ap::AnalysisPoint) = PerturbOutput(ap, false) function apply_transformation(tf::PerturbOutput, sys::AbstractSystem) + @show "ok" + @show tf.ap modify_nested_subsystem(sys, tf.ap) do ap_sys # get analysis point + @show tf.ap ap_idx = analysis_point_index(ap_sys, tf.ap) + @show ap_idx ap_idx === nothing && error("Analysis point $(nameof(tf.ap)) not found in system $(nameof(sys)).") # modified equations diff --git a/src/systems/diffeqs/odesystem.jl b/src/systems/diffeqs/odesystem.jl index cfabaf2f2e..2e4c7a10b5 100644 --- a/src/systems/diffeqs/odesystem.jl +++ b/src/systems/diffeqs/odesystem.jl @@ -468,7 +468,7 @@ Generates a function that computes the observed value(s) `ts` in the system `sys - `eval_expression = false`: If true and `expression = false`, evaluates the returned function in the module `eval_module` - `output_type = Array` the type of the array generated by a out-of-place vector-valued function - `param_only = false` if true, only allow the generated function to access system parameters -- `inputs = nothing` additinoal symbolic variables that should be provided to the generated function +- `inputs = Any[]` additional symbolic variables that should be provided to the generated function - `checkbounds = true` checks bounds if true when destructuring parameters - `op = Operator` sets the recursion terminator for the walk done by `vars` to identify the variables that appear in `ts`. See the documentation for `vars` for more detail. - `throw = true` if true, throw an error when generating a function for `ts` that reference variables that do not exist. @@ -498,8 +498,8 @@ For example, a function `g(op, unknowns, p..., inputs, t)` will be the in-place an array of inputs `inputs` is given, and `param_only` is false for a time-dependent system. """ function build_explicit_observed_function(sys, ts; - inputs = nothing, - disturbance_inputs = nothing, + inputs = Any[], + disturbance_inputs = Any[], disturbance_argument = false, expression = false, eval_expression = false, @@ -572,13 +572,13 @@ function build_explicit_observed_function(sys, ts; else (unknowns(sys),) end - if inputs === nothing + if isempty(inputs) inputs = () else ps = setdiff(ps, inputs) # Inputs have been converted to parameters, remove those from the parameter list inputs = (inputs,) end - if disturbance_inputs !== nothing + if !isempty(disturbance_inputs) # Disturbance inputs may or may not be included as inputs, depending on disturbance_argument ps = setdiff(ps, disturbance_inputs) end diff --git a/src/systems/systems.jl b/src/systems/systems.jl index ac96ad9c2a..3e938578d6 100644 --- a/src/systems/systems.jl +++ b/src/systems/systems.jl @@ -29,8 +29,8 @@ topological sort of the observed equations in `sys`. function mtkbuild( sys::AbstractSystem; additional_passes = [], simplify = false, split = true, allow_symbolic = false, allow_parameter = true, conservative = false, fully_determined = true, - inputs = nothing, outputs = nothing, - disturbance_inputs = nothing, + inputs = Any[], outputs = Any[], + disturbance_inputs = Any[], kwargs...) isscheduled(sys) && throw(RepeatedStructuralSimplificationError()) newsys′ = __structural_simplification(sys; simplify, @@ -74,8 +74,8 @@ function __structural_simplification(sys::SDESystem, args...; kwargs...) end function __structural_simplification(sys::AbstractSystem; simplify = false, - inputs = nothing, outputs = nothing, - disturbance_inputs = nothing, + inputs = Any[], outputs = Any[], + disturbance_inputs = Any[], kwargs...) sys = expand_connections(sys) state = TearingState(sys; sort_eqs) diff --git a/src/systems/systemstructure.jl b/src/systems/systemstructure.jl index 03ced79c73..0c929c4bb3 100644 --- a/src/systems/systemstructure.jl +++ b/src/systems/systemstructure.jl @@ -659,8 +659,8 @@ end function structural_simplification!(state::TearingState; simplify = false, check_consistency = true, fully_determined = true, warn_initialize_determined = true, - inputs = nothing, outputs = nothing, - disturbance_inputs = nothing, + inputs = Any[], outputs = Any[], + disturbance_inputs = Any[], kwargs...) if state.sys isa ODESystem @@ -672,7 +672,7 @@ function structural_simplification!(state::TearingState; simplify = false, cont_inputs = [inputs; clocked_inputs[continuous_id]] sys = _structural_simplification!(tss[continuous_id]; simplify, check_consistency, fully_determined, - cont_inputs, outputs, disturbance_inputs, + inputs = cont_inputs, outputs, disturbance_inputs, kwargs...) if length(tss) > 1 if continuous_id > 0 @@ -690,7 +690,7 @@ function structural_simplification!(state::TearingState; simplify = false, end disc_inputs = [inputs; clocked_inputs[i]] ss, = _structural_simplification!(state; simplify, check_consistency, - disc_inputs, outputs, disturbance_inputs, + inputs = disc_inputs, outputs, disturbance_inputs, fully_determined, kwargs...) append!(appended_parameters, inputs[i], unknowns(ss)) discrete_subsystems[i] = ss @@ -717,8 +717,8 @@ end function _structural_simplification!(state::TearingState; simplify = false, check_consistency = true, fully_determined = true, warn_initialize_determined = false, dummy_derivative = true, - inputs = nothing, outputs = nothing, - disturbance_inputs = nothing, + inputs = Any[], outputs = Any[], + disturbance_inputs = Any[], kwargs...) if fully_determined isa Bool check_consistency &= fully_determined diff --git a/test/downstream/linearize.jl b/test/downstream/linearize.jl index 45574ec8c4..c90b324db4 100644 --- a/test/downstream/linearize.jl +++ b/test/downstream/linearize.jl @@ -25,14 +25,14 @@ lsys3 = linearize(sys, [r], [y]; autodiff = AutoFiniteDiff()) @test lsys.C[] == lsys2.C[] == lsys3.C[] == 1 @test lsys.D[] == lsys2.D[] == lsys3.D[] == 0 -lsys, ssys = linearize(sys, [r], [r]) +lsys = linearize(sys, [r], [r]) @test lsys.A[] == -2 @test lsys.B[] == 1 @test lsys.C[] == 0 @test lsys.D[] == 1 -lsys, ssys = linearize(sys, r, r) # Test allow scalars +lsys = linearize(sys, r, r) # Test allow scalars @test lsys.A[] == -2 @test lsys.B[] == 1 @@ -89,11 +89,11 @@ connections = [f.y ~ c.r # filtered reference to controller reference @named cl = ODESystem(connections, t, systems = [f, c, p]) cl = mtkbuild(cl, inputs = [f.u], outputs = [p.x]) -lsys0, ssys = linearize(cl) +lsys0 = linearize(cl, [f.u], [p.x]) desired_order = [f.x, p.x] -lsys = ModelingToolkit.reorder_unknowns(lsys0, unknowns(ssys), desired_order) -lsys1, ssys = linearize(cl; autodiff = AutoFiniteDiff()) -lsys2 = ModelingToolkit.reorder_unknowns(lsys1, unknowns(ssys), desired_order) +lsys = ModelingToolkit.reorder_unknowns(lsys0, unknowns(cl), desired_order) +lsys1 = linearize(cl, [f.u], [p.x]; autodiff = AutoFiniteDiff()) +lsys2 = ModelingToolkit.reorder_unknowns(lsys1, unknowns(cl), desired_order) @test lsys.A == lsys2.A == [-2 0; 1 -2] @test lsys.B == lsys2.B == reshape([1, 0], 2, 1) @@ -101,7 +101,7 @@ lsys2 = ModelingToolkit.reorder_unknowns(lsys1, unknowns(ssys), desired_order) @test lsys.D[] == lsys2.D[] == 0 ## Symbolic linearization -lsyss, _ = ModelingToolkit.linearize_symbolic(cl, [f.u], [p.x]) +lsyss = ModelingToolkit.linearize_symbolic(cl, [f.u], [p.x]) @test ModelingToolkit.fixpoint_sub(lsyss.A, ModelingToolkit.defaults(cl)) == lsys.A @test ModelingToolkit.fixpoint_sub(lsyss.B, ModelingToolkit.defaults(cl)) == lsys.B @@ -116,11 +116,12 @@ Nd = 10 @named pid = LimPID(; k, Ti, Td, Nd) @unpack reference, measurement, ctr_output = pid -lsys0, ssys = linearize(pid, [reference.u, measurement.u], [ctr_output.u]; +pid = mtkbuild(pid, inputs = [reference.u, measurement.u], outputs = [ctr_output.u]) +lsys0 = linearize(pid, [reference.u, measurement.u], [ctr_output.u]; op = Dict(reference.u => 0.0, measurement.u => 0.0)) @unpack int, der = pid desired_order = [int.x, der.x] -lsys = ModelingToolkit.reorder_unknowns(lsys0, unknowns(ssys), desired_order) +lsys = ModelingToolkit.reorder_unknowns(lsys0, unknowns(pid), desired_order) @test lsys.A == [0 0; 0 -10] @test lsys.B == [2 -2; 10 -10] @@ -150,12 +151,12 @@ lsys = ModelingToolkit.reorder_unknowns(lsys, desired_order, reverse(desired_ord ## Test that there is a warning when input is misspecified if VERSION >= v"1.8" - @test_throws "Some specified inputs were not found" linearize(pid, + @test_throws "Some parameters are missing from the variable map." linearize(pid, [ pid.reference.u, pid.measurement.u ], [ctr_output.u]) - @test_throws "Some specified outputs were not found" linearize(pid, + @test_throws "Some parameters are missing from the variable map." linearize(pid, [ reference.u, measurement.u @@ -186,15 +187,16 @@ function saturation(; y_max, y_min = y_max > 0 ? -y_max : -Inf, name) ODESystem(eqs, t, name = name) end @named sat = saturation(; y_max = 1) +sat = mtkbuild(sat, inputs = [u], outputs = [y]) # inside the linear region, the function is identity @unpack u, y = sat -lsys, ssys = linearize(sat, [u], [y]) +lsys = linearize(sat, [u], [y]) @test isempty(lsys.A) # there are no differential variables in this system @test isempty(lsys.B) @test isempty(lsys.C) @test lsys.D[] == 1 -@test_skip lsyss, _ = ModelingToolkit.linearize_symbolic(sat, [u], [y]) # Code gen replaces ifelse with if statements causing symbolic evaluation to fail +@test_skip lsyss = ModelingToolkit.linearize_symbolic(sat, [u], [y]) # Code gen replaces ifelse with if statements causing symbolic evaluation to fail # @test substitute(lsyss.A, ModelingToolkit.defaults(sat)) == lsys.A # @test substitute(lsyss.B, ModelingToolkit.defaults(sat)) == lsys.B # @test substitute(lsyss.C, ModelingToolkit.defaults(sat)) == lsys.C @@ -267,9 +269,9 @@ closed_loop = ODESystem(connections, t, systems = [model, pid, filt, sensor, r, filt.x => 0.0, filt.xd => 0.0 ]) -closed_loop = mtkbuild(closed_loop, inputs = :r, outputs = :y) +closed_loop = mtkbuild(closed_loop) -@test_nowarn linearize(closed_loop; warn_empty_op = false) +@test_nowarn linearize(closed_loop, :r, :y; warn_empty_op = false) # https://discourse.julialang.org/t/mtk-change-in-linearize/115760/3 @mtkmodel Tank_noi begin From 1f4243d11b9095d7516d3a03ae6cf7d752ca8515 Mon Sep 17 00:00:00 2001 From: vyudu Date: Tue, 22 Apr 2025 00:15:19 -0400 Subject: [PATCH 014/122] fix: simplify if not simplified --- src/linearization.jl | 2 +- test/downstream/linearize.jl | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/linearization.jl b/src/linearization.jl index cbc538a881..db3939d7b2 100644 --- a/src/linearization.jl +++ b/src/linearization.jl @@ -46,7 +46,7 @@ function linearization_function(sys::AbstractSystem, inputs = unbound_inputs(sys warn_empty_op = true, kwargs...) if !iscomplete(sys) - error("A completed `ODESystem` is required. Call `complete` or `mtkbuild` on the system before creating the linearization.") + sys = mtkbuild(sys; inputs, outputs) end op = Dict(op) if isempty(op) && warn_empty_op diff --git a/test/downstream/linearize.jl b/test/downstream/linearize.jl index c90b324db4..738cae70fe 100644 --- a/test/downstream/linearize.jl +++ b/test/downstream/linearize.jl @@ -269,7 +269,6 @@ closed_loop = ODESystem(connections, t, systems = [model, pid, filt, sensor, r, filt.x => 0.0, filt.xd => 0.0 ]) -closed_loop = mtkbuild(closed_loop) @test_nowarn linearize(closed_loop, :r, :y; warn_empty_op = false) From c424570e3ac19290e6a3ceb3d8184b0682c5ed50 Mon Sep 17 00:00:00 2001 From: vyudu Date: Tue, 22 Apr 2025 00:28:54 -0400 Subject: [PATCH 015/122] revert rename --- docs/src/basics/AbstractSystem.md | 2 +- docs/src/basics/Composition.md | 10 ++-- docs/src/basics/Debugging.md | 2 +- docs/src/basics/Events.md | 4 +- docs/src/basics/FAQ.md | 6 +- docs/src/basics/InputOutput.md | 4 ++ docs/src/basics/Linearization.md | 2 +- docs/src/basics/MTKLanguage.md | 6 +- docs/src/basics/Precompilation.md | 2 +- docs/src/basics/Validation.md | 2 +- docs/src/comparison.md | 4 +- docs/src/examples/higher_order.md | 4 +- .../modelingtoolkitize_index_reduction.md | 4 +- docs/src/examples/spring_mass.md | 8 +-- docs/src/examples/tearing_parallelism.md | 6 +- docs/src/internals.md | 2 +- docs/src/systems/DiscreteSystem.md | 2 +- docs/src/systems/ImplicitDiscreteSystem.md | 2 +- docs/src/systems/JumpSystem.md | 2 +- docs/src/systems/NonlinearSystem.md | 2 +- docs/src/systems/ODESystem.md | 2 +- docs/src/systems/SDESystem.md | 2 +- docs/src/tutorials/acausal_components.md | 2 +- docs/src/tutorials/attractors.md | 2 +- .../tutorials/change_independent_variable.md | 8 +-- docs/src/tutorials/domain_connections.md | 8 +-- docs/src/tutorials/fmi.md | 6 +- docs/src/tutorials/initialization.md | 6 +- .../tutorials/programmatically_generating.md | 2 +- ext/MTKBifurcationKitExt.jl | 4 +- ext/MTKFMIExt.jl | 2 +- src/ModelingToolkit.jl | 4 +- src/inputoutput.jl | 2 +- src/linearization.jl | 23 ++++---- .../StructuralTransformations.jl | 2 +- src/structural_transformation/pantelides.jl | 2 +- .../symbolics_tearing.jl | 2 +- src/systems/abstractsystem.jl | 16 ++--- src/systems/diffeqs/abstractodesystem.jl | 34 +++++------ src/systems/diffeqs/basic_transformations.jl | 4 +- src/systems/diffeqs/odesystem.jl | 4 +- src/systems/diffeqs/sdesystem.jl | 10 ++-- .../discrete_system/discrete_system.jl | 4 +- .../implicit_discrete_system.jl | 4 +- src/systems/index_cache.jl | 4 +- src/systems/jumps/jumpsystem.jl | 8 +-- .../nonlinear/homotopy_continuation.jl | 4 +- src/systems/nonlinear/nonlinearsystem.jl | 24 ++++---- .../optimization/optimizationsystem.jl | 8 +-- src/systems/parameter_buffer.jl | 2 +- src/systems/systems.jl | 16 ++--- src/systems/systemstructure.jl | 10 ++-- src/utils.jl | 8 +-- test/accessor_functions.jl | 10 ++-- test/analysis_points.jl | 2 +- test/basic_transformations.jl | 20 +++---- test/clock.jl | 14 ++--- test/code_generation.jl | 2 +- test/components.jl | 24 ++++---- test/constants.jl | 6 +- test/dde.jl | 8 +-- test/debugging.jl | 4 +- test/discrete_system.jl | 4 +- test/domain_connectors.jl | 2 +- test/downstream/analysis_points.jl | 22 +++---- test/downstream/inversemodel.jl | 2 +- test/downstream/linearize.jl | 20 +++---- test/downstream/test_disturbance_model.jl | 10 ++-- test/dq_units.jl | 8 +-- test/error_handling.jl | 4 +- test/extensions/ad.jl | 4 +- test/extensions/bifurcationkit.jl | 4 +- test/funcaffect.jl | 8 +-- test/guess_propagation.jl | 8 +-- test/hierarchical_initialization_eqs.jl | 2 +- test/if_lifting.jl | 6 +- test/initializationsystem.jl | 26 ++++----- test/input_output_handling.jl | 18 +++--- test/jumpsystem.jl | 2 +- test/modelingtoolkitize.jl | 2 +- test/mtkparameters.jl | 6 +- test/nonlinearsystem.jl | 20 +++---- test/odesystem.jl | 58 +++++++++---------- test/optimizationsystem.jl | 2 +- test/parameter_dependencies.jl | 4 +- test/reduction.jl | 28 ++++----- test/scc_nonlinear_problem.jl | 4 +- test/sciml_problem_inputs.jl | 2 +- test/sdesystem.jl | 14 ++--- test/split_parameters.jl | 4 +- test/state_selection.jl | 6 +- test/static_arrays.jl | 2 +- test/stream_connectors.jl | 12 ++-- .../index_reduction.jl | 4 +- test/structural_transformation/tearing.jl | 6 +- test/structural_transformation/utils.jl | 10 ++-- test/substitute_component.jl | 4 +- test/symbolic_events.jl | 48 +++++++-------- test/symbolic_indexing_interface.jl | 2 +- test/units.jl | 8 +-- test/variable_scope.jl | 2 +- 101 files changed, 399 insertions(+), 404 deletions(-) diff --git a/docs/src/basics/AbstractSystem.md b/docs/src/basics/AbstractSystem.md index 276fcb44f0..d1707f822f 100644 --- a/docs/src/basics/AbstractSystem.md +++ b/docs/src/basics/AbstractSystem.md @@ -152,7 +152,7 @@ a lower level in the system. ## Namespacing By default, unsimplified systems will namespace variables accessed via `getproperty`. -Systems created via `@mtkbuild`, or ones passed through `mtkbuild` or +Systems created via `@mtkbuild`, or ones passed through `structural_simplify` or `complete` will not perform this namespacing. However, all of these processes modify the system in a variety of ways. To toggle namespacing without transforming any other property of the system, use `toggle_namespacing`. diff --git a/docs/src/basics/Composition.md b/docs/src/basics/Composition.md index ba5fe48137..d3de71d696 100644 --- a/docs/src/basics/Composition.md +++ b/docs/src/basics/Composition.md @@ -42,7 +42,7 @@ equations(connected) # Differential(t)(decay1₊x(t)) ~ decay1₊f(t) - (decay1₊a*(decay1₊x(t))) # Differential(t)(decay2₊x(t)) ~ decay2₊f(t) - (decay2₊a*(decay2₊x(t))) -simplified_sys = mtkbuild(connected) +simplified_sys = structural_simplify(connected) equations(simplified_sys) ``` @@ -84,7 +84,7 @@ example, let's say there is a variable `x` in `unknowns` and a variable `x` in `subsys`. We can declare that these two variables are the same by specifying their equality: `x ~ subsys.x` in the `eqs` for `sys`. This algebraic relationship can then be simplified by transformations -like `mtkbuild` which will be described later. +like `structural_simplify` which will be described later. ### Numerics with Composed Models @@ -169,7 +169,7 @@ parameters(level3) In many cases, the nicest way to build a model may leave a lot of unnecessary variables. Thus one may want to remove these equations -before numerically solving. The `mtkbuild` function removes +before numerically solving. The `structural_simplify` function removes these trivial equality relationships and trivial singularity equations, i.e. equations which result in `0~0` expressions, in over-specified systems. @@ -227,7 +227,7 @@ values. The user of this model can then solve this model simply by specifying the values at the highest level: ```@example compose -sireqn_simple = mtkbuild(sir) +sireqn_simple = structural_simplify(sir) equations(sireqn_simple) ``` @@ -251,7 +251,7 @@ sol[reqn.R] ## Tearing Problem Construction Some system types (specifically `NonlinearSystem`) can be further -reduced if `mtkbuild` has already been applied to them. This is done +reduced if `structural_simplify` has already been applied to them. This is done by using the alternative problem constructors (`BlockNonlinearProblem`). In these cases, the constructor uses the knowledge of the strongly connected components calculated during the process of simplification diff --git a/docs/src/basics/Debugging.md b/docs/src/basics/Debugging.md index 45384ecf9c..6e2d471461 100644 --- a/docs/src/basics/Debugging.md +++ b/docs/src/basics/Debugging.md @@ -13,7 +13,7 @@ using ModelingToolkit: t_nounits as t, D_nounits as D eqs = [D(u1) ~ -√(u1), D(u2) ~ -√(u2), D(u3) ~ -√(u3)] defaults = [u1 => 1.0, u2 => 2.0, u3 => 3.0] @named sys = ODESystem(eqs, t; defaults) -sys = mtkbuild(sys) +sys = structural_simplify(sys) ``` This problem causes the ODE solver to crash: diff --git a/docs/src/basics/Events.md b/docs/src/basics/Events.md index 125588540b..3a76f478f1 100644 --- a/docs/src/basics/Events.md +++ b/docs/src/basics/Events.md @@ -472,7 +472,7 @@ to the system. ```@example events @named sys = ODESystem( eqs, t, [temp], params; continuous_events = [furnace_disable, furnace_enable]) -ss = mtkbuild(sys) +ss = structural_simplify(sys) prob = ODEProblem(ss, [temp => 0.0, furnace_on => true], (0.0, 10.0)) sol = solve(prob, Tsit5()) plot(sol) @@ -585,7 +585,7 @@ We can now simulate the encoder. ```@example events @named sys = ODESystem( eqs, t, [theta, omega], params; continuous_events = [qAevt, qBevt]) -ss = mtkbuild(sys) +ss = structural_simplify(sys) prob = ODEProblem(ss, [theta => 0.0], (0.0, pi)) sol = solve(prob, Tsit5(); dtmax = 0.01) sol.ps[cnt] diff --git a/docs/src/basics/FAQ.md b/docs/src/basics/FAQ.md index 52152b761b..10671299c6 100644 --- a/docs/src/basics/FAQ.md +++ b/docs/src/basics/FAQ.md @@ -28,7 +28,7 @@ are similarly undocumented. Following is the list of behaviors that should be re - `setindex!(::MTKParameters, value, ::ParameterIndex)` can be used to set the value of a parameter with the given index. - `parameter_values(sys, sym)` will return a `ParameterIndex` object if `sys` has been - `complete`d (through `mtkbuild`, `complete` or `@mtkbuild`). + `complete`d (through `structural_simplify`, `complete` or `@mtkbuild`). - `copy(::MTKParameters)` is defined and duplicates the parameter object, including the memory used by the underlying buffers. @@ -194,7 +194,7 @@ p, replace, alias = SciMLStructures.canonicalize(Tunable(), prob.p) # ERROR: ArgumentError: SymbolicUtils.BasicSymbolic{Real}[xˍt(t)] are missing from the variable map. -This error can come up after running `mtkbuild` on a system that generates dummy derivatives (i.e. variables with `ˍt`). For example, here even though all the variables are defined with initial values, the `ODEProblem` generation will throw an error that defaults are missing from the variable map. +This error can come up after running `structural_simplify` on a system that generates dummy derivatives (i.e. variables with `ˍt`). For example, here even though all the variables are defined with initial values, the `ODEProblem` generation will throw an error that defaults are missing from the variable map. ```julia using ModelingToolkit @@ -206,7 +206,7 @@ eqs = [x1 + x2 + 1 ~ 0 x1 + D(x3) + x4 + 3 ~ 0 2 * D(D(x1)) + D(D(x2)) + D(D(x3)) + D(x4) + 4 ~ 0] @named sys = ODESystem(eqs, t) -sys = mtkbuild(sys) +sys = structural_simplify(sys) prob = ODEProblem(sys, [], (0, 1)) ``` diff --git a/docs/src/basics/InputOutput.md b/docs/src/basics/InputOutput.md index d6108d7cc4..4dc5a3d50f 100644 --- a/docs/src/basics/InputOutput.md +++ b/docs/src/basics/InputOutput.md @@ -28,6 +28,10 @@ ModelingToolkit can generate the dynamics of a system, the function ``M\dot x = This function takes a vector of variables that are to be considered inputs, i.e., part of the vector ``u``. Alongside returning the function ``f``, [`ModelingToolkit.generate_control_function`](@ref) also returns the chosen state realization of the system after simplification. This vector specifies the order of the state variables ``x``, while the user-specified vector `u` specifies the order of the input variables ``u``. +!!! note "Un-simplified system" + + This function expects `sys` to be un-simplified, i.e., `structural_simplify` or `@mtkbuild` should not be called on the system before passing it into this function. `generate_control_function` calls a special version of `structural_simplify` internally. + ### Example: The following example implements a simple first-order system with an input `u` and state `x`. The function `f` is generated using `generate_control_function`, and the function `f` is then tested with random input and state values. diff --git a/docs/src/basics/Linearization.md b/docs/src/basics/Linearization.md index a95f2b0104..78b6d5925d 100644 --- a/docs/src/basics/Linearization.md +++ b/docs/src/basics/Linearization.md @@ -75,7 +75,7 @@ eqs = [D(x) ~ v y.u ~ x] @named duffing = ODESystem(eqs, t, systems = [y, u], defaults = [u.u => 0]) -duffing = mtkbuild(duffing, inputs = [u.u], outputs = [y.u]) +duffing = structural_simplify(duffing, inputs = [u.u], outputs = [y.u]) # pass a constant value for `x`, since it is the variable we will change in operating points linfun = linearization_function(duffing, [u.u], [y.u]; op = Dict(x => NaN)); diff --git a/docs/src/basics/MTKLanguage.md b/docs/src/basics/MTKLanguage.md index db5ce3b723..e91f2bcb67 100644 --- a/docs/src/basics/MTKLanguage.md +++ b/docs/src/basics/MTKLanguage.md @@ -535,10 +535,10 @@ This is equivalent to: ```julia @named model = CustomModel() -sys = mtkbuild(model) +sys = structural_simplify(model) ``` -Pass keyword arguments to `mtkbuild` using the following syntax: +Pass keyword arguments to `structural_simplify` using the following syntax: ```julia @mtkbuild sys=CustomModel() fully_determined=false @@ -548,5 +548,5 @@ This is equivalent to: ```julia @named model = CustomModel() -sys = mtkbuild(model; fully_determined = false) +sys = structural_simplify(model; fully_determined = false) ``` diff --git a/docs/src/basics/Precompilation.md b/docs/src/basics/Precompilation.md index 88a425710a..97111f0d6b 100644 --- a/docs/src/basics/Precompilation.md +++ b/docs/src/basics/Precompilation.md @@ -22,7 +22,7 @@ using ModelingToolkit @variables x(ModelingToolkit.t_nounits) @named sys = ODESystem([ModelingToolkit.D_nounits(x) ~ -x + 1], ModelingToolkit.t_nounits) -prob = ODEProblem(mtkbuild(sys), [x => 30.0], (0, 100), [], +prob = ODEProblem(structural_simplify(sys), [x => 30.0], (0, 100), [], eval_expression = true, eval_module = @__MODULE__) end diff --git a/docs/src/basics/Validation.md b/docs/src/basics/Validation.md index 39a51ccf68..79c5d0d214 100644 --- a/docs/src/basics/Validation.md +++ b/docs/src/basics/Validation.md @@ -112,7 +112,7 @@ ps = @parameters s=-1 [unit = u"cm"] c=c [unit = u"cm"] eqs = [D(a) ~ dummycomplex(c, s);] sys = ODESystem( eqs, t, [sts...;], [ps...;], name = :sys, checks = ~ModelingToolkit.CheckUnits) -sys_simple = mtkbuild(sys) +sys_simple = structural_simplify(sys) ``` ## `DynamicQuantities` Literals diff --git a/docs/src/comparison.md b/docs/src/comparison.md index 0bf88b72c3..52d5ab2f70 100644 --- a/docs/src/comparison.md +++ b/docs/src/comparison.md @@ -12,7 +12,7 @@ - All current Modelica compiler implementations are fixed and not extendable by the users from the Modelica language itself. For example, the Dymola compiler [shares its symbolic processing pipeline](https://www.claytex.com/tech-blog/model-translation-and-symbolic-manipulation/), - which is roughly equivalent to the `dae_index_lowering` and `mtkbuild` + which is roughly equivalent to the `dae_index_lowering` and `structural_simplify` of ModelingToolkit.jl. ModelingToolkit.jl is an open and hackable transformation system which allows users to add new non-standard transformations and control the order of application. @@ -90,7 +90,7 @@ [Dymola symbolic processing pipeline](https://www.claytex.com/tech-blog/model-translation-and-symbolic-manipulation/) with some improvements. ModelingToolkit.jl has an open transformation pipeline that allows for users to extend and reorder transformation passes, where - `mtkbuild` is an adaptation of the Modia.jl-improved alias elimination + `structural_simplify` is an adaptation of the Modia.jl-improved alias elimination and tearing algorithms. - Both Modia and ModelingToolkit generate `DAEProblem` and `ODEProblem` forms for solving with [DifferentialEquations.jl](https://docs.sciml.ai/DiffEqDocs/stable/). diff --git a/docs/src/examples/higher_order.md b/docs/src/examples/higher_order.md index 6bdffb2ace..fac707525f 100644 --- a/docs/src/examples/higher_order.md +++ b/docs/src/examples/higher_order.md @@ -4,7 +4,7 @@ ModelingToolkit has a system for transformations of mathematical systems. These transformations allow for symbolically changing the representation of the model to problems that are easier to numerically solve. One simple to demonstrate transformation, is -`mtkbuild`, which does a lot of tricks, one being the +`structural_simplify`, which does a lot of tricks, one being the transformation that turns an Nth order ODE into N coupled 1st order ODEs. @@ -43,7 +43,7 @@ and this syntax extends to `N`-th order. Also, we can use `*` or `∘` to compos `Differential`s, like `Differential(t) * Differential(x)`. Now let's transform this into the `ODESystem` of first order components. -We do this by calling `mtkbuild`: +We do this by calling `structural_simplify`: Now we can directly numerically solve the lowered system. Note that, following the original problem, the solution requires knowing the diff --git a/docs/src/examples/modelingtoolkitize_index_reduction.md b/docs/src/examples/modelingtoolkitize_index_reduction.md index 40204c3a5f..b19ea46701 100644 --- a/docs/src/examples/modelingtoolkitize_index_reduction.md +++ b/docs/src/examples/modelingtoolkitize_index_reduction.md @@ -29,7 +29,7 @@ p = [9.8, 1] tspan = (0, 10.0) pendulum_prob = ODEProblem(pendulum_fun!, u0, tspan, p) traced_sys = modelingtoolkitize(pendulum_prob) -pendulum_sys = mtkbuild(dae_index_lowering(traced_sys)) +pendulum_sys = structural_simplify(dae_index_lowering(traced_sys)) prob = ODEProblem(pendulum_sys, [], tspan) sol = solve(prob, Rodas5P(), abstol = 1e-8, reltol = 1e-8) plot(sol, idxs = unknowns(traced_sys)) @@ -157,7 +157,7 @@ numerical solver. Let's try that out: ```@example indexred traced_sys = modelingtoolkitize(pendulum_prob) -pendulum_sys = mtkbuild(dae_index_lowering(traced_sys)) +pendulum_sys = structural_simplify(dae_index_lowering(traced_sys)) prob = ODEProblem(pendulum_sys, Pair[], tspan) sol = solve(prob, Rodas5P()) diff --git a/docs/src/examples/spring_mass.md b/docs/src/examples/spring_mass.md index e9c6b4f908..355e5c20b2 100644 --- a/docs/src/examples/spring_mass.md +++ b/docs/src/examples/spring_mass.md @@ -45,7 +45,7 @@ eqs = [connect_spring(spring, mass.pos, center) @named _model = ODESystem(eqs, t, [spring.x; spring.dir; mass.pos], []) @named model = compose(_model, mass, spring) -sys = mtkbuild(model) +sys = structural_simplify(model) prob = ODEProblem(sys, [], (0.0, 3.0)) sol = solve(prob, Rosenbrock23()) @@ -153,10 +153,10 @@ parameters(model) ### Simplifying and solving this system -This system can be solved directly as a DAE using [one of the DAE solvers from DifferentialEquations.jl](https://docs.sciml.ai/DiffEqDocs/stable/solvers/dae_solve/). However, we can symbolically simplify the system first beforehand. Running `mtkbuild` eliminates unnecessary variables from the model to give the leanest numerical representation of the system. +This system can be solved directly as a DAE using [one of the DAE solvers from DifferentialEquations.jl](https://docs.sciml.ai/DiffEqDocs/stable/solvers/dae_solve/). However, we can symbolically simplify the system first beforehand. Running `structural_simplify` eliminates unnecessary variables from the model to give the leanest numerical representation of the system. ```@example component -sys = mtkbuild(model) +sys = structural_simplify(model) equations(sys) ``` @@ -177,7 +177,7 @@ sol = solve(prob, Rosenbrock23()) plot(sol) ``` -What if we want the timeseries of a different variable? That information is not lost! Instead, `mtkbuild` simply changes unknown variables into `observed` variables. +What if we want the timeseries of a different variable? That information is not lost! Instead, `structural_simplify` simply changes unknown variables into `observed` variables. ```@example component observed(sys) diff --git a/docs/src/examples/tearing_parallelism.md b/docs/src/examples/tearing_parallelism.md index 688b47917f..9540e610bd 100644 --- a/docs/src/examples/tearing_parallelism.md +++ b/docs/src/examples/tearing_parallelism.md @@ -1,7 +1,7 @@ # Exposing More Parallelism By Tearing Algebraic Equations in ODESystems Sometimes it can be very non-trivial to parallelize a system. In this tutorial, -we will demonstrate how to make use of `mtkbuild` to expose more +we will demonstrate how to make use of `structural_simplify` to expose more parallelism in the solution process and parallelize the resulting simulation. ## The Component Library @@ -122,7 +122,7 @@ Now let's say we want to expose a bit more parallelism via running tearing. How do we do that? ```@example tearing -sys = mtkbuild(big_rc) +sys = structural_simplify(big_rc) ``` Done, that's it. There's no more to it. @@ -175,5 +175,5 @@ so this is better than trying to do it by hand. After performing this, you can construct the `ODEProblem` and set `parallel_form` to use the exposed parallelism in multithreaded function -constructions, but this showcases why `mtkbuild` is so important +constructions, but this showcases why `structural_simplify` is so important to that process. diff --git a/docs/src/internals.md b/docs/src/internals.md index 2db7fd8bc6..00b29f1a64 100644 --- a/docs/src/internals.md +++ b/docs/src/internals.md @@ -18,7 +18,7 @@ and are then used to generate the `observed` equation found in the variable when necessary. In this sense, there is an equivalence between observables and the variable elimination system. -The procedure for variable elimination inside [`mtkbuild`](@ref) is +The procedure for variable elimination inside [`structural_simplify`](@ref) is 1. [`ModelingToolkit.initialize_system_structure`](@ref). 2. [`ModelingToolkit.alias_elimination`](@ref). This step moves equations into `observed(sys)`. diff --git a/docs/src/systems/DiscreteSystem.md b/docs/src/systems/DiscreteSystem.md index e787b8f004..f8a71043ab 100644 --- a/docs/src/systems/DiscreteSystem.md +++ b/docs/src/systems/DiscreteSystem.md @@ -17,7 +17,7 @@ DiscreteSystem ## Transformations ```@docs; canonical=false -mtkbuild +structural_simplify ``` ## Problem Constructors diff --git a/docs/src/systems/ImplicitDiscreteSystem.md b/docs/src/systems/ImplicitDiscreteSystem.md index 13910f3995..d69f88f106 100644 --- a/docs/src/systems/ImplicitDiscreteSystem.md +++ b/docs/src/systems/ImplicitDiscreteSystem.md @@ -17,7 +17,7 @@ ImplicitDiscreteSystem ## Transformations ```@docs; canonical=false -mtkbuild +structural_simplify ``` ## Problem Constructors diff --git a/docs/src/systems/JumpSystem.md b/docs/src/systems/JumpSystem.md index 2db9246f70..5bd0d50602 100644 --- a/docs/src/systems/JumpSystem.md +++ b/docs/src/systems/JumpSystem.md @@ -17,7 +17,7 @@ JumpSystem ## Transformations ```@docs; canonical=false -mtkbuild +structural_simplify ``` ## Analyses diff --git a/docs/src/systems/NonlinearSystem.md b/docs/src/systems/NonlinearSystem.md index 2a470f0820..06d587b1b9 100644 --- a/docs/src/systems/NonlinearSystem.md +++ b/docs/src/systems/NonlinearSystem.md @@ -16,7 +16,7 @@ NonlinearSystem ## Transformations ```@docs; canonical=false -mtkbuild +structural_simplify alias_elimination tearing ``` diff --git a/docs/src/systems/ODESystem.md b/docs/src/systems/ODESystem.md index ce6149edeb..24e2952fc5 100644 --- a/docs/src/systems/ODESystem.md +++ b/docs/src/systems/ODESystem.md @@ -27,7 +27,7 @@ ODESystem ## Transformations ```@docs -mtkbuild +structural_simplify ode_order_lowering dae_index_lowering change_independent_variable diff --git a/docs/src/systems/SDESystem.md b/docs/src/systems/SDESystem.md index 2f51528a51..5789d2d9cb 100644 --- a/docs/src/systems/SDESystem.md +++ b/docs/src/systems/SDESystem.md @@ -33,7 +33,7 @@ sde = SDESystem(ode, noiseeqs) ## Transformations ```@docs; canonical=false -mtkbuild +structural_simplify alias_elimination ``` diff --git a/docs/src/tutorials/acausal_components.md b/docs/src/tutorials/acausal_components.md index 4e0f14fc63..751b678dae 100644 --- a/docs/src/tutorials/acausal_components.md +++ b/docs/src/tutorials/acausal_components.md @@ -323,7 +323,7 @@ plot(sol) By default, this plots only the unknown variables that had to be solved for. However, what if we wanted to plot the timeseries of a different variable? Do not worry, that information was not thrown away! Instead, transformations -like `mtkbuild` simply change unknown variables into observables which are +like `structural_simplify` simply change unknown variables into observables which are defined by `observed` equations. ```@example acausal diff --git a/docs/src/tutorials/attractors.md b/docs/src/tutorials/attractors.md index 24649d307d..317384b01a 100644 --- a/docs/src/tutorials/attractors.md +++ b/docs/src/tutorials/attractors.md @@ -42,7 +42,7 @@ Because our dynamical system is super simple, we will directly make an `ODESyste ```@example Attractors @named modlorenz = ODESystem(eqs, t) -ssys = mtkbuild(modlorenz) +ssys = structural_simplify(modlorenz) # The timespan given to the problem is irrelevant for DynamicalSystems.jl prob = ODEProblem(ssys, [], (0.0, 1.0), []) ``` diff --git a/docs/src/tutorials/change_independent_variable.md b/docs/src/tutorials/change_independent_variable.md index 18b6f75bb3..d55639a669 100644 --- a/docs/src/tutorials/change_independent_variable.md +++ b/docs/src/tutorials/change_independent_variable.md @@ -25,7 +25,7 @@ using ModelingToolkit: t_nounits as t, D_nounits as D eqs = [D(D(y)) ~ -g, D(x) ~ v] initialization_eqs = [D(x) ~ D(y)] # 45° initial angle M1 = ODESystem(eqs, t; initialization_eqs, name = :M) -M1s = mtkbuild(M1) +M1s = structural_simplify(M1) @assert length(equations(M1s)) == 3 # hide M1s # hide ``` @@ -44,7 +44,7 @@ This transformation is well-defined for any non-zero horizontal velocity $v$, so ```@example changeivar M2 = change_independent_variable(M1, x) -M2s = mtkbuild(M2; allow_symbolic = true) +M2s = structural_simplify(M2; allow_symbolic = true) # a sanity test on the 10 x/y variables that are accessible to the user # hide @assert allequal([x, M1s.x]) # hide @assert allequal([M2.x, M2s.x]) # hide @@ -97,7 +97,7 @@ eqs = [Ω ~ r.Ω + m.Ω + Λ.Ω, D(a) ~ √(Ω) * a^2] initialization_eqs = [Λ.Ω + r.Ω + m.Ω ~ 1] M1 = ODESystem(eqs, t, [Ω, a], []; initialization_eqs, name = :M) M1 = compose(M1, r, m, Λ) -M1s = mtkbuild(M1) +M1s = structural_simplify(M1) ``` Of course, we can solve this ODE as it is: @@ -137,7 +137,7 @@ M3 = change_independent_variable(M2, b, [Da(b) ~ exp(-b), a ~ exp(b)]) We can now solve and plot the ODE in terms of $b$: ```@example changeivar -M3s = mtkbuild(M3; allow_symbolic = true) +M3s = structural_simplify(M3; allow_symbolic = true) prob = ODEProblem(M3s, [M3s.r.Ω => 5e-5, M3s.m.Ω => 0.3], (0, -15), []) sol = solve(prob, Tsit5()) @assert Symbol(sol.retcode) == :Success # surrounding text assumes the solution was successful # hide diff --git a/docs/src/tutorials/domain_connections.md b/docs/src/tutorials/domain_connections.md index ff124ce481..d6dc2d8781 100644 --- a/docs/src/tutorials/domain_connections.md +++ b/docs/src/tutorials/domain_connections.md @@ -115,10 +115,10 @@ end nothing #hide ``` -To see how the domain works, we can examine the set parameter values for each of the ports `src.port` and `vol.port`. First we assemble the system using `mtkbuild()` and then check the default value of `vol.port.ρ`, whichs points to the setter value `fluid₊ρ`. Likewise, `src.port.ρ`, will also point to the setter value `fluid₊ρ`. Therefore, there is now only 1 defined density value `fluid₊ρ` which sets the density for the connected network. +To see how the domain works, we can examine the set parameter values for each of the ports `src.port` and `vol.port`. First we assemble the system using `structural_simplify()` and then check the default value of `vol.port.ρ`, whichs points to the setter value `fluid₊ρ`. Likewise, `src.port.ρ`, will also point to the setter value `fluid₊ρ`. Therefore, there is now only 1 defined density value `fluid₊ρ` which sets the density for the connected network. ```@repl domain -sys = mtkbuild(odesys) +sys = structural_simplify(odesys) ModelingToolkit.defaults(sys)[odesys.vol.port.ρ] ``` @@ -181,7 +181,7 @@ end nothing #hide ``` -After running `mtkbuild()` on `actsys2`, the defaults will show that `act.port_a.ρ` points to `fluid_a₊ρ` and `act.port_b.ρ` points to `fluid_b₊ρ`. This is a special case, in most cases a hydraulic system will have only 1 fluid, however this simple system has 2 separate domain networks. Therefore, we can connect a single fluid to both networks. This does not interfere with the mathematical equations of the system, since no unknown variables are connected. +After running `structural_simplify()` on `actsys2`, the defaults will show that `act.port_a.ρ` points to `fluid_a₊ρ` and `act.port_b.ρ` points to `fluid_b₊ρ`. This is a special case, in most cases a hydraulic system will have only 1 fluid, however this simple system has 2 separate domain networks. Therefore, we can connect a single fluid to both networks. This does not interfere with the mathematical equations of the system, since no unknown variables are connected. ```@example domain @component function ActuatorSystem1(; name) @@ -252,7 +252,7 @@ end nothing #hide ``` -When `mtkbuild()` is applied to this system it can be seen that the defaults are missing for `res.port_b` and `vol.port`. +When `structural_simplify()` is applied to this system it can be seen that the defaults are missing for `res.port_b` and `vol.port`. ```@repl domain ModelingToolkit.defaults(ressys)[ressys.res.port_a.ρ] diff --git a/docs/src/tutorials/fmi.md b/docs/src/tutorials/fmi.md index 9015f91987..ef00477c78 100644 --- a/docs/src/tutorials/fmi.md +++ b/docs/src/tutorials/fmi.md @@ -76,7 +76,7 @@ initialization semantics. We can simulate this model like any other ModelingToolkit system. ```@repl fmi -sys = mtkbuild(model) +sys = structural_simplify(model) prob = ODEProblem(sys, [sys.mass__s => 0.5, sys.mass__v => 0.0], (0.0, 5.0)) sol = solve(prob, Tsit5()) ``` @@ -104,11 +104,11 @@ constant until the next time the callback triggers. The periodic interval must b `communication_step_size` keyword argument. A smaller step size typically leads to less error but is more computationally expensive. -This model alone does not have any differential variables, and calling `mtkbuild` will lead +This model alone does not have any differential variables, and calling `structural_simplify` will lead to an `ODESystem` with no unknowns. ```@example fmi -mtkbuild(inner) +structural_simplify(inner) ``` Simulating this model will cause the OrdinaryDiffEq integrator to immediately finish, and will not diff --git a/docs/src/tutorials/initialization.md b/docs/src/tutorials/initialization.md index 8712262cbc..ba733e0bfb 100644 --- a/docs/src/tutorials/initialization.md +++ b/docs/src/tutorials/initialization.md @@ -380,7 +380,7 @@ with observables, those observables are too treated as initial equations. We can resulting simplified system via the command: ```@example init -isys = mtkbuild(isys; fully_determined = false) +isys = structural_simplify(isys; fully_determined = false) ``` Note `fully_determined=false` allows for the simplification to occur when the number of equations @@ -392,7 +392,7 @@ isys = ModelingToolkit.generate_initializesystem( ``` ```@example init -isys = mtkbuild(isys; fully_determined = false) +isys = structural_simplify(isys; fully_determined = false) ``` ```@example init @@ -504,7 +504,7 @@ eqs = [D(x) ~ α * x - β * x * y z ~ x + y] @named sys = ODESystem(eqs, t) -simpsys = mtkbuild(sys) +simpsys = structural_simplify(sys) tspan = (0.0, 10.0) ``` diff --git a/docs/src/tutorials/programmatically_generating.md b/docs/src/tutorials/programmatically_generating.md index 6abba4dd3d..9fc1db1834 100644 --- a/docs/src/tutorials/programmatically_generating.md +++ b/docs/src/tutorials/programmatically_generating.md @@ -47,7 +47,7 @@ eqs = [D(x) ~ (h - x) / τ] # create an array of equations # Perform the standard transformations and mark the model complete # Note: Complete models cannot be subsystems of other models! -fol = mtkbuild(model) +fol = structural_simplify(model) prob = ODEProblem(fol, [], (0.0, 10.0), []) using OrdinaryDiffEq sol = solve(prob) diff --git a/ext/MTKBifurcationKitExt.jl b/ext/MTKBifurcationKitExt.jl index e0e9cbf8fe..0b9f104d9b 100644 --- a/ext/MTKBifurcationKitExt.jl +++ b/ext/MTKBifurcationKitExt.jl @@ -92,7 +92,7 @@ function BifurcationKit.BifurcationProblem(nsys::NonlinearSystem, jac = true, kwargs...) if !ModelingToolkit.iscomplete(nsys) - error("A completed `NonlinearSystem` is required. Call `complete` or `mtkbuild` on the system before creating a `BifurcationProblem`") + error("A completed `NonlinearSystem` is required. Call `complete` or `structural_simplify` on the system before creating a `BifurcationProblem`") end @set! nsys.index_cache = nothing # force usage of a parameter vector instead of `MTKParameters` # Creates F and J functions. @@ -146,7 +146,7 @@ end # When input is a ODESystem. function BifurcationKit.BifurcationProblem(osys::ODESystem, args...; kwargs...) if !ModelingToolkit.iscomplete(osys) - error("A completed `ODESystem` is required. Call `complete` or `mtkbuild` on the system before creating a `BifurcationProblem`") + error("A completed `ODESystem` is required. Call `complete` or `structural_simplify` on the system before creating a `BifurcationProblem`") end nsys = NonlinearSystem([0 ~ eq.rhs for eq in full_equations(osys)], unknowns(osys), diff --git a/ext/MTKFMIExt.jl b/ext/MTKFMIExt.jl index 4501bbcbaf..5cfe9a82ef 100644 --- a/ext/MTKFMIExt.jl +++ b/ext/MTKFMIExt.jl @@ -127,7 +127,7 @@ function MTK.FMIComponent(::Val{Ver}; fmu = nothing, tolerance = 1e-6, # no differential variables __mtk_internal_u = Float64[] elseif type == :ME - # to avoid running into `mtkbuild` warnings about array variables + # to avoid running into `structural_simplify` warnings about array variables # and some unfortunate circular dependency issues, ME FMUs use an array of # symbolics instead. This is also not worse off in performance # because the former approach would allocate anyway. diff --git a/src/ModelingToolkit.jl b/src/ModelingToolkit.jl index 43872763b7..198a84d48b 100644 --- a/src/ModelingToolkit.jl +++ b/src/ModelingToolkit.jl @@ -227,7 +227,7 @@ PrecompileTools.@compile_workload begin using ModelingToolkit @variables x(ModelingToolkit.t_nounits) @named sys = ODESystem([ModelingToolkit.D_nounits(x) ~ -x], ModelingToolkit.t_nounits) - prob = ODEProblem(mtkbuild(sys), [x => 30.0], (0, 100), [], jac = true) + prob = ODEProblem(structural_simplify(sys), [x => 30.0], (0, 100), [], jac = true) @mtkmodel __testmod__ begin @constants begin c = 1.0 @@ -300,7 +300,7 @@ export Term, Sym export SymScope, LocalScope, ParentScope, GlobalScope export independent_variable, equations, controls, observed, full_equations export initialization_equations, guesses, defaults, parameter_dependencies, hierarchy -export mtkbuild, expand_connections, linearize, linearization_function, +export structural_simplify, expand_connections, linearize, linearization_function, LinearizationProblem export solve diff --git a/src/inputoutput.jl b/src/inputoutput.jl index 89d27cb729..c03da22abf 100644 --- a/src/inputoutput.jl +++ b/src/inputoutput.jl @@ -201,7 +201,7 @@ function generate_control_function(sys::AbstractODESystem, inputs = unbound_inpu # Remove this when the ControlFunction gets merged. if !iscomplete(sys) - error("A completed `ODESystem` is required. Call `complete` or `mtkbuild` on the system before creating the control function.") + error("A completed `ODESystem` is required. Call `complete` or `structural_simplify` on the system before creating the control function.") end isempty(inputs) && @warn("No unbound inputs were found in system.") if disturbance_inputs !== nothing diff --git a/src/linearization.jl b/src/linearization.jl index db3939d7b2..eb06d142f3 100644 --- a/src/linearization.jl +++ b/src/linearization.jl @@ -1,5 +1,5 @@ """ - lin_fun = linearization_function(sys::AbstractSystem, inputs, outputs; initialize = true, initialization_solver_alg = TrustRegion(), kwargs...) + lin_fun, simplified_sys = linearization_function(sys::AbstractSystem, inputs, outputs; initialize = true, initialization_solver_alg = TrustRegion(), kwargs...) Return a function that linearizes the system `sys`. The function [`linearize`](@ref) provides a higher-level and easier to use interface. @@ -15,7 +15,7 @@ y &= h(x, z, u) where `x` are differential unknown variables, `z` algebraic variables, `u` inputs and `y` outputs. To obtain a linear statespace representation, see [`linearize`](@ref). The input argument `variables` is a vector defining the operating point, corresponding to `unknowns(simplified_sys)` and `p` is a vector corresponding to the parameters of `simplified_sys`. Note: all variables in `inputs` have been converted to parameters in `simplified_sys`. -The `simplified_sys` has undergone [`mtkbuild`](@ref) and had any occurring input or output variables replaced with the variables provided in arguments `inputs` and `outputs`. The unknowns of this system also indicate the order of the unknowns that holds for the linearized matrices. +The `simplified_sys` has undergone [`structural_simplify`](@ref) and had any occurring input or output variables replaced with the variables provided in arguments `inputs` and `outputs`. The unknowns of this system also indicate the order of the unknowns that holds for the linearized matrices. # Arguments: @@ -29,8 +29,8 @@ The `simplified_sys` has undergone [`mtkbuild`](@ref) and had any occurring inpu See also [`linearize`](@ref) which provides a higher-level interface. """ -function linearization_function(sys::AbstractSystem, inputs = unbound_inputs(sys), - outputs = unbound_outputs(sys); +function linearization_function(sys::AbstractSystem, inputs, + outputs; initialize = true, initializealg = nothing, initialization_abstol = 1e-5, @@ -484,13 +484,10 @@ y &= h(x, z, u) ``` where `x` are differential unknown variables, `z` algebraic variables, `u` inputs and `y` outputs. """ -function linearize_symbolic(sys::AbstractSystem, inputs = unbound_inputs(sys), - outputs = unbound_outputs(sys); allow_input_derivatives = false, +function linearize_symbolic(sys::AbstractSystem, inputs, + outputs; allow_input_derivatives = false, eval_expression = false, eval_module = @__MODULE__, kwargs...) - if !iscomplete(sys) - error("A completed `ODESystem` is required. Call `complete` or `mtkbuild` on the system before creating the linearization.") - end diff_idxs, alge_idxs = eq_idxs(sys) sts = unknowns(sys) t = get_iv(sys) @@ -549,7 +546,7 @@ function linearize_symbolic(sys::AbstractSystem, inputs = unbound_inputs(sys), end end - (; A, B, C, D, f_x, f_z, g_x, g_z, f_u, g_u, h_x, h_z, h_u) + (; A, B, C, D, f_x, f_z, g_x, g_z, f_u, g_u, h_x, h_z, h_u), sys end function markio!(state, orig_inputs, inputs, outputs; check = true) @@ -716,17 +713,17 @@ function linearize(sys, lin_fun::LinearizationFunction; t = 0.0, return solve(prob; allow_input_derivatives) end -function linearize(sys, inputs = unbound_inputs(sys), outputs = unbound_outputs(sys); op = Dict(), t = 0.0, +function linearize(sys, inputs, outputs; op = Dict(), t = 0.0, allow_input_derivatives = false, zero_dummy_der = false, kwargs...) - lin_fun = linearization_function(sys, + lin_fun, ssys = linearization_function(sys, inputs, outputs; zero_dummy_der, op, kwargs...) - linearize(sys, lin_fun; op, t, allow_input_derivatives) + linearize(ssys, lin_fun; op, t, allow_input_derivatives), ssys end """ diff --git a/src/structural_transformation/StructuralTransformations.jl b/src/structural_transformation/StructuralTransformations.jl index 0b8b2eddcb..4adc817ef8 100644 --- a/src/structural_transformation/StructuralTransformations.jl +++ b/src/structural_transformation/StructuralTransformations.jl @@ -35,7 +35,7 @@ import ModelingToolkit: var_derivative!, var_derivative_graph! using Graphs using ModelingToolkit: algeqs, EquationsView, SystemStructure, TransformationState, TearingState, - structural_simplification!, + structural_simplify!, isdiffvar, isdervar, isalgvar, isdiffeq, algeqs, is_only_discrete, dervars_range, diffvars_range, algvars_range, DiffGraph, complete!, diff --git a/src/structural_transformation/pantelides.jl b/src/structural_transformation/pantelides.jl index 335c7d52cd..585c4a29d1 100644 --- a/src/structural_transformation/pantelides.jl +++ b/src/structural_transformation/pantelides.jl @@ -210,7 +210,7 @@ end dae_index_lowering(sys::ODESystem; kwargs...) -> ODESystem Perform the Pantelides algorithm to transform a higher index DAE to an index 1 -DAE. `kwargs` are forwarded to [`pantelides!`](@ref). End users are encouraged to call [`mtkbuild`](@ref) +DAE. `kwargs` are forwarded to [`pantelides!`](@ref). End users are encouraged to call [`structural_simplify`](@ref) instead, which calls this function internally. """ function dae_index_lowering(sys::ODESystem; kwargs...) diff --git a/src/structural_transformation/symbolics_tearing.jl b/src/structural_transformation/symbolics_tearing.jl index 7b554f97d1..548c7da519 100644 --- a/src/structural_transformation/symbolics_tearing.jl +++ b/src/structural_transformation/symbolics_tearing.jl @@ -1018,7 +1018,7 @@ end tearing(sys; simplify=false) Tear the nonlinear equations in system. When `simplify=true`, we simplify the -new residual equations after tearing. End users are encouraged to call [`mtkbuild`](@ref) +new residual equations after tearing. End users are encouraged to call [`structural_simplify`](@ref) instead, which calls this function internally. """ function tearing(sys::AbstractSystem, state = TearingState(sys); mm = nothing, diff --git a/src/systems/abstractsystem.jl b/src/systems/abstractsystem.jl index 8559151747..025199c92b 100644 --- a/src/systems/abstractsystem.jl +++ b/src/systems/abstractsystem.jl @@ -157,7 +157,7 @@ may be subsetted using `dvs` and `ps`. All `kwargs` are passed to the internal [`build_function`](@ref) call. The returned function can be called as `f(u, p, t)` or `f(du, u, p, t)` for time-dependent systems and `f(u, p)` or `f(du, u, p)` for time-independent systems. If `split=true` (the default) was passed to [`complete`](@ref), -[`mtkbuild`](@ref) or [`@mtkbuild`](@ref), `p` is expected to be an `MTKParameters` +[`structural_simplify`](@ref) or [`@mtkbuild`](@ref), `p` is expected to be an `MTKParameters` object. """ function generate_custom_function(sys::AbstractSystem, exprs, dvs = unknowns(sys), @@ -165,7 +165,7 @@ function generate_custom_function(sys::AbstractSystem, exprs, dvs = unknowns(sys expression = Val{true}, eval_expression = false, eval_module = @__MODULE__, cachesyms::Tuple = (), kwargs...) if !iscomplete(sys) - error("A completed system is required. Call `complete` or `mtkbuild` on the system.") + error("A completed system is required. Call `complete` or `structural_simplify` on the system.") end p = (reorder_parameters(sys, unwrap.(ps))..., cachesyms...) isscalar = !(exprs isa AbstractArray) @@ -771,7 +771,7 @@ function complete( newparams = OrderedSet() iv = has_iv(sys) ? get_iv(sys) : nothing collect_scoped_vars!(newunknowns, newparams, sys, iv; depth = -1) - # don't update unknowns to not disturb `mtkbuild` order + # don't update unknowns to not disturb `structural_simplify` order # `GlobalScope`d unknowns will be picked up and added there @set! sys.ps = unique!(vcat(get_ps(sys), collect(newparams))) @@ -1196,7 +1196,7 @@ end Denotes that a variable belongs to the root system in the hierarchy, regardless of which equations of subsystems in the hierarchy it is involved in. Variables with this scope are never namespaced and only added to the unknowns/parameters of a system when calling -`complete` or `mtkbuild`. +`complete` or `structural_simplify`. """ struct GlobalScope <: SymScope end @@ -2416,7 +2416,7 @@ macro mtkbuild(exprs...) else kwargs = (Expr(:parameters, kwargs...),) end - call_expr = Expr(:call, mtkbuild, kwargs..., name) + call_expr = Expr(:call, structural_simplify, kwargs..., name) esc(quote $named_expr $name = $call_expr @@ -2455,7 +2455,7 @@ function debug_system( functions = Set(functions) # more efficient "in" lookup end if has_systems(sys) && !isempty(get_systems(sys)) - error("debug_system(sys) only works on systems with no sub-systems! Consider flattening it with flatten(sys) or mtkbuild(sys) first.") + error("debug_system(sys) only works on systems with no sub-systems! Consider flattening it with flatten(sys) or structural_simplify(sys) first.") end if has_eqs(sys) eqs = debug_sub.(equations(sys), Ref(functions); kw...) @@ -2552,10 +2552,10 @@ end function check_array_equations_unknowns(eqs, dvs) if any(eq -> eq isa Equation && Symbolics.isarraysymbolic(eq.lhs), eqs) - throw(ArgumentError("The system has array equations. Call `mtkbuild` to handle such equations or scalarize them manually.")) + throw(ArgumentError("The system has array equations. Call `structural_simplify` to handle such equations or scalarize them manually.")) end if any(x -> Symbolics.isarraysymbolic(x), dvs) - throw(ArgumentError("The system has array unknowns. Call `mtkbuild` to handle this or scalarize them manually.")) + throw(ArgumentError("The system has array unknowns. Call `structural_simplify` to handle this or scalarize them manually.")) end end diff --git a/src/systems/diffeqs/abstractodesystem.jl b/src/systems/diffeqs/abstractodesystem.jl index 96dbae0762..d9e8b05eeb 100644 --- a/src/systems/diffeqs/abstractodesystem.jl +++ b/src/systems/diffeqs/abstractodesystem.jl @@ -364,7 +364,7 @@ function DiffEqBase.ODEFunction{iip, specialize}(sys::AbstractODESystem, cse = true, kwargs...) where {iip, specialize} if !iscomplete(sys) - error("A completed system is required. Call `complete` or `mtkbuild` on the system before creating an `ODEFunction`") + error("A completed system is required. Call `complete` or `structural_simplify` on the system before creating an `ODEFunction`") end f_gen = generate_function(sys, dvs, ps; expression = Val{true}, expression_module = eval_module, checkbounds = checkbounds, cse, @@ -469,7 +469,7 @@ function DiffEqBase.DAEFunction{iip}(sys::AbstractODESystem, dvs = unknowns(sys) cse = true, kwargs...) where {iip} if !iscomplete(sys) - error("A completed system is required. Call `complete` or `mtkbuild` on the system before creating a `DAEFunction`") + error("A completed system is required. Call `complete` or `structural_simplify` on the system before creating a `DAEFunction`") end f_gen = generate_function(sys, dvs, ps; implicit_dae = true, expression = Val{true}, cse, @@ -529,7 +529,7 @@ function DiffEqBase.DDEFunction{iip}(sys::AbstractODESystem, dvs = unknowns(sys) cse = true, kwargs...) where {iip} if !iscomplete(sys) - error("A completed system is required. Call `complete` or `mtkbuild` on the system before creating an `DDEFunction`") + error("A completed system is required. Call `complete` or `structural_simplify` on the system before creating an `DDEFunction`") end f_gen = generate_function(sys, dvs, ps; isdde = true, expression = Val{true}, @@ -554,7 +554,7 @@ function DiffEqBase.SDDEFunction{iip}(sys::AbstractODESystem, dvs = unknowns(sys cse = true, kwargs...) where {iip} if !iscomplete(sys) - error("A completed system is required. Call `complete` or `mtkbuild` on the system before creating an `SDDEFunction`") + error("A completed system is required. Call `complete` or `structural_simplify` on the system before creating an `SDDEFunction`") end f_gen = generate_function(sys, dvs, ps; isdde = true, expression = Val{true}, @@ -598,7 +598,7 @@ function ODEFunctionExpr{iip, specialize}(sys::AbstractODESystem, dvs = unknowns observedfun_exp = nothing, kwargs...) where {iip, specialize} if !iscomplete(sys) - error("A completed system is required. Call `complete` or `mtkbuild` on the system before creating an `ODEFunctionExpr`") + error("A completed system is required. Call `complete` or `structural_simplify` on the system before creating an `ODEFunctionExpr`") end f_oop, f_iip = generate_function(sys, dvs, ps; expression = Val{true}, kwargs...) @@ -688,7 +688,7 @@ function DAEFunctionExpr{iip}(sys::AbstractODESystem, dvs = unknowns(sys), sparse = false, simplify = false, kwargs...) where {iip} if !iscomplete(sys) - error("A completed system is required. Call `complete` or `mtkbuild` on the system before creating an `DAEFunctionExpr`") + error("A completed system is required. Call `complete` or `structural_simplify` on the system before creating an `DAEFunctionExpr`") end f_oop, f_iip = generate_function(sys, dvs, ps; expression = Val{true}, implicit_dae = true, kwargs...) @@ -784,7 +784,7 @@ function DiffEqBase.ODEProblem{iip, specialize}(sys::AbstractODESystem, u0map = eval_module = @__MODULE__, kwargs...) where {iip, specialize} if !iscomplete(sys) - error("A completed system is required. Call `complete` or `mtkbuild` on the system before creating an `ODEProblem`") + error("A completed system is required. Call `complete` or `structural_simplify` on the system before creating an `ODEProblem`") end if !isnothing(get_constraintsystem(sys)) @@ -900,7 +900,7 @@ function SciMLBase.BVProblem{iip, specialize}(sys::AbstractODESystem, u0map = [] cse = true, kwargs...) where {iip, specialize} if !iscomplete(sys) - error("A completed system is required. Call `complete` or `mtkbuild` on the system before creating an `BVProblem`") + error("A completed system is required. Call `complete` or `structural_simplify` on the system before creating an `BVProblem`") end !isnothing(callback) && error("BVP solvers do not support callbacks.") @@ -1014,7 +1014,7 @@ function DiffEqBase.DAEProblem{iip}(sys::AbstractODESystem, du0map, u0map, tspan warn_initialize_determined = true, check_length = true, eval_expression = false, eval_module = @__MODULE__, kwargs...) where {iip} if !iscomplete(sys) - error("A completed system is required. Call `complete` or `mtkbuild` on the system before creating a `DAEProblem`.") + error("A completed system is required. Call `complete` or `structural_simplify` on the system before creating a `DAEProblem`.") end if !isempty(get_costs(sys)) && !allow_cost @@ -1066,7 +1066,7 @@ function DiffEqBase.DDEProblem{iip}(sys::AbstractODESystem, u0map = [], cse = true, kwargs...) where {iip} if !iscomplete(sys) - error("A completed system is required. Call `complete` or `mtkbuild` on the system before creating a `DDEProblem`") + error("A completed system is required. Call `complete` or `structural_simplify` on the system before creating a `DDEProblem`") end f, u0, p = process_SciMLProblem(DDEFunction{iip}, sys, u0map, parammap; t = tspan !== nothing ? tspan[1] : tspan, @@ -1106,7 +1106,7 @@ function DiffEqBase.SDDEProblem{iip}(sys::AbstractODESystem, u0map = [], cse = true, kwargs...) where {iip} if !iscomplete(sys) - error("A completed system is required. Call `complete` or `mtkbuild` on the system before creating a `SDDEProblem`") + error("A completed system is required. Call `complete` or `structural_simplify` on the system before creating a `SDDEProblem`") end f, u0, p = process_SciMLProblem(SDDEFunction{iip}, sys, u0map, parammap; t = tspan !== nothing ? tspan[1] : tspan, @@ -1167,7 +1167,7 @@ function ODEProblemExpr{iip}(sys::AbstractODESystem, u0map, tspan, parammap = DiffEqBase.NullParameters(); check_length = true, kwargs...) where {iip} if !iscomplete(sys) - error("A completed system is required. Call `complete` or `mtkbuild` on the system before creating a `ODEProblemExpr`") + error("A completed system is required. Call `complete` or `structural_simplify` on the system before creating a `ODEProblemExpr`") end f, u0, p = process_SciMLProblem( ODEFunctionExpr{iip}, sys, u0map, parammap; check_length, @@ -1214,7 +1214,7 @@ function DAEProblemExpr{iip}(sys::AbstractODESystem, du0map, u0map, tspan, parammap = DiffEqBase.NullParameters(); check_length = true, kwargs...) where {iip} if !iscomplete(sys) - error("A completed system is required. Call `complete` or `mtkbuild` on the system before creating a `DAEProblemExpr`") + error("A completed system is required. Call `complete` or `structural_simplify` on the system before creating a `DAEProblemExpr`") end f, du0, u0, p = process_SciMLProblem(DAEFunctionExpr{iip}, sys, u0map, parammap; t = tspan !== nothing ? tspan[1] : tspan, @@ -1266,7 +1266,7 @@ function DiffEqBase.SteadyStateProblem{iip}(sys::AbstractODESystem, u0map, parammap = SciMLBase.NullParameters(); check_length = true, kwargs...) where {iip} if !iscomplete(sys) - error("A completed system is required. Call `complete` or `mtkbuild` on the system before creating a `SteadyStateProblem`") + error("A completed system is required. Call `complete` or `structural_simplify` on the system before creating a `SteadyStateProblem`") end f, u0, p = process_SciMLProblem(ODEFunction{iip}, sys, u0map, parammap; steady_state = true, @@ -1298,7 +1298,7 @@ function SteadyStateProblemExpr{iip}(sys::AbstractODESystem, u0map, check_length = true, kwargs...) where {iip} if !iscomplete(sys) - error("A completed system is required. Call `complete` or `mtkbuild` on the system before creating a `SteadyStateProblemExpr`") + error("A completed system is required. Call `complete` or `structural_simplify` on the system before creating a `SteadyStateProblemExpr`") end f, u0, p = process_SciMLProblem(ODEFunctionExpr{iip}, sys, u0map, parammap; steady_state = true, @@ -1449,7 +1449,7 @@ function InitializationProblem{iip, specialize}(sys::AbstractSystem, algebraic_only = false, kwargs...) where {iip, specialize} if !iscomplete(sys) - error("A completed system is required. Call `complete` or `mtkbuild` on the system before creating an `ODEProblem`") + error("A completed system is required. Call `complete` or `structural_simplify` on the system before creating an `ODEProblem`") end if isempty(u0map) && get_initializesystem(sys) !== nothing isys = get_initializesystem(sys; initialization_eqs, check_units) @@ -1474,7 +1474,7 @@ function InitializationProblem{iip, specialize}(sys::AbstractSystem, end if simplify_system - isys = mtkbuild(isys; fully_determined) + isys = structural_simplify(isys; fully_determined) end ts = get_tearing_state(isys) diff --git a/src/systems/diffeqs/basic_transformations.jl b/src/systems/diffeqs/basic_transformations.jl index e448b23dfb..d41e948d64 100644 --- a/src/systems/diffeqs/basic_transformations.jl +++ b/src/systems/diffeqs/basic_transformations.jl @@ -74,7 +74,7 @@ Any extra equations `eqs` involving the new and old independent variables will b # Usage before structural simplification The variable change must take place before structural simplification. -In following calls to `mtkbuild`, consider passing `allow_symbolic = true` to avoid undesired constraint equations between between dummy variables. +In following calls to `structural_simplify`, consider passing `allow_symbolic = true` to avoid undesired constraint equations between between dummy variables. # Usage with non-autonomous systems @@ -99,7 +99,7 @@ julia> @named M = ODESystem([D(D(y)) ~ -9.81, D(D(x)) ~ 0.0], t); julia> M = change_independent_variable(M, x); -julia> M = mtkbuild(M; allow_symbolic = true); +julia> M = structural_simplify(M; allow_symbolic = true); julia> unknowns(M) 3-element Vector{SymbolicUtils.BasicSymbolic{Real}}: diff --git a/src/systems/diffeqs/odesystem.jl b/src/systems/diffeqs/odesystem.jl index 2e4c7a10b5..630ba74295 100644 --- a/src/systems/diffeqs/odesystem.jl +++ b/src/systems/diffeqs/odesystem.jl @@ -575,7 +575,7 @@ function build_explicit_observed_function(sys, ts; if isempty(inputs) inputs = () else - ps = setdiff(ps, inputs) # Inputs have been converted to parameters, remove those from the parameter list + ps = setdiff(ps, inputs) # Inputs have been converted to parameters by io_preprocessing, remove those from the parameter list inputs = (inputs,) end if !isempty(disturbance_inputs) @@ -658,7 +658,7 @@ end # We have a stand-alone function to convert a `NonlinearSystem` or `ODESystem` # to an `ODESystem` to connect systems, and we later can reply on -# `mtkbuild` to convert `ODESystem`s to `NonlinearSystem`s. +# `structural_simplify` to convert `ODESystem`s to `NonlinearSystem`s. """ $(TYPEDSIGNATURES) diff --git a/src/systems/diffeqs/sdesystem.jl b/src/systems/diffeqs/sdesystem.jl index 4ce6b9605c..c5299c28be 100644 --- a/src/systems/diffeqs/sdesystem.jl +++ b/src/systems/diffeqs/sdesystem.jl @@ -600,7 +600,7 @@ function DiffEqBase.SDEFunction{iip, specialize}(sys::SDESystem, dvs = unknowns( checkbounds = false, initialization_data = nothing, cse = true, kwargs...) where {iip, specialize} if !iscomplete(sys) - error("A completed `SDESystem` is required. Call `complete` or `mtkbuild` on the system before creating an `SDEFunction`") + error("A completed `SDESystem` is required. Call `complete` or `structural_simplify` on the system before creating an `SDEFunction`") end dvs = scalarize.(dvs) @@ -720,7 +720,7 @@ function SDEFunctionExpr{iip}(sys::SDESystem, dvs = unknowns(sys), sparse = false, linenumbers = false, kwargs...) where {iip} if !iscomplete(sys) - error("A completed `SDESystem` is required. Call `complete` or `mtkbuild` on the system before creating an `SDEFunctionExpr`") + error("A completed `SDESystem` is required. Call `complete` or `structural_simplify` on the system before creating an `SDEFunctionExpr`") end idx = iip ? 2 : 1 f = generate_function(sys, dvs, ps; expression = Val{true}, kwargs...)[idx] @@ -788,7 +788,7 @@ function DiffEqBase.SDEProblem{iip, specialize}( sparsenoise = nothing, check_length = true, callback = nothing, kwargs...) where {iip, specialize} if !iscomplete(sys) - error("A completed `SDESystem` is required. Call `complete` or `mtkbuild` on the system before creating an `SDEProblem`") + error("A completed `SDESystem` is required. Call `complete` or `structural_simplify` on the system before creating an `SDEProblem`") end f, u0, p = process_SciMLProblem( @@ -824,7 +824,7 @@ end function DiffEqBase.SDEProblem(sys::ODESystem, args...; kwargs...) if any(ModelingToolkit.isbrownian, unknowns(sys)) - error("SDESystem constructed by defining Brownian variables with @brownian must be simplified by calling `mtkbuild` before a SDEProblem can be constructed.") + error("SDESystem constructed by defining Brownian variables with @brownian must be simplified by calling `structural_simplify` before a SDEProblem can be constructed.") else error("Cannot construct SDEProblem from a normal ODESystem.") end @@ -886,7 +886,7 @@ function SDEProblemExpr{iip}(sys::SDESystem, u0map, tspan, sparsenoise = nothing, check_length = true, kwargs...) where {iip} if !iscomplete(sys) - error("A completed `SDESystem` is required. Call `complete` or `mtkbuild` on the system before creating an `SDEProblemExpr`") + error("A completed `SDESystem` is required. Call `complete` or `structural_simplify` on the system before creating an `SDEProblemExpr`") end f, u0, p = process_SciMLProblem( SDEFunctionExpr{iip}, sys, u0map, parammap; check_length, diff --git a/src/systems/discrete_system/discrete_system.jl b/src/systems/discrete_system/discrete_system.jl index fa2e9f89ec..075aa27e4d 100644 --- a/src/systems/discrete_system/discrete_system.jl +++ b/src/systems/discrete_system/discrete_system.jl @@ -311,7 +311,7 @@ function SciMLBase.DiscreteProblem( kwargs... ) if !iscomplete(sys) - error("A completed `DiscreteSystem` is required. Call `complete` or `mtkbuild` on the system before creating a `DiscreteProblem`") + error("A completed `DiscreteSystem` is required. Call `complete` or `structural_simplify` on the system before creating a `DiscreteProblem`") end dvs = unknowns(sys) ps = parameters(sys) @@ -363,7 +363,7 @@ function SciMLBase.DiscreteFunction{iip, specialize}( analytic = nothing, cse = true, kwargs...) where {iip, specialize} if !iscomplete(sys) - error("A completed `DiscreteSystem` is required. Call `complete` or `mtkbuild` on the system before creating a `DiscreteProblem`") + error("A completed `DiscreteSystem` is required. Call `complete` or `structural_simplify` on the system before creating a `DiscreteProblem`") end f_gen = generate_function(sys, dvs, ps; expression = Val{true}, expression_module = eval_module, cse, kwargs...) diff --git a/src/systems/discrete_system/implicit_discrete_system.jl b/src/systems/discrete_system/implicit_discrete_system.jl index 6440d5cce9..3956c089d4 100644 --- a/src/systems/discrete_system/implicit_discrete_system.jl +++ b/src/systems/discrete_system/implicit_discrete_system.jl @@ -330,7 +330,7 @@ function SciMLBase.ImplicitDiscreteProblem( kwargs... ) if !iscomplete(sys) - error("A completed `ImplicitDiscreteSystem` is required. Call `complete` or `mtkbuild` on the system before creating a `ImplicitDiscreteProblem`.") + error("A completed `ImplicitDiscreteSystem` is required. Call `complete` or `structural_simplify` on the system before creating a `ImplicitDiscreteProblem`.") end dvs = unknowns(sys) ps = parameters(sys) @@ -372,7 +372,7 @@ function SciMLBase.ImplicitDiscreteFunction{iip, specialize}( analytic = nothing, cse = true, kwargs...) where {iip, specialize} if !iscomplete(sys) - error("A completed `ImplicitDiscreteSystem` is required. Call `complete` or `mtkbuild` on the system before creating a `ImplicitDiscreteProblem`") + error("A completed `ImplicitDiscreteSystem` is required. Call `complete` or `structural_simplify` on the system before creating a `ImplicitDiscreteProblem`") end f_gen = generate_function(sys, dvs, ps; expression = Val{true}, expression_module = eval_module, cse, kwargs...) diff --git a/src/systems/index_cache.jl b/src/systems/index_cache.jl index 8d051f4b2c..d0b687c212 100644 --- a/src/systems/index_cache.jl +++ b/src/systems/index_cache.jl @@ -653,10 +653,10 @@ See also: [`MTKParameters`](@ref), [`tunable_parameters`](@ref), [`reorder_dimen function reorder_dimension_by_tunables!( dest::AbstractArray, sys::AbstractSystem, arr::AbstractArray, syms; dim = 1) if !iscomplete(sys) - throw(ArgumentError("A completed system is required. Call `complete` or `mtkbuild` on the system.")) + throw(ArgumentError("A completed system is required. Call `complete` or `structural_simplify` on the system.")) end if !has_index_cache(sys) || (ic = get_index_cache(sys)) === nothing - throw(ArgumentError("The system does not have an index cache. Call `complete` or `mtkbuild` on the system with `split = true`.")) + throw(ArgumentError("The system does not have an index cache. Call `complete` or `structural_simplify` on the system with `split = true`.")) end if size(dest) != size(arr) throw(ArgumentError("Source and destination arrays must have the same size. Got source array with size $(size(arr)) and destination with size $(size(dest)).")) diff --git a/src/systems/jumps/jumpsystem.jl b/src/systems/jumps/jumpsystem.jl index 8a5997d235..06f5e1b623 100644 --- a/src/systems/jumps/jumpsystem.jl +++ b/src/systems/jumps/jumpsystem.jl @@ -401,7 +401,7 @@ function DiffEqBase.DiscreteProblem(sys::JumpSystem, u0map, tspan::Union{Tuple, cse = true, kwargs...) if !iscomplete(sys) - error("A completed `JumpSystem` is required. Call `complete` or `mtkbuild` on the system before creating a `DiscreteProblem`") + error("A completed `JumpSystem` is required. Call `complete` or `structural_simplify` on the system before creating a `DiscreteProblem`") end if has_equations(sys) || (!isempty(continuous_events(sys))) @@ -446,7 +446,7 @@ function DiscreteProblemExpr{iip}(sys::JumpSystem, u0map, tspan::Union{Tuple, No parammap = DiffEqBase.NullParameters(); kwargs...) where {iip} if !iscomplete(sys) - error("A completed `JumpSystem` is required. Call `complete` or `mtkbuild` on the system before creating a `DiscreteProblemExpr`") + error("A completed `JumpSystem` is required. Call `complete` or `structural_simplify` on the system before creating a `DiscreteProblemExpr`") end _, u0, p = process_SciMLProblem(EmptySciMLFunction, sys, u0map, parammap; @@ -492,7 +492,7 @@ function DiffEqBase.ODEProblem(sys::JumpSystem, u0map, tspan::Union{Tuple, Nothi eval_module = @__MODULE__, cse = true, kwargs...) if !iscomplete(sys) - error("A completed `JumpSystem` is required. Call `complete` or `mtkbuild` on the system before creating a `DiscreteProblem`") + error("A completed `JumpSystem` is required. Call `complete` or `structural_simplify` on the system before creating a `DiscreteProblem`") end # forward everything to be an ODESystem but the jumps and discrete events @@ -535,7 +535,7 @@ function JumpProcesses.JumpProblem(js::JumpSystem, prob, aggregator = JumpProcesses.NullAggregator(); callback = nothing, eval_expression = false, eval_module = @__MODULE__, kwargs...) if !iscomplete(js) - error("A completed `JumpSystem` is required. Call `complete` or `mtkbuild` on the system before creating a `JumpProblem`") + error("A completed `JumpSystem` is required. Call `complete` or `structural_simplify` on the system before creating a `JumpProblem`") end unknowntoid = Dict(value(unknown) => i for (i, unknown) in enumerate(unknowns(js))) eqs = equations(js) diff --git a/src/systems/nonlinear/homotopy_continuation.jl b/src/systems/nonlinear/homotopy_continuation.jl index 796fce0c2d..9a77779103 100644 --- a/src/systems/nonlinear/homotopy_continuation.jl +++ b/src/systems/nonlinear/homotopy_continuation.jl @@ -492,7 +492,7 @@ function SciMLBase.HomotopyNonlinearFunction{iip, specialize}( p = nothing, fraction_cancel_fn = SymbolicUtils.simplify_fractions, cse = true, kwargs...) where {iip, specialize} if !iscomplete(sys) - error("A completed `NonlinearSystem` is required. Call `complete` or `mtkbuild` on the system before creating a `HomotopyContinuationFunction`") + error("A completed `NonlinearSystem` is required. Call `complete` or `structural_simplify` on the system before creating a `HomotopyContinuationFunction`") end transformation = PolynomialTransformation(sys) if transformation isa NotPolynomialError @@ -553,7 +553,7 @@ function HomotopyContinuationProblem{iip, spec}( sys::NonlinearSystem, u0map, pmap = SciMLBase.NullParameters(); kwargs...) where {iip, spec} if !iscomplete(sys) - error("A completed `NonlinearSystem` is required. Call `complete` or `mtkbuild` on the system before creating a `HomotopyContinuationProblem`") + error("A completed `NonlinearSystem` is required. Call `complete` or `structural_simplify` on the system before creating a `HomotopyContinuationProblem`") end f, u0, p = process_SciMLProblem( HomotopyNonlinearFunction{iip, spec}, sys, u0map, pmap; kwargs...) diff --git a/src/systems/nonlinear/nonlinearsystem.jl b/src/systems/nonlinear/nonlinearsystem.jl index a5e0779813..7146fb6b5e 100644 --- a/src/systems/nonlinear/nonlinearsystem.jl +++ b/src/systems/nonlinear/nonlinearsystem.jl @@ -383,7 +383,7 @@ function SciMLBase.NonlinearFunction{iip}(sys::NonlinearSystem, dvs = unknowns(s initialization_data = nothing, cse = true, kwargs...) where {iip} if !iscomplete(sys) - error("A completed `NonlinearSystem` is required. Call `complete` or `mtkbuild` on the system before creating a `NonlinearFunction`") + error("A completed `NonlinearSystem` is required. Call `complete` or `structural_simplify` on the system before creating a `NonlinearFunction`") end f_gen = generate_function(sys, dvs, ps; expression = Val{true}, cse, kwargs...) f_oop, f_iip = eval_or_rgf.(f_gen; eval_expression, eval_module) @@ -430,7 +430,7 @@ function SciMLBase.IntervalNonlinearFunction( p = nothing, eval_expression = false, eval_module = @__MODULE__, initialization_data = nothing, kwargs...) if !iscomplete(sys) - error("A completed `NonlinearSystem` is required. Call `complete` or `mtkbuild` on the system before creating a `IntervalNonlinearFunction`") + error("A completed `NonlinearSystem` is required. Call `complete` or `structural_simplify` on the system before creating a `IntervalNonlinearFunction`") end if !isone(length(dvs)) || !isone(length(equations(sys))) error("`IntervalNonlinearFunction` only supports systems with a single equation and a single unknown.") @@ -472,7 +472,7 @@ function NonlinearFunctionExpr{iip}(sys::NonlinearSystem, dvs = unknowns(sys), sparse = false, simplify = false, kwargs...) where {iip} if !iscomplete(sys) - error("A completed `NonlinearSystem` is required. Call `complete` or `mtkbuild` on the system before creating a `NonlinearFunctionExpr`") + error("A completed `NonlinearSystem` is required. Call `complete` or `structural_simplify` on the system before creating a `NonlinearFunctionExpr`") end f_oop, f_iip = generate_function(sys, dvs, ps; expression = Val{true}, kwargs...) f = :($(GeneratedFunctionWrapper{(2, 2, is_split(sys))})($f_oop, $f_iip)) @@ -521,7 +521,7 @@ function IntervalNonlinearFunctionExpr( sys::NonlinearSystem, dvs = unknowns(sys), ps = parameters(sys), u0 = nothing; p = nothing, linenumbers = false, kwargs...) if !iscomplete(sys) - error("A completed `NonlinearSystem` is required. Call `complete` or `mtkbuild` on the system before creating a `IntervalNonlinearFunctionExpr`") + error("A completed `NonlinearSystem` is required. Call `complete` or `structural_simplify` on the system before creating a `IntervalNonlinearFunctionExpr`") end if !isone(length(dvs)) || !isone(length(equations(sys))) error("`IntervalNonlinearFunctionExpr` only supports systems with a single equation and a single unknown.") @@ -558,7 +558,7 @@ function DiffEqBase.NonlinearProblem{iip}(sys::NonlinearSystem, u0map, parammap = DiffEqBase.NullParameters(); check_length = true, kwargs...) where {iip} if !iscomplete(sys) - error("A completed `NonlinearSystem` is required. Call `complete` or `mtkbuild` on the system before creating a `NonlinearProblem`") + error("A completed `NonlinearSystem` is required. Call `complete` or `structural_simplify` on the system before creating a `NonlinearProblem`") end f, u0, p = process_SciMLProblem(NonlinearFunction{iip}, sys, u0map, parammap; check_length, kwargs...) @@ -592,7 +592,7 @@ function DiffEqBase.NonlinearLeastSquaresProblem{iip}(sys::NonlinearSystem, u0ma parammap = DiffEqBase.NullParameters(); check_length = false, kwargs...) where {iip} if !iscomplete(sys) - error("A completed `NonlinearSystem` is required. Call `complete` or `mtkbuild` on the system before creating a `NonlinearLeastSquaresProblem`") + error("A completed `NonlinearSystem` is required. Call `complete` or `structural_simplify` on the system before creating a `NonlinearLeastSquaresProblem`") end f, u0, p = process_SciMLProblem(NonlinearFunction{iip}, sys, u0map, parammap; check_length, kwargs...) @@ -676,11 +676,11 @@ function SciMLBase.SCCNonlinearProblem{iip}(sys::NonlinearSystem, u0map, parammap = SciMLBase.NullParameters(); eval_expression = false, eval_module = @__MODULE__, cse = true, kwargs...) where {iip} if !iscomplete(sys) || get_tearing_state(sys) === nothing - error("A simplified `NonlinearSystem` is required. Call `mtkbuild` on the system before creating an `SCCNonlinearProblem`.") + error("A simplified `NonlinearSystem` is required. Call `structural_simplify` on the system before creating an `SCCNonlinearProblem`.") end if !is_split(sys) - error("The system has been simplified with `split = false`. `SCCNonlinearProblem` is not compatible with this system. Pass `split = true` to `mtkbuild` to use `SCCNonlinearProblem`.") + error("The system has been simplified with `split = false`. `SCCNonlinearProblem` is not compatible with this system. Pass `split = true` to `structural_simplify` to use `SCCNonlinearProblem`.") end ts = get_tearing_state(sys) @@ -857,7 +857,7 @@ symbolically calculating numerical enhancements. function DiffEqBase.IntervalNonlinearProblem(sys::NonlinearSystem, uspan::NTuple{2}, parammap = SciMLBase.NullParameters(); kwargs...) if !iscomplete(sys) - error("A completed `NonlinearSystem` is required. Call `complete` or `mtkbuild` on the system before creating a `IntervalNonlinearProblem`") + error("A completed `NonlinearSystem` is required. Call `complete` or `structural_simplify` on the system before creating a `IntervalNonlinearProblem`") end if !isone(length(unknowns(sys))) || !isone(length(equations(sys))) error("`IntervalNonlinearProblem` only supports with a single equation and a single unknown.") @@ -893,7 +893,7 @@ function NonlinearProblemExpr{iip}(sys::NonlinearSystem, u0map, check_length = true, kwargs...) where {iip} if !iscomplete(sys) - error("A completed `NonlinearSystem` is required. Call `complete` or `mtkbuild` on the system before creating a `NonlinearProblemExpr`") + error("A completed `NonlinearSystem` is required. Call `complete` or `structural_simplify` on the system before creating a `NonlinearProblemExpr`") end f, u0, p = process_SciMLProblem(NonlinearFunctionExpr{iip}, sys, u0map, parammap; check_length, kwargs...) @@ -933,7 +933,7 @@ function NonlinearLeastSquaresProblemExpr{iip}(sys::NonlinearSystem, u0map, check_length = false, kwargs...) where {iip} if !iscomplete(sys) - error("A completed `NonlinearSystem` is required. Call `complete` or `mtkbuild` on the system before creating a `NonlinearProblemExpr`") + error("A completed `NonlinearSystem` is required. Call `complete` or `structural_simplify` on the system before creating a `NonlinearProblemExpr`") end f, u0, p = process_SciMLProblem(NonlinearFunctionExpr{iip}, sys, u0map, parammap; check_length, kwargs...) @@ -958,7 +958,7 @@ numerical enhancements. function IntervalNonlinearProblemExpr(sys::NonlinearSystem, uspan::NTuple{2}, parammap = SciMLBase.NullParameters(); kwargs...) if !iscomplete(sys) - error("A completed `NonlinearSystem` is required. Call `complete` or `mtkbuild` on the system before creating a `IntervalNonlinearProblemExpr`") + error("A completed `NonlinearSystem` is required. Call `complete` or `structural_simplify` on the system before creating a `IntervalNonlinearProblemExpr`") end if !isone(length(unknowns(sys))) || !isone(length(equations(sys))) error("`IntervalNonlinearProblemExpr` only supports with a single equation and a single unknown.") diff --git a/src/systems/optimization/optimizationsystem.jl b/src/systems/optimization/optimizationsystem.jl index b7cc541ed7..bfe15b62d7 100644 --- a/src/systems/optimization/optimizationsystem.jl +++ b/src/systems/optimization/optimizationsystem.jl @@ -303,7 +303,7 @@ function DiffEqBase.OptimizationProblem{iip}(sys::OptimizationSystem, u0map, checks = true, cse = true, kwargs...) where {iip} if !iscomplete(sys) - error("A completed `OptimizationSystem` is required. Call `complete` or `mtkbuild` on the system before creating a `OptimizationProblem`") + error("A completed `OptimizationSystem` is required. Call `complete` or `structural_simplify` on the system before creating a `OptimizationProblem`") end if haskey(kwargs, :lcons) || haskey(kwargs, :ucons) Base.depwarn( @@ -544,7 +544,7 @@ function OptimizationProblemExpr{iip}(sys::OptimizationSystem, u0map, eval_expression = false, eval_module = @__MODULE__, kwargs...) where {iip} if !iscomplete(sys) - error("A completed `OptimizationSystem` is required. Call `complete` or `mtkbuild` on the system before creating a `OptimizationProblemExpr`") + error("A completed `OptimizationSystem` is required. Call `complete` or `structural_simplify` on the system before creating a `OptimizationProblemExpr`") end if haskey(kwargs, :lcons) || haskey(kwargs, :ucons) Base.depwarn( @@ -726,7 +726,7 @@ function OptimizationProblemExpr{iip}(sys::OptimizationSystem, u0map, end end -function mtkbuild(sys::OptimizationSystem; split = true, kwargs...) +function structural_simplify(sys::OptimizationSystem; split = true, kwargs...) sys = flatten(sys) cons = constraints(sys) econs = Equation[] @@ -739,7 +739,7 @@ function mtkbuild(sys::OptimizationSystem; split = true, kwargs...) end end nlsys = NonlinearSystem(econs, unknowns(sys), parameters(sys); name = :___tmp_nlsystem) - snlsys = mtkbuild(nlsys; fully_determined = false, kwargs...) + snlsys = structural_simplify(nlsys; fully_determined = false, kwargs...) obs = observed(snlsys) subs = Dict(eq.lhs => eq.rhs for eq in observed(snlsys)) seqs = equations(snlsys) diff --git a/src/systems/parameter_buffer.jl b/src/systems/parameter_buffer.jl index fa2bce9289..6142c95776 100644 --- a/src/systems/parameter_buffer.jl +++ b/src/systems/parameter_buffer.jl @@ -23,7 +23,7 @@ dependent systems. It is only required if the symbolic expressions also use the variable of the system. This requires that `complete` has been called on the system (usually via -`mtkbuild` or `@mtkbuild`) and the keyword `split = true` was passed (which is +`structural_simplify` or `@mtkbuild`) and the keyword `split = true` was passed (which is the default behavior). """ function MTKParameters( diff --git a/src/systems/systems.jl b/src/systems/systems.jl index 3e938578d6..7e47a21e75 100644 --- a/src/systems/systems.jl +++ b/src/systems/systems.jl @@ -33,7 +33,7 @@ function mtkbuild( disturbance_inputs = Any[], kwargs...) isscheduled(sys) && throw(RepeatedStructuralSimplificationError()) - newsys′ = __structural_simplification(sys; simplify, + newsys′ = __structural_simplify(sys; simplify, allow_symbolic, allow_parameter, conservative, fully_determined, inputs, outputs, disturbance_inputs, kwargs...) @@ -65,12 +65,12 @@ function mtkbuild( end end -function __structural_simplification(sys::JumpSystem, args...; kwargs...) +function __structural_simplify(sys::JumpSystem, args...; kwargs...) return sys end -function __structural_simplification(sys::SDESystem, args...; kwargs...) - return __structural_simplification(ODESystem(sys), args...; kwargs...) +function __structural_simplify(sys::SDESystem, args...; kwargs...) + return __structural_simplify(ODESystem(sys), args...; kwargs...) end function __structural_simplification(sys::AbstractSystem; simplify = false, @@ -94,7 +94,7 @@ function __structural_simplification(sys::AbstractSystem; simplify = false, end end if isempty(brown_vars) - return structural_simplification!(state; simplify, inputs, outputs, disturbance_inputs, kwargs...) + return structural_simplify!(state; simplify, inputs, outputs, disturbance_inputs, kwargs...) else Is = Int[] Js = Int[] @@ -127,7 +127,7 @@ function __structural_simplification(sys::AbstractSystem; simplify = false, if !iszero(new_idxs[i]) && invview(var_to_diff)[i] === nothing] # TODO: IO is not handled. - ode_sys = structural_simplification(sys; simplify, inputs, outputs, disturbance_inputs, kwargs...) + ode_sys = structural_simplify(sys; simplify, inputs, outputs, disturbance_inputs, kwargs...) eqs = equations(ode_sys) sorted_g_rows = zeros(Num, length(eqs), size(g, 2)) for (i, eq) in enumerate(eqs) @@ -170,7 +170,7 @@ end """ $(TYPEDSIGNATURES) -Given a system that has been simplified via `mtkbuild`, return a `Dict` mapping +Given a system that has been simplified via `structural_simplify`, return a `Dict` mapping variables of the system to equations that are used to solve for them. This includes observed variables. @@ -186,7 +186,7 @@ function map_variables_to_equations(sys::AbstractSystem; rename_dummy_derivative end ts = get_tearing_state(sys) if ts === nothing - throw(ArgumentError("`map_variables_to_equations` requires a simplified system. Call `mtkbuild` on the system before calling this function.")) + throw(ArgumentError("`map_variables_to_equations` requires a simplified system. Call `structural_simplify` on the system before calling this function.")) end dummy_sub = Dict() diff --git a/src/systems/systemstructure.jl b/src/systems/systemstructure.jl index 0c929c4bb3..bbad46217e 100644 --- a/src/systems/systemstructure.jl +++ b/src/systems/systemstructure.jl @@ -24,7 +24,7 @@ function quick_cancel_expr(expr) kws...))(expr) end -export SystemStructure, TransformationState, TearingState, structural_simplification! +export SystemStructure, TransformationState, TearingState, structural_simplify! export isdiffvar, isdervar, isalgvar, isdiffeq, algeqs, is_only_discrete export dervars_range, diffvars_range, algvars_range export DiffGraph, complete! @@ -657,7 +657,7 @@ function Base.show(io::IO, mime::MIME"text/plain", ms::MatchedSystemStructure) printstyled(io, " SelectedState") end -function structural_simplification!(state::TearingState; simplify = false, +function structural_simplify!(state::TearingState; simplify = false, check_consistency = true, fully_determined = true, warn_initialize_determined = true, inputs = Any[], outputs = Any[], disturbance_inputs = Any[], @@ -670,7 +670,7 @@ function structural_simplification!(state::TearingState; simplify = false, Dict(default_toterm.(state.fullvars) .=> ci.var_domain)) tss, clocked_inputs, continuous_id, id_to_clock = ModelingToolkit.split_system(ci) cont_inputs = [inputs; clocked_inputs[continuous_id]] - sys = _structural_simplification!(tss[continuous_id]; simplify, + sys = _structural_simplify!(tss[continuous_id]; simplify, check_consistency, fully_determined, inputs = cont_inputs, outputs, disturbance_inputs, kwargs...) @@ -707,14 +707,14 @@ function structural_simplification!(state::TearingState; simplify = false, for sym in get_ps(sys)] @set! sys.ps = ps else - sys = _structural_simplification!(state; simplify, check_consistency, + sys = _structural_simplify!(state; simplify, check_consistency, inputs, outputs, disturbance_inputs, fully_determined, kwargs...) end return sys end -function _structural_simplification!(state::TearingState; simplify = false, +function _structural_simplify!(state::TearingState; simplify = false, check_consistency = true, fully_determined = true, warn_initialize_determined = false, dummy_derivative = true, inputs = Any[], outputs = Any[], diff --git a/src/utils.jl b/src/utils.jl index d1645783eb..2b3cbedab0 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -159,7 +159,7 @@ function check_lhs(eq::Equation, op, dvs::Set) v = unwrap(eq.lhs) _iszero(v) && return (operation(v) isa op && only(arguments(v)) in dvs) && return - error("$v is not a valid LHS. Please run mtkbuild before simulation.") + error("$v is not a valid LHS. Please run structural_simplify before simulation.") end check_lhs(eqs, op, dvs::Set) = for eq in eqs @@ -323,7 +323,7 @@ Throw error when difference/derivative operation occurs in the R.H.S. optext = "derivative" end msg = "The $optext variable must be isolated to the left-hand " * - "side of the equation like `$opvar ~ ...`. You may want to use `mtkbuild` or the DAE form.\nGot $eq." + "side of the equation like `$opvar ~ ...`. You may want to use `structural_simplify` or the DAE form.\nGot $eq." throw(InvalidSystemException(msg)) end @@ -359,10 +359,10 @@ function check_operator_variables(eqs, op::T) where {T} is_tmp_fine = iszero(nd) end is_tmp_fine || - error("The LHS cannot contain nondifferentiated variables. Please run `mtkbuild` or use the DAE form.\nGot $eq") + error("The LHS cannot contain nondifferentiated variables. Please run `structural_simplify` or use the DAE form.\nGot $eq") for v in tmp v in ops && - error("The LHS operator must be unique. Please run `mtkbuild` or use the DAE form. $v appears in LHS more than once.") + error("The LHS operator must be unique. Please run `structural_simplify` or use the DAE form. $v appears in LHS more than once.") push!(ops, v) end empty!(tmp) diff --git a/test/accessor_functions.jl b/test/accessor_functions.jl index 3f5a22f34c..7ce477155b 100644 --- a/test/accessor_functions.jl +++ b/test/accessor_functions.jl @@ -68,10 +68,10 @@ let sys_mid2_comp = complete(sys_mid2) sys_mid1_comp = complete(sys_mid1) sys_top_comp = complete(sys_top) - sys_bot_ss = mtkbuild(sys_bot) - sys_mid2_ss = mtkbuild(sys_mid2) - sys_mid1_ss = mtkbuild(sys_mid1) - sys_top_ss = mtkbuild(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 `parameters1. @test all_sets_equal(parameters.([sys_bot, sys_bot_comp, sys_bot_ss])..., [d, p_bot]) @@ -98,7 +98,7 @@ let @test all(sym_issubset(parameters_toplevel(sys), get_ps(sys)) for sys in [sys_bot, sys_mid2, sys_mid1, sys_top]) - # Checks `unknowns`. O(t) is eliminated by `mtkbuild` 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]) diff --git a/test/analysis_points.jl b/test/analysis_points.jl index e52ac9e1a7..e36afc02f7 100644 --- a/test/analysis_points.jl +++ b/test/analysis_points.jl @@ -67,7 +67,7 @@ sys = ODESystem(eqs, t, systems = [P, C], name = :hej) nonamespace_sys = toggle_namespacing(nested_sys, false) @testset "simplifies and solves" begin - ssys = mtkbuild(sys) + ssys = structural_simplify(sys) prob = ODEProblem(ssys, [P.x => 1], (0, 10)) sol = solve(prob, Rodas5()) @test norm(sol.u[1]) >= 1 diff --git a/test/basic_transformations.jl b/test/basic_transformations.jl index f677e10066..183feca6d2 100644 --- a/test/basic_transformations.jl +++ b/test/basic_transformations.jl @@ -50,8 +50,8 @@ end M1 = ODESystem(eqs, t; initialization_eqs, name = :M) M2 = change_independent_variable(M1, s) - M1 = mtkbuild(M1; allow_symbolic = true) - M2 = mtkbuild(M2; allow_symbolic = true) + M1 = structural_simplify(M1; allow_symbolic = true) + M2 = structural_simplify(M2; allow_symbolic = true) prob1 = ODEProblem(M1, [M1.s => 1.0], (1.0, 4.0), []) prob2 = ODEProblem(M2, [], (1.0, 2.0), []) sol1 = solve(prob1, Tsit5(); reltol = 1e-10, abstol = 1e-10) @@ -100,9 +100,9 @@ end extraeqs = [Differential(M2.a)(b) ~ exp(-b), M2.a ~ exp(b)] M3 = change_independent_variable(M2, b, extraeqs) - M1 = mtkbuild(M1) - M2 = mtkbuild(M2; allow_symbolic = true) - M3 = mtkbuild(M3; allow_symbolic = true) + M1 = structural_simplify(M1) + M2 = structural_simplify(M2; allow_symbolic = true) + M3 = structural_simplify(M3; allow_symbolic = true) @test length(unknowns(M3)) == length(unknowns(M2)) == length(unknowns(M1)) - 1 end @@ -120,7 +120,7 @@ end @parameters g=9.81 v # gravitational acceleration and constant horizontal velocity Mt = ODESystem([D(D(y)) ~ -g, D(x) ~ v], t; name = :M) # gives (x, y) as function of t, ... Mx = change_independent_variable(Mt, x; add_old_diff = true) # ... but we want y as a function of x - Mx = mtkbuild(Mx; allow_symbolic = true) + Mx = structural_simplify(Mx; allow_symbolic = true) Dx = Differential(Mx.x) u0 = [Mx.y => 0.0, Dx(Mx.y) => 1.0, Mx.t => 0.0] p = [v => 10.0] @@ -134,7 +134,7 @@ end @parameters g = 9.81 # gravitational acceleration Mt = ODESystem([D(D(y)) ~ -g, D(D(x)) ~ 0], t; name = :M) # gives (x, y) as function of t, ... Mx = change_independent_variable(Mt, x; add_old_diff = true) # ... but we want y as a function of x - Mx = mtkbuild(Mx; allow_symbolic = true) + Mx = structural_simplify(Mx; allow_symbolic = true) Dx = Differential(Mx.x) u0 = [Mx.y => 0.0, Dx(Mx.y) => 1.0, Mx.t => 0.0, Mx.xˍt => 10.0] prob = ODEProblem(Mx, u0, (0.0, 20.0), []) # 1 = dy/dx = (dy/dt)/(dx/dt) means equal initial horizontal and vertical velocities @@ -152,7 +152,7 @@ end ] M1 = ODESystem(eqs, t; name = :M) M2 = change_independent_variable(M1, x; add_old_diff = true) - @test_nowarn mtkbuild(M2) + @test_nowarn structural_simplify(M2) # Compare to pen-and-paper result @variables x xˍt(x) xˍt(x) y(x) t(x) @@ -198,7 +198,7 @@ end ]) _f = LinearInterpolation([1.0, 1.0], [-100.0, +100.0]) # constant value 1 - M2s = mtkbuild(M2; allow_symbolic = true) + M2s = structural_simplify(M2; allow_symbolic = true) prob = ODEProblem(M2s, [M2s.y => 0.0], (1.0, 4.0), [fc => _f, f => _f]) sol = solve(prob, Tsit5(); abstol = 1e-5) @test isapprox(sol(4.0, idxs = M2.y), 12.0; atol = 1e-5) # Anal solution is D(y) ~ 12 => y(t) ~ 12*t + C => y(x) ~ 12*√(x) + C. With y(x=1)=0 => 12*(√(x)-1), so y(x=4) ~ 12 @@ -207,7 +207,7 @@ end @testset "Change independent variable (errors)" begin @variables x(t) y z(y) w(t) v(t) M = ODESystem([D(x) ~ 1, v ~ x], t; name = :M) - Ms = mtkbuild(M) + Ms = structural_simplify(M) @test_throws "structurally simplified" change_independent_variable(Ms, y) @test_throws "not a function of" change_independent_variable(M, y) @test_throws "not a function of" change_independent_variable(M, z) diff --git a/test/clock.jl b/test/clock.jl index 296afa899d..c6051a52a8 100644 --- a/test/clock.jl +++ b/test/clock.jl @@ -65,10 +65,10 @@ By inference: ci, varmap = infer_clocks(sys) eqmap = ci.eq_domain tss, inputs, continuous_id = ModelingToolkit.split_system(deepcopy(ci)) -sss, = ModelingToolkit._mtkbuild!( +sss, = ModelingToolkit._structural_simplify!( deepcopy(tss[continuous_id]), (inputs[continuous_id], ())) @test equations(sss) == [D(x) ~ u - x] -sss, = ModelingToolkit._mtkbuild!(deepcopy(tss[1]), (inputs[1], ())) +sss, = ModelingToolkit._structural_simplify!(deepcopy(tss[1]), (inputs[1], ())) @test isempty(equations(sss)) d = Clock(dt) k = ShiftIndex(d) @@ -115,7 +115,7 @@ eqs = [yd ~ Sample(dt)(y) D(x) ~ -x + u y ~ x] @named sys = ODESystem(eqs, t) -@test_throws ModelingToolkit.HybridSystemNotSupportedException ss=mtkbuild(sys); +@test_throws ModelingToolkit.HybridSystemNotSupportedException ss=structural_simplify(sys); @test_skip begin Tf = 1.0 @@ -128,7 +128,7 @@ eqs = [yd ~ Sample(dt)(y) [kp => 1.0; ud(k - 1) => 2.1; ud(k - 2) => 2.0]) # recreate problem to empty saved values sol = solve(prob, Tsit5(), kwargshandle = KeywordArgSilent) - ss_nosplit = mtkbuild(sys; split = false) + ss_nosplit = structural_simplify(sys; split = false) prob_nosplit = ODEProblem(ss_nosplit, [x => 0.1], (0.0, Tf), [kp => 1.0; ud(k - 1) => 2.1; ud(k - 2) => 2.0]) int = init(prob_nosplit, Tsit5(); kwargshandle = KeywordArgSilent) @@ -294,8 +294,8 @@ eqs = [yd ~ Sample(dt)(y) @test varmap[y] == ContinuousClock() @test varmap[u] == ContinuousClock() - ss = mtkbuild(cl) - ss_nosplit = mtkbuild(cl; split = false) + ss = structural_simplify(cl) + ss_nosplit = structural_simplify(cl; split = false) if VERSION >= v"1.7" prob = ODEProblem(ss, [x => 0.0], (0.0, 1.0), [kp => 1.0]) @@ -426,7 +426,7 @@ eqs = [yd ~ Sample(dt)(y) @test varmap[_model.feedback.output.u] == d @test varmap[_model.feedback.input2.u] == d - ssys = mtkbuild(model) + ssys = structural_simplify(model) Tf = 0.2 timevec = 0:(d.dt):Tf diff --git a/test/code_generation.jl b/test/code_generation.jl index 83d324cce3..cf3d660b81 100644 --- a/test/code_generation.jl +++ b/test/code_generation.jl @@ -70,7 +70,7 @@ end @parameters p[1:2] (f::Function)(..) @named sys = ODESystem( [D(x[0]) ~ p[1] * x[0] + x[2], D(x[1]) ~ p[2] * f(x) + x[2]], t) - sys, = mtkbuild(sys, ([x[2]], [])) + sys, = structural_simplify(sys, ([x[2]], [])) @test is_parameter(sys, x[2]) prob = ODEProblem(sys, [x[0] => 1.0, x[1] => 1.0], (0.0, 1.0), [p => ones(2), f => sum, x[2] => 2.0]) diff --git a/test/components.jl b/test/components.jl index b697a79787..43c0d1acf8 100644 --- a/test/components.jl +++ b/test/components.jl @@ -48,9 +48,9 @@ include("common/rc_model.jl") completed_rc_model = complete(rc_model) @test isequal(completed_rc_model.resistor.n.i, resistor.n.i) @test ModelingToolkit.n_expanded_connection_equations(capacitor) == 2 - @test length(equations(mtkbuild(rc_model, allow_parameter = false))) == 2 - sys = mtkbuild(rc_model) - @test_throws ModelingToolkit.RepeatedStructuralSimplificationError mtkbuild(sys) + @test length(equations(structural_simplify(rc_model, allow_parameter = false))) == 2 + sys = structural_simplify(rc_model) + @test_throws ModelingToolkit.RepeatedStructuralSimplificationError structural_simplify(sys) @test length(equations(sys)) == 1 check_contract(sys) @test !isempty(ModelingToolkit.defaults(sys)) @@ -61,7 +61,7 @@ include("common/rc_model.jl") end @testset "Outer/inner connections" begin - sys = mtkbuild(rc_model) + sys = structural_simplify(rc_model) prob = ODEProblem(sys, [sys.capacitor.v => 0.0], (0.0, 10.0)) sol = solve(prob, Rodas4()) @@ -91,7 +91,7 @@ end @named sys_inner_outer = compose(sys′, [ground, shape, source, rc_comp]) @test_nowarn show(IOBuffer(), MIME"text/plain"(), sys_inner_outer) expand_connections(sys_inner_outer, debug = true) - sys_inner_outer = mtkbuild(sys_inner_outer) + sys_inner_outer = structural_simplify(sys_inner_outer) @test !isempty(ModelingToolkit.defaults(sys_inner_outer)) u0 = [rc_comp.capacitor.v => 0.0] prob = ODEProblem(sys_inner_outer, u0, (0, 10.0), sparse = true) @@ -113,7 +113,7 @@ end include("common/serial_inductor.jl") @testset "Serial inductor" begin - sys = mtkbuild(ll_model) + sys = structural_simplify(ll_model) @test length(equations(sys)) == 2 u0 = unknowns(sys) .=> 0 @test_nowarn ODEProblem( @@ -122,7 +122,7 @@ include("common/serial_inductor.jl") sol = solve(prob, DFBDF()) @test sol.retcode == SciMLBase.ReturnCode.Success - sys2 = mtkbuild(ll2_model) + sys2 = structural_simplify(ll2_model) @test length(equations(sys2)) == 3 u0 = unknowns(sys) .=> 0 prob = ODEProblem(sys, u0, (0, 10.0)) @@ -181,7 +181,7 @@ function Circuit(; name) end @named foo = Circuit() -@test mtkbuild(foo) isa ModelingToolkit.AbstractSystem +@test structural_simplify(foo) isa ModelingToolkit.AbstractSystem # BLT tests @testset "BLT ordering" begin @@ -242,7 +242,7 @@ end @named _rc_model = ODESystem(rc_eqs, t) @named rc_model = compose(_rc_model, [resistor, capacitor, ground]) - sys = mtkbuild(rc_model) + sys = structural_simplify(rc_model) prob = ODEProblem(sys, [sys.c1.v => 0.0], (0, 10.0)) sol = solve(prob, Tsit5()) end @@ -288,7 +288,7 @@ end end @named outer = Outer() - simp = mtkbuild(outer) + simp = structural_simplify(outer) @test sort(propertynames(outer)) == [:inner, :t, :x] @test propertynames(simp) == propertynames(outer) @@ -305,7 +305,7 @@ end @test_throws ArgumentError outer.inner₊p end -@testset "`getproperty` on `mtkbuild(complete(sys))`" begin +@testset "`getproperty` on `structural_simplify(complete(sys))`" begin @mtkmodel Foo begin @variables begin x(t) @@ -321,7 +321,7 @@ end end @named bar = Bar() cbar = complete(bar) - ss = mtkbuild(cbar) + ss = structural_simplify(cbar) @test isequal(cbar.foo.x, ss.foo.x) end diff --git a/test/constants.jl b/test/constants.jl index 3e48c83f59..f2c4fdaa86 100644 --- a/test/constants.jl +++ b/test/constants.jl @@ -17,12 +17,12 @@ sol = solve(prob, Tsit5()) newsys = MT.eliminate_constants(sys) @test isequal(equations(newsys), [D(x) ~ 1]) -# Test mtkbuild substitutions & observed values +# Test structural_simplify substitutions & observed values eqs = [D(x) ~ 1, w ~ a] @named sys = ODESystem(eqs, t) # Now eliminate the constants first -simp = mtkbuild(sys) +simp = structural_simplify(sys) @test equations(simp) == [D(x) ~ 1.0] #Constant with units @@ -34,7 +34,7 @@ UMT.get_unit(β) D = Differential(t) eqs = [D(x) ~ β] @named sys = ODESystem(eqs, t) -simp = mtkbuild(sys) +simp = structural_simplify(sys) @test isempty(MT.collect_constants(nothing)) diff --git a/test/dde.jl b/test/dde.jl index e94e4aa5d0..c7561e6c24 100644 --- a/test/dde.jl +++ b/test/dde.jl @@ -119,11 +119,11 @@ eqs = [osc1.jcn ~ osc2.delx, @test ModelingToolkit.is_dde(coupledOsc2) @test !is_markovian(coupledOsc2) for coupledOsc in [coupledOsc, coupledOsc2] - local sys = mtkbuild(coupledOsc) + local sys = structural_simplify(coupledOsc) @test length(equations(sys)) == 4 @test length(unknowns(sys)) == 4 end -sys = mtkbuild(coupledOsc) +sys = structural_simplify(coupledOsc) prob = DDEProblem(sys, [], (0.0, 10.0); constant_lags = [sys.osc1.τ, sys.osc2.τ]) sol = solve(prob, MethodOfSteps(Tsit5())) obsfn = ModelingToolkit.build_explicit_observed_function( @@ -178,7 +178,7 @@ end eqs = [D(x(t)) ~ -w * x(t - τ)] @named sys = System(eqs, t) - sys = mtkbuild(sys) + sys = structural_simplify(sys) prob = DDEProblem(sys, [], @@ -191,7 +191,7 @@ end @brownian r eqs = [D(x(t)) ~ -w * x(t - τ) + r] @named sys = System(eqs, t) - sys = mtkbuild(sys) + sys = structural_simplify(sys) prob = SDDEProblem(sys, [], (0.0, 10.0), diff --git a/test/debugging.jl b/test/debugging.jl index 3a3cbd5d88..a55684737c 100644 --- a/test/debugging.jl +++ b/test/debugging.jl @@ -6,8 +6,8 @@ using ModelingToolkit: t_nounits as t, D_nounits as D, ASSERTION_LOG_VARIABLE @brownian a @named inner_ode = ODESystem(D(x) ~ -sqrt(x), t; assertions = [(x > 0) => "ohno"]) @named inner_sde = System([D(x) ~ -sqrt(x) + a], t; assertions = [(x > 0) => "ohno"]) -sys_ode = mtkbuild(inner_ode) -sys_sde = mtkbuild(inner_sde) +sys_ode = structural_simplify(inner_ode) +sys_sde = structural_simplify(inner_sde) @testset "assertions are present in generated `f`" begin @testset "$(typeof(sys))" for (Problem, sys, alg) in [ diff --git a/test/discrete_system.jl b/test/discrete_system.jl index b183fdca62..b0e2481e56 100644 --- a/test/discrete_system.jl +++ b/test/discrete_system.jl @@ -31,7 +31,7 @@ eqs = [S ~ S(k - 1) - infection * h, # System @named sys = DiscreteSystem(eqs, t, [S, I, R], [c, nsteps, δt, β, γ]) -syss = mtkbuild(sys) +syss = structural_simplify(sys) @test syss == syss df = DiscreteFunction(syss) @@ -254,7 +254,7 @@ end @variables x(t) y(t) k = ShiftIndex(t) @named sys = DiscreteSystem([x ~ x^2 + y^2, y ~ x(k - 1) + y(k - 1)], t) -@test_throws ["algebraic equations", "ImplicitDiscreteSystem"] mtkbuild(sys) +@test_throws ["algebraic equations", "ImplicitDiscreteSystem"] structural_simplify(sys) @testset "Passing `nothing` to `u0`" begin @variables x(t) = 1 diff --git a/test/domain_connectors.jl b/test/domain_connectors.jl index 736abfc81f..9a43d2938f 100644 --- a/test/domain_connectors.jl +++ b/test/domain_connectors.jl @@ -148,7 +148,7 @@ esys = ModelingToolkit.expand_connections(odesys) csys = complete(odesys) -sys = mtkbuild(odesys) +sys = structural_simplify(odesys) @test length(equations(sys)) == length(unknowns(sys)) sys_defs = ModelingToolkit.defaults(sys) diff --git a/test/downstream/analysis_points.jl b/test/downstream/analysis_points.jl index fddadd20c4..29b9aad512 100644 --- a/test/downstream/analysis_points.jl +++ b/test/downstream/analysis_points.jl @@ -60,7 +60,7 @@ import ControlSystemsBase as CS filt.xd => 0.0 ]) - sys = mtkbuild(closed_loop) + sys = structural_simplify(closed_loop) prob = ODEProblem(sys, unknowns(sys) .=> 0.0, (0.0, 4.0)) sol = solve(prob, Rodas5P(), reltol = 1e-6, abstol = 1e-9) @@ -100,8 +100,8 @@ end connect(F.output, sys_inner.add.input1)] sys_outer = ODESystem(eqs, t, systems = [F, sys_inner, r], name = :outer) - # test first that the mtkbuild works correctly - ssys = mtkbuild(sys_outer) + # test first that the structural_simplify works correctly + ssys = structural_simplify(sys_outer) prob = ODEProblem(ssys, Pair[], (0, 10)) @test_nowarn solve(prob, Rodas5()) @@ -136,8 +136,8 @@ end connect(F.output, sys_inner.add.input1)] sys_outer = ODESystem(eqs, t, systems = [F, sys_inner, r], name = :outer) - # test first that the mtkbuild works correctly - ssys = mtkbuild(sys_outer) + # test first that the structural_simplify works correctly + ssys = structural_simplify(sys_outer) prob = ODEProblem(ssys, Pair[], (0, 10)) @test_nowarn solve(prob, Rodas5()) @@ -172,8 +172,8 @@ end connect(F.output, sys_inner.add.input1)] sys_outer = ODESystem(eqs, t, systems = [F, sys_inner, r], name = :outer) - # test first that the mtkbuild works correctly - ssys = mtkbuild(sys_outer) + # test first that the structural_simplify works correctly + ssys = structural_simplify(sys_outer) prob = ODEProblem(ssys, Pair[], (0, 10)) @test_nowarn solve(prob, Rodas5()) @@ -363,7 +363,7 @@ end sys_normal = normal_test_system() -prob = ODEProblem(mtkbuild(sys_normal), [], (0.0, 10.0)) +prob = ODEProblem(structural_simplify(sys_normal), [], (0.0, 10.0)) @test SciMLBase.successful_retcode(solve(prob, Rodas5P())) matrices_normal, _ = get_sensitivity(sys_normal, sys_normal.normal_inner.ap) @@ -384,7 +384,7 @@ matrices_normal, _ = get_sensitivity(sys_normal, sys_normal.normal_inner.ap) connect(inner.back.output, :ap, inner.F1.input)] @named sys = ODESystem(eqs2, t; systems = [inner, step]) - prob = ODEProblem(mtkbuild(sys), [], (0.0, 10.0)) + prob = ODEProblem(structural_simplify(sys), [], (0.0, 10.0)) @test SciMLBase.successful_retcode(solve(prob, Rodas5P())) matrices, _ = get_sensitivity(sys, sys.ap) @@ -408,7 +408,7 @@ end connect(inner.back.output.u, :ap, inner.F1.input.u)] @named sys = ODESystem(eqs2, t; systems = [inner, step]) - prob = ODEProblem(mtkbuild(sys), [], (0.0, 10.0)) + prob = ODEProblem(structural_simplify(sys), [], (0.0, 10.0)) @test SciMLBase.successful_retcode(solve(prob, Rodas5P())) matrices, _ = get_sensitivity(sys, sys.ap) @@ -432,7 +432,7 @@ end connect(inner.back.output.u, :ap, inner.F1.input.u)] @named sys = ODESystem(eqs2, t; systems = [inner, step]) - prob = ODEProblem(mtkbuild(sys), [], (0.0, 10.0)) + prob = ODEProblem(structural_simplify(sys), [], (0.0, 10.0)) @test SciMLBase.successful_retcode(solve(prob, Rodas5P())) matrices, _ = get_sensitivity(sys, sys.ap) diff --git a/test/downstream/inversemodel.jl b/test/downstream/inversemodel.jl index 0251c43e38..32d5ee87ec 100644 --- a/test/downstream/inversemodel.jl +++ b/test/downstream/inversemodel.jl @@ -110,7 +110,7 @@ end end end; @named model = InverseControlledTank() -ssys = mtkbuild(model) +ssys = structural_simplify(model) cm = complete(model) op = Dict( diff --git a/test/downstream/linearize.jl b/test/downstream/linearize.jl index 738cae70fe..40cb277236 100644 --- a/test/downstream/linearize.jl +++ b/test/downstream/linearize.jl @@ -13,12 +13,11 @@ eqs = [u ~ kp * (r - y) y ~ x] @named sys = ODESystem(eqs, t) -sys = mtkbuild(sys, inputs = [r], outputs = [y]) -lsys = linearize(sys, [r], [y]) +lsys, ssys = linearize(sys, [r], [y]) lprob = LinearizationProblem(sys, [r], [y]) lsys2 = solve(lprob) -lsys3 = linearize(sys, [r], [y]; autodiff = AutoFiniteDiff()) +lsys3, _ = linearize(sys, [r], [y]; autodiff = AutoFiniteDiff()) @test lsys.A[] == lsys2.A[] == lsys3.A[] == -2 @test lsys.B[] == lsys2.B[] == lsys3.B[] == 1 @@ -87,7 +86,6 @@ connections = [f.y ~ c.r # filtered reference to controller reference p.y ~ c.y] @named cl = ODESystem(connections, t, systems = [f, c, p]) -cl = mtkbuild(cl, inputs = [f.u], outputs = [p.x]) lsys0 = linearize(cl, [f.u], [p.x]) desired_order = [f.x, p.x] @@ -203,7 +201,7 @@ lsys = linearize(sat, [u], [y]) # @test substitute(lsyss.D, ModelingToolkit.defaults(sat)) == lsys.D # outside the linear region the derivative is 0 -lsys = linearize(sat, [u], [y]; op = Dict(u => 2)) +lsys, ssys = linearize(sat, [u], [y]; op = Dict(u => 2)) @test isempty(lsys.A) # there are no differential variables in this system @test isempty(lsys.B) @test isempty(lsys.C) @@ -300,7 +298,6 @@ end @named tank_noi = Tank_noi() @unpack md_i, h, m = tank_noi -tank_noi = mtkbuild(tank_noi, inputs = [md_i], outputs = [h]) m_ss = 2.4000000003229878 @test_nowarn linearize(tank_noi, [md_i], [h]; op = Dict(m => m_ss, md_i => 2)) @@ -309,14 +306,13 @@ m_ss = 2.4000000003229878 @parameters p = 1.0 eqs = [D(x) ~ p * u, x ~ y] @named sys = ODESystem(eqs, t) -sys = mtkbuild(sys, inputs = [u]) -matrices1 = linearize(sys; op = Dict(x => 2.0)) -matrices2 = linearize(sys; op = Dict(y => 2.0)) +matrices1, _ = linearize(sys, [u], []; op = Dict(x => 2.0)) +matrices2, _ = linearize(sys, [u], []; op = Dict(y => 2.0)) @test matrices1 == matrices2 # Ensure parameter values passed as `Dict` are respected -linfun = linearization_function(sys, [u], []; op = Dict(x => 2.0)) +linfun, _ = linearization_function(sys, [u], []; op = Dict(x => 2.0)) matrices = linfun([1.0], Dict(p => 3.0), 1.0) # this would be 1 if the parameter value isn't respected @test matrices.f_u[] == 3.0 @@ -332,7 +328,7 @@ end @parameters p eqs = [0 ~ x * log(y) - p] @named sys = ODESystem(eqs, t; defaults = [p => 1.0]) - sys = mtkbuild(sys, inputs = [x]) + sys = complete(sys) @test_throws ModelingToolkit.MissingVariablesError linearize( sys, [x], []; op = Dict(x => 1.0), allow_input_derivatives = true) @test_nowarn linearize( @@ -343,7 +339,6 @@ end @testset "Symbolic values for parameters in `linearize`" begin @named tank_noi = Tank_noi() @unpack md_i, h, m, ρ, A, K = tank_noi - tank_noi = mtkbuild(tank_noi, inputs = [md_i], outputs = [h]) m_ss = 2.4000000003229878 @test_nowarn linearize( tank_noi, [md_i], [h]; op = Dict(m => m_ss, md_i => 2, ρ => A / K, A => 5)) @@ -352,7 +347,6 @@ end @testset "Warn on empty operating point" begin @named tank_noi = Tank_noi() @unpack md_i, h, m = tank_noi - tank_noi = mtkbuild(tank_noi, inputs = [md_i], outputs = [h]) m_ss = 2.4000000003229878 @test_warn ["empty operating point", "warn_empty_op"] linearize( tank_noi, [md_i], [h]; p = [md_i => 1.0]) diff --git a/test/downstream/test_disturbance_model.jl b/test/downstream/test_disturbance_model.jl index 937000d28a..97276437e2 100644 --- a/test/downstream/test_disturbance_model.jl +++ b/test/downstream/test_disturbance_model.jl @@ -55,7 +55,7 @@ end end @named model = ModelWithInputs() # Model with load disturbance -ssys = mtkbuild(model) +ssys = structural_simplify(model) prob = ODEProblem(ssys, [], (0.0, 10.0)) sol = solve(prob, Tsit5()) # plot(sol) @@ -94,10 +94,10 @@ dist(; name) = ODESystem(1 / s; name) end @named model_with_disturbance = SystemModelWithDisturbanceModel() -# ssys = mtkbuild(open_loop(model_with_disturbance, :d)) # Open loop worked, but it's a bit awkward that we have to use it here +# ssys = structural_simplify(open_loop(model_with_disturbance, :d)) # Open loop worked, but it's a bit awkward that we have to use it here # lsys2 = named_ss(model_with_disturbance, [:u, :d1], # [P.inertia1.phi, P.inertia2.phi, P.inertia1.w, P.inertia2.w]) -ssys = mtkbuild(model_with_disturbance) +ssys = structural_simplify(model_with_disturbance) prob = ODEProblem(ssys, [], (0.0, 10.0)) sol = solve(prob, Tsit5()) @test SciMLBase.successful_retcode(sol) @@ -137,10 +137,10 @@ dist3(; name) = ODESystem(ss(1 + 10 / s, balance = false); name) end @named model_with_disturbance = SystemModelWithDisturbanceModel() -# ssys = mtkbuild(open_loop(model_with_disturbance, :d)) # Open loop worked, but it's a bit awkward that we have to use it here +# ssys = structural_simplify(open_loop(model_with_disturbance, :d)) # Open loop worked, but it's a bit awkward that we have to use it here # lsys3 = named_ss(model_with_disturbance, [:u, :d1], # [P.inertia1.phi, P.inertia2.phi, P.inertia1.w, P.inertia2.w]) -ssys = mtkbuild(model_with_disturbance) +ssys = structural_simplify(model_with_disturbance) prob = ODEProblem(ssys, [], (0.0, 10.0)) sol = solve(prob, Tsit5()) @test SciMLBase.successful_retcode(sol) diff --git a/test/dq_units.jl b/test/dq_units.jl index cd5396b10d..3c59c479c1 100644 --- a/test/dq_units.jl +++ b/test/dq_units.jl @@ -113,24 +113,24 @@ noiseeqs = [0.1us"W" 0.1us"W" eqs = [D(L) ~ v, V ~ L^3] @named sys = ODESystem(eqs, t) -sys_simple = mtkbuild(sys) +sys_simple = structural_simplify(sys) eqs = [D(V) ~ r, V ~ L^3] @named sys = ODESystem(eqs, t) -sys_simple = mtkbuild(sys) +sys_simple = structural_simplify(sys) @variables V [unit = u"m"^3] L [unit = u"m"] @parameters v [unit = u"m/s"] r [unit = u"m"^3 / u"s"] eqs = [V ~ r * t, V ~ L^3] @named sys = NonlinearSystem(eqs, [V, L], [t, r]) -sys_simple = mtkbuild(sys) +sys_simple = structural_simplify(sys) eqs = [L ~ v * t, V ~ L^3] @named sys = NonlinearSystem(eqs, [V, L], [t, r]) -sys_simple = mtkbuild(sys) +sys_simple = structural_simplify(sys) #Jump System @parameters β [unit = u"(mol^2*s)^-1"] γ [unit = u"(mol*s)^-1"] jumpmol [ diff --git a/test/error_handling.jl b/test/error_handling.jl index 7a7b67a0db..d5605e3cbc 100644 --- a/test/error_handling.jl +++ b/test/error_handling.jl @@ -43,7 +43,7 @@ rc_eqs = [connect(source.p, resistor.p) connect(capacitor.n, source.n)] @named rc_model = ODESystem(rc_eqs, t, systems = [resistor, capacitor, source]) -@test_throws ModelingToolkit.ExtraVariablesSystemException mtkbuild(rc_model) +@test_throws ModelingToolkit.ExtraVariablesSystemException structural_simplify(rc_model) @named source2 = OverdefinedConstantVoltage(V = V, I = V / R) rc_eqs2 = [connect(source2.p, resistor.p) @@ -51,4 +51,4 @@ rc_eqs2 = [connect(source2.p, resistor.p) connect(capacitor.n, source2.n)] @named rc_model2 = ODESystem(rc_eqs2, t, systems = [resistor, capacitor, source2]) -@test_throws ModelingToolkit.ExtraEquationsSystemException mtkbuild(rc_model2) +@test_throws ModelingToolkit.ExtraEquationsSystemException structural_simplify(rc_model2) diff --git a/test/extensions/ad.jl b/test/extensions/ad.jl index 69b14d15ec..adaf6117c6 100644 --- a/test/extensions/ad.jl +++ b/test/extensions/ad.jl @@ -46,7 +46,7 @@ end Th0 => (4 / 11)^(1 / 3) * Tγ0, Tγ0 => (15 / π^2 * ργ0 * (2 * h)^2 / 7)^(1 / 4) / 5 ]) - sys = mtkbuild(sys) + sys = structural_simplify(sys) function x_at_0(θ) prob = ODEProblem(sys, [sys.x => 1.0], (0.0, 1.0), [sys.ργ0 => θ[1], sys.h => θ[2]]) @@ -111,7 +111,7 @@ fwd, back = ChainRulesCore.rrule(remake_buffer, sys, ps, idxs, vals) eqs = [D(D(y)) ~ -9.81] initialization_eqs = [y^2 ~ 0] # initialize y = 0 in a way that builds an initialization problem @named sys = ODESystem(eqs, t; initialization_eqs) - sys = mtkbuild(sys) + sys = structural_simplify(sys) # Find initial throw velocity that reaches exactly 10 m after 1 s dprob0 = ODEProblem(sys, [D(y) => NaN], (0.0, 1.0), []; guesses = [y => 0.0]) diff --git a/test/extensions/bifurcationkit.jl b/test/extensions/bifurcationkit.jl index 101efb202b..629edf46a6 100644 --- a/test/extensions/bifurcationkit.jl +++ b/test/extensions/bifurcationkit.jl @@ -97,14 +97,14 @@ end # Checks that default parameter values are accounted for. # Checks that observables (that depend on other observables, as in this case) are accounted for. let - # Creates model, and uses `mtkbuild` to generate observables. + # Creates model, and uses `structural_simplify` to generate observables. @parameters μ p=2 @variables x(t) y(t) z(t) eqs = [0 ~ μ - x^3 + 2x^2, 0 ~ p * μ - y, 0 ~ y - z] @named nsys = NonlinearSystem(eqs, [x, y, z], [μ, p]) - nsys = mtkbuild(nsys) + nsys = structural_simplify(nsys) # Creates BifurcationProblem. bif_par = μ diff --git a/test/funcaffect.jl b/test/funcaffect.jl index c7c5ad80d7..05543c9161 100644 --- a/test/funcaffect.jl +++ b/test/funcaffect.jl @@ -131,7 +131,7 @@ s1 = compose( ODESystem(Equation[], t, [], [], name = :s1, discrete_events = 1.0 => (affect4!, [resistor.v], [], [], ctx)), resistor) -s2 = mtkbuild(s1) +s2 = structural_simplify(s1) prob = ODEProblem(s2, [resistor.v => 10.0], (0, 2.01)) sol = solve(prob, Tsit5()) @test ctx[1] == 2 @@ -152,7 +152,7 @@ end rc_model = extend(rc_model, event_sys) # rc_model = compose(rc_model, [resistor, capacitor, source, ground]) -sys = mtkbuild(rc_model) +sys = structural_simplify(rc_model) u0 = [capacitor.v => 0.0 capacitor.p.i => 0.0 resistor.v => 0.0] @@ -199,7 +199,7 @@ rc_eqs2 = [connect(shape.output, source.V) @named rc_model2 = ODESystem(rc_eqs2, t) rc_model2 = compose(rc_model2, [resistor, capacitor2, shape, source, ground]) -sys2 = mtkbuild(rc_model2) +sys2 = structural_simplify(rc_model2) u0 = [capacitor2.v => 0.0 capacitor2.p.i => 0.0 resistor.v => 0.0] @@ -288,7 +288,7 @@ end [y ~ zr] => (bb_affect!, [v], [], [], nothing) ]) -bb_sys = mtkbuild(bb_model) +bb_sys = structural_simplify(bb_model) @test only(ModelingToolkit.affects(ModelingToolkit.continuous_events(bb_sys))) isa ModelingToolkit.FunctionalAffect diff --git a/test/guess_propagation.jl b/test/guess_propagation.jl index ae188ba2df..738e930adc 100644 --- a/test/guess_propagation.jl +++ b/test/guess_propagation.jl @@ -11,7 +11,7 @@ eqs = [D(x) ~ 1 initialization_eqs = [1 ~ exp(1 + x)] @named sys = ODESystem(eqs, t; initialization_eqs) -sys = complete(mtkbuild(sys)) +sys = complete(structural_simplify(sys)) tspan = (0.0, 0.2) prob = ODEProblem(sys, [], tspan, []) @@ -28,7 +28,7 @@ eqs = [D(x) ~ 1 initialization_eqs = [1 ~ exp(1 + x)] @named sys = ODESystem(eqs, t; initialization_eqs) -sys = complete(mtkbuild(sys)) +sys = complete(structural_simplify(sys)) tspan = (0.0, 0.2) prob = ODEProblem(sys, [], tspan, []) @@ -46,7 +46,7 @@ eqs = [D(x) ~ a] initialization_eqs = [1 ~ exp(1 + x)] @named sys = ODESystem(eqs, t; initialization_eqs) -sys = complete(mtkbuild(sys)) +sys = complete(structural_simplify(sys)) tspan = (0.0, 0.2) prob = ODEProblem(sys, [], tspan, []) @@ -66,7 +66,7 @@ eqs = [D(x) ~ a, initialization_eqs = [1 ~ exp(1 + x)] @named sys = ODESystem(eqs, t; initialization_eqs) -sys = complete(mtkbuild(sys)) +sys = complete(structural_simplify(sys)) tspan = (0.0, 0.2) prob = ODEProblem(sys, [], tspan, []) diff --git a/test/hierarchical_initialization_eqs.jl b/test/hierarchical_initialization_eqs.jl index 022c5c4431..1e3109a66e 100644 --- a/test/hierarchical_initialization_eqs.jl +++ b/test/hierarchical_initialization_eqs.jl @@ -140,7 +140,7 @@ syslist = ModelingToolkit.get_systems(model) @test length(ModelingToolkit.initialization_equations(model)) == 2 u0 = [] -prob = ODEProblem(mtkbuild(model), u0, (0.0, 10.0)) +prob = ODEProblem(structural_simplify(model), u0, (0.0, 10.0)) sol = solve(prob, Rodas5P()) @test length(sol.u[end]) == 2 @test length(equations(prob.f.initializeprob.f.sys)) == 0 diff --git a/test/if_lifting.jl b/test/if_lifting.jl index b6f5ac3387..9c58e676d0 100644 --- a/test/if_lifting.jl +++ b/test/if_lifting.jl @@ -13,9 +13,9 @@ using ModelingToolkit: t_nounits as t, D_nounits as D, IfLifting, no_if_lift end end @named sys = SimpleAbs() - ss1 = mtkbuild(sys) + ss1 = structural_simplify(sys) @test length(equations(ss1)) == 1 - ss2 = mtkbuild(sys, additional_passes = [IfLifting]) + ss2 = structural_simplify(sys, additional_passes = [IfLifting]) @test length(equations(ss2)) == 1 @test length(parameters(ss2)) == 1 @test operation(only(equations(ss2)).rhs) === ifelse @@ -71,7 +71,7 @@ end end @named sys = BigModel() - ss = mtkbuild(sys, additional_passes = [IfLifting]) + ss = structural_simplify(sys, additional_passes = [IfLifting]) ps = parameters(ss) @test length(ps) == 9 diff --git a/test/initializationsystem.jl b/test/initializationsystem.jl index 2d90940983..5c36fcba3e 100644 --- a/test/initializationsystem.jl +++ b/test/initializationsystem.jl @@ -415,7 +415,7 @@ sol = solve(prob, Tsit5()) D(z) ~ x * y - β * z] @named sys = ODESystem(eqs, t) - sys = mtkbuild(sys) + sys = structural_simplify(sys) u0 = [D(x) => 2.0, x => 1.0, @@ -444,7 +444,7 @@ eqs = [D(x) ~ α * x - β * x * y z ~ x + y] @named sys = ODESystem(eqs, t) -simpsys = mtkbuild(sys) +simpsys = structural_simplify(sys) tspan = (0.0, 10.0) prob = ODEProblem(simpsys, [D(x) => 0.0, y => 0.0], tspan, guesses = [x => 0.0]) @@ -477,7 +477,7 @@ prob = ODEProblem(pend, [x => 1], (0.0, 1.5), [g => 1], guesses = [λ => 0, y => 1], initialization_eqs = [y ~ 1]) unsimp = generate_initializesystem(pend; u0map = [x => 1], initialization_eqs = [y ~ 1]) -sys = mtkbuild(unsimp; fully_determined = false) +sys = structural_simplify(unsimp; fully_determined = false) @test length(equations(sys)) in (3, 4) # could be either depending on tearing # Extend two systems with initialization equations and guesses @@ -493,7 +493,7 @@ sys = extend(sysx, sysy) @testset "Error on missing defaults" begin @variables x(t) y(t) @named sys = ODESystem([x^2 + y^2 ~ 25, D(x) ~ 1], t) - ssys = mtkbuild(sys) + ssys = structural_simplify(sys) @test_throws ModelingToolkit.MissingVariablesError ODEProblem( ssys, [x => 3], (0, 1), []) # y should have a guess end @@ -504,7 +504,7 @@ end # system 1 should solve to x = 1 ics1 = [x => 1] - sys1 = ODESystem([D(x) ~ 0], t; defaults = ics1, name = :sys1) |> mtkbuild + sys1 = ODESystem([D(x) ~ 0], t; defaults = ics1, name = :sys1) |> structural_simplify prob1 = ODEProblem(sys1, [], (0.0, 1.0), []) sol1 = solve(prob1, Tsit5()) @test all(sol1[x] .== 1) @@ -513,7 +513,7 @@ end sys2 = extend( sys1, ODESystem([D(y) ~ 0], t; initialization_eqs = [y ~ 2], name = :sys2) - ) |> mtkbuild + ) |> structural_simplify ics2 = unknowns(sys1) .=> 2 # should be equivalent to "ics2 = [x => 2]" prob2 = ODEProblem(sys2, ics2, (0.0, 1.0), []; fully_determined = true) sol2 = solve(prob2, Tsit5()) @@ -525,12 +525,12 @@ end @variables x(t) sys = ODESystem( [D(D(x)) ~ 0], t; initialization_eqs = [x ~ 0, D(x) ~ 1], name = :sys) |> - mtkbuild + structural_simplify @test_nowarn ODEProblem(sys, [], (0.0, 1.0), []) sys = ODESystem( [D(D(x)) ~ 0], t; initialization_eqs = [x ~ 0, D(D(x)) ~ 0], name = :sys) |> - mtkbuild + structural_simplify @test_nowarn ODEProblem(sys, [D(x) => 1.0], (0.0, 1.0), []) end @@ -541,7 +541,7 @@ end sys = ODESystem( [D(D(x)) ~ 0], t; initialization_eqs = [D(x)^2 ~ 1, x ~ 0], guesses = [D(x) => sign], name = :sys - ) |> mtkbuild + ) |> structural_simplify prob = ODEProblem(sys, [], (0.0, 1.0), []) sol = solve(prob, Tsit5()) @test sol(1.0, idxs = sys.x) ≈ sign # system with D(x(0)) = ±1 should solve to x(1) = ±1 @@ -582,7 +582,7 @@ sol = solve(oprob_2nd_order_2, Rosenbrock23()) # retcode: Success @testset "Vector in initial conditions" begin @variables x(t)[1:5] y(t)[1:5] @named sys = ODESystem([D(x) ~ x, D(y) ~ y], t; initialization_eqs = [y ~ -x]) - sys = mtkbuild(sys) + sys = structural_simplify(sys) prob = ODEProblem(sys, [sys.x => ones(5)], (0.0, 1.0), []) sol = solve(prob, Tsit5(), reltol = 1e-4) @test all(sol(1.0, idxs = sys.x) .≈ +exp(1)) && all(sol(1.0, idxs = sys.y) .≈ -exp(1)) @@ -1145,7 +1145,7 @@ end end model = dc_motor() - sys = mtkbuild(model) + sys = structural_simplify(model) prob = ODEProblem(sys, [sys.L1.i => 0.0], (0, 6.0)) @@ -1250,7 +1250,7 @@ end @parameters a = 1 @named sys = ODESystem([D(x) ~ 0, D(y) ~ x + a], t; initialization_eqs = [y ~ a]) - ssys = mtkbuild(sys) + ssys = structural_simplify(sys) prob = ODEProblem(ssys, [], (0, 1), []) @test SciMLBase.successful_retcode(solve(prob)) @@ -1355,7 +1355,7 @@ end continuous_events = [ [y ~ 0.5] => (stop!, [y], [], [], nothing) ]) - sys = mtkbuild(sys) + sys = structural_simplify(sys) prob0 = ODEProblem(sys, [x => NaN], (0.0, 1.0), []) # final_x(x0) is equivalent to x0 + 0.5 diff --git a/test/input_output_handling.jl b/test/input_output_handling.jl index 0a211de9de..693a00b9ad 100644 --- a/test/input_output_handling.jl +++ b/test/input_output_handling.jl @@ -7,10 +7,10 @@ using ModelingToolkit: t_nounits as t, D_nounits as D @variables xx(t) some_input(t) [input = true] eqs = [D(xx) ~ some_input] @named model = ODESystem(eqs, t) -@test_throws ExtraVariablesSystemException mtkbuild(model, ((), ())) +@test_throws ExtraVariablesSystemException structural_simplify(model, ((), ())) if VERSION >= v"1.8" err = "In particular, the unset input(s) are:\n some_input(t)" - @test_throws err mtkbuild(model, ((), ())) + @test_throws err structural_simplify(model, ((), ())) end # Test input handling @@ -50,7 +50,7 @@ end @test !is_bound(sys31, sys1.v[2]) # simplification turns input variables into parameters -ssys, _ = mtkbuild(sys, ([u], [])) +ssys, _ = structural_simplify(sys, ([u], [])) @test ModelingToolkit.isparameter(unbound_inputs(ssys)[]) @test !is_bound(ssys, u) @test u ∈ Set(unbound_inputs(ssys)) @@ -88,7 +88,7 @@ fsys4 = flatten(sys4) @variables x(t) y(t) [output = true] @test isoutput(y) @named sys = ODESystem([D(x) ~ -x, y ~ x], t) # both y and x are unbound -syss = mtkbuild(sys) # This makes y an observed variable +syss = structural_simplify(sys) # This makes y an observed variable @named sys2 = ODESystem([D(x) ~ -sys.x, y ~ sys.y], t, systems = [sys]) @@ -106,7 +106,7 @@ syss = mtkbuild(sys) # This makes y an observed variable @test isequal(unbound_outputs(sys2), [y]) @test isequal(bound_outputs(sys2), [sys.y]) -syss = mtkbuild(sys2) +syss = structural_simplify(sys2) @test !is_bound(syss, y) @test !is_bound(syss, x) @@ -281,7 +281,7 @@ i = findfirst(isequal(u[1]), out) @variables x(t) u(t) [input = true] eqs = [D(x) ~ u] @named sys = ODESystem(eqs, t) -@test_nowarn mtkbuild(sys, ([u], [])) +@test_nowarn structural_simplify(sys, ([u], [])) #= ## Disturbance input handling @@ -366,7 +366,7 @@ eqs = [D(y₁) ~ -k₁ * y₁ + k₃ * y₂ * y₃ + u1 @named sys = ODESystem(eqs, t) m_inputs = [u[1], u[2]] m_outputs = [y₂] -sys_simp, input_idxs = mtkbuild(sys, (; inputs = m_inputs, outputs = m_outputs)) +sys_simp, input_idxs = structural_simplify(sys, (; inputs = m_inputs, outputs = m_outputs)) @test isequal(unknowns(sys_simp), collect(x[1:2])) @test length(input_idxs) == 2 @@ -384,12 +384,12 @@ sys_simp, input_idxs = mtkbuild(sys, (; inputs = m_inputs, outputs = m_outputs)) ], t, systems = [int, gain, c, fb]) -sys = mtkbuild(model) +sys = structural_simplify(model) @test length(unknowns(sys)) == length(equations(sys)) == 1 ## Disturbance models when plant has multiple inputs using ModelingToolkit, LinearAlgebra -using ModelingToolkit: DisturbanceModel, get_iv, get_disturbance_system +using ModelingToolkit: DisturbanceModel, io_preprocessing, get_iv, get_disturbance_system using ModelingToolkitStandardLibrary.Blocks A, C = [randn(2, 2) for i in 1:2] B = [1.0 0; 0 1.0] diff --git a/test/jumpsystem.jl b/test/jumpsystem.jl index 488b9396f6..6c96055270 100644 --- a/test/jumpsystem.jl +++ b/test/jumpsystem.jl @@ -269,7 +269,7 @@ dp4 = DiscreteProblem(js4, u0, tspan) @test_nowarn jp3 = JumpProblem(js3, dp3, Direct()) @test_nowarn jp4 = JumpProblem(js4, dp4, Direct()) -# Ensure `mtkbuild` (and `@mtkbuild`) works on JumpSystem (by doing nothing) +# Ensure `structural_simplify` (and `@mtkbuild`) works on JumpSystem (by doing nothing) # Issue#2558 @parameters k @variables X(t) diff --git a/test/modelingtoolkitize.jl b/test/modelingtoolkitize.jl index 4ce25602d2..db99cc91a4 100644 --- a/test/modelingtoolkitize.jl +++ b/test/modelingtoolkitize.jl @@ -440,7 +440,7 @@ prob = NonlinearLeastSquaresProblem( NonlinearFunction(nlls!, resid_prototype = zeros(3)), u0) sys = modelingtoolkitize(prob) @test length(equations(sys)) == 3 -@test length(equations(mtkbuild(sys; fully_determined = false))) == 0 +@test length(equations(structural_simplify(sys; fully_determined = false))) == 0 @testset "`modelingtoolkitize(::SDEProblem)` sets defaults" begin function sdeg!(du, u, p, t) diff --git a/test/mtkparameters.jl b/test/mtkparameters.jl index 10bda707d2..55de0768e0 100644 --- a/test/mtkparameters.jl +++ b/test/mtkparameters.jl @@ -170,7 +170,7 @@ function level1() eqs = [D(x) ~ p1 * x - p2 * x * y D(y) ~ -p3 * y + p4 * x * y] - sys = mtkbuild(complete(ODESystem( + sys = structural_simplify(complete(ODESystem( eqs, t, tspan = (0, 3.0), name = :sys, parameter_dependencies = [y0 => 2p4]))) prob = ODEProblem{true, SciMLBase.FullSpecialize}(sys) end @@ -184,7 +184,7 @@ function level2() eqs = [D(x) ~ p1 * x - p23[1] * x * y D(y) ~ -p23[2] * y + p4 * x * y] - sys = mtkbuild(complete(ODESystem( + sys = structural_simplify(complete(ODESystem( eqs, t, tspan = (0, 3.0), name = :sys, parameter_dependencies = [y0 => 2p4]))) prob = ODEProblem{true, SciMLBase.FullSpecialize}(sys) end @@ -198,7 +198,7 @@ function level3() eqs = [D(x) ~ p1 * x - p23[1] * x * y D(y) ~ -p23[2] * y + p4 * x * y] - sys = mtkbuild(complete(ODESystem( + sys = structural_simplify(complete(ODESystem( eqs, t, tspan = (0, 3.0), name = :sys, parameter_dependencies = [y0 => 2p4]))) prob = ODEProblem{true, SciMLBase.FullSpecialize}(sys) end diff --git a/test/nonlinearsystem.jl b/test/nonlinearsystem.jl index 06f0122724..a315371141 100644 --- a/test/nonlinearsystem.jl +++ b/test/nonlinearsystem.jl @@ -121,7 +121,7 @@ using OrdinaryDiffEq D = Differential(t) @named subsys = convert_system(ODESystem, lorenz1, t) @named sys = ODESystem([D(subsys.x) ~ subsys.x + subsys.x], t, systems = [subsys]) -sys = mtkbuild(sys) +sys = structural_simplify(sys) u0 = [subsys.x => 1, subsys.z => 2.0, subsys.y => 1.0] prob = ODEProblem(sys, u0, (0, 1.0), [subsys.σ => 1, subsys.ρ => 2, subsys.β => 3]) sol = solve(prob, FBDF(), reltol = 1e-7, abstol = 1e-7) @@ -198,7 +198,7 @@ eq = [v1 ~ sin(2pi * t * h) v2 ~ i2 i1 ~ i2] @named sys = ODESystem(eq, t) -@test length(equations(mtkbuild(sys))) == 0 +@test length(equations(structural_simplify(sys))) == 0 @testset "Issue: 1504" begin @variables u[1:4] @@ -257,7 +257,7 @@ end @named ns = NonlinearSystem(eqs, [x, y, z], []) ns = complete(ns) vs = [unknowns(ns); parameters(ns)] - ss_mtk = mtkbuild(ns) + ss_mtk = structural_simplify(ns) prob = NonlinearProblem(ss_mtk, vs .=> 1.0) sol = solve(prob) @test_nowarn sol[unknowns(ns)] @@ -277,16 +277,16 @@ sys = @test_nowarn NonlinearSystem(alg_eqs; name = :name) @parameters u3 u4 eqs = [u3 ~ u1 + u2, u4 ~ 2 * (u1 + u2), u3 + u4 ~ 3 * (u1 + u2)] @named ns = NonlinearSystem(eqs, [u1, u2], [u3, u4]) -sys = mtkbuild(ns; fully_determined = false) +sys = structural_simplify(ns; fully_determined = false) @test length(unknowns(sys)) == 1 # Conservative @variables X(t) alg_eqs = [1 ~ 2X] @named ns = NonlinearSystem(alg_eqs) -sys = mtkbuild(ns) +sys = structural_simplify(ns) @test length(equations(sys)) == 0 -sys = mtkbuild(ns; conservative = true) +sys = structural_simplify(ns; conservative = true) @test length(equations(sys)) == 1 # https://github.com/SciML/ModelingToolkit.jl/issues/2858 @@ -338,7 +338,7 @@ end -1 1/2 -1] b = [1, -2, 0] @named sys = NonlinearSystem(A * x ~ b, [x], []) - sys = mtkbuild(sys) + sys = structural_simplify(sys) prob = NonlinearProblem(sys, unknowns(sys) .=> 0.0) sol = solve(prob) @test all(sol[x] .≈ A \ b) @@ -349,8 +349,8 @@ end @parameters p @named sys = NonlinearSystem([x ~ 1, x^2 - p ~ 0]) for sys in [ - mtkbuild(sys, fully_determined = false), - mtkbuild(sys, fully_determined = false, split = false) + structural_simplify(sys, fully_determined = false), + structural_simplify(sys, fully_determined = false, split = false) ] @test length(equations(sys)) == 1 @test length(unknowns(sys)) == 0 @@ -424,7 +424,7 @@ end @test ModelingToolkit.iscomplete(nlsys) @test ModelingToolkit.is_split(nlsys) - sys3 = mtkbuild(sys) + sys3 = structural_simplify(sys) nlsys = NonlinearSystem(sys3) @test length(equations(nlsys)) == length(ModelingToolkit.observed(nlsys)) == 1 diff --git a/test/odesystem.jl b/test/odesystem.jl index 27a1a3432e..f5fef1fd6f 100644 --- a/test/odesystem.jl +++ b/test/odesystem.jl @@ -478,7 +478,7 @@ end let @variables x(t)[1:3, 1:3] @named sys = ODESystem(D.(x) .~ x, t) - @test_nowarn mtkbuild(sys) + @test_nowarn structural_simplify(sys) end # Array vars @@ -489,7 +489,7 @@ ps = @parameters p[1:3] = [1, 2, 3] eqs = [collect(D.(x) .~ x) D(y) ~ norm(collect(x)) * y - x[1]] @named sys = ODESystem(eqs, t, sts, ps) -sys = mtkbuild(sys) +sys = structural_simplify(sys) @test isequal(@nonamespace(sys.x), x) @test isequal(@nonamespace(sys.y), y) @test isequal(@nonamespace(sys.p), p) @@ -661,7 +661,7 @@ let D(x[2]) ~ -x[1] - 0.5 * x[2] + k y ~ 0.9 * x[1] + x[2]] @named sys = ODESystem(eqs, t, vcat(x, [y]), [k], defaults = Dict(x .=> 0)) - sys = mtkbuild(sys) + sys = structural_simplify(sys) u0 = [0.5, 0] du0 = 0 .* copy(u0) @@ -743,7 +743,7 @@ let 0 ~ q / C - R * F] @named sys = ODESystem(eqs, t) - @test length(equations(mtkbuild(sys))) == 2 + @test length(equations(structural_simplify(sys))) == 2 end let @@ -778,12 +778,12 @@ let @named sys1 = ODESystem(eqs, t) @named sys2 = ODESystem(eqs2, t) @named sys3 = ODESystem(eqs3, t) - ssys3 = mtkbuild(sys3) + ssys3 = structural_simplify(sys3) @named sys4 = ODESystem(eqs4, t) @test ModelingToolkit.isisomorphic(sys1, sys2) @test !ModelingToolkit.isisomorphic(sys1, sys3) - @test ModelingToolkit.isisomorphic(sys1, ssys3) # I don't call mtkbuild in isisomorphic + @test ModelingToolkit.isisomorphic(sys1, ssys3) # I don't call structural_simplify in isisomorphic @test !ModelingToolkit.isisomorphic(sys1, sys4) # 1281 @@ -801,7 +801,7 @@ let spm ~ 0 sph ~ a] @named sys = ODESystem(eqs, t, vars, pars) - @test_throws ModelingToolkit.ExtraEquationsSystemException mtkbuild(sys) + @test_throws ModelingToolkit.ExtraEquationsSystemException structural_simplify(sys) end # 1561 @@ -825,9 +825,9 @@ let ps = [] @named sys = ODESystem(eqs, t, u, ps) - @test_nowarn simpsys = mtkbuild(sys) + @test_nowarn simpsys = structural_simplify(sys) - sys = mtkbuild(sys) + sys = structural_simplify(sys) u0 = ModelingToolkit.missing_variable_defaults(sys) u0_expected = Pair[s => 0.0 for s in unknowns(sys)] @@ -913,7 +913,7 @@ let @named connected = ODESystem(connections, t) @named sys_con = compose(connected, sys, ctrl) - sys_simp = mtkbuild(sys_con) + sys_simp = structural_simplify(sys_con) true_eqs = [D(sys.x) ~ sys.v D(sys.v) ~ ctrl.kv * sys.v + ctrl.kx * sys.x] @test issetequal(full_equations(sys_simp), true_eqs) @@ -924,7 +924,7 @@ let @variables y(t) = 1 @parameters pp = -1 @named sys4 = ODESystem([D(x) ~ -y; D(y) ~ 1 + pp * y + x], t) - sys4s = mtkbuild(sys4) + sys4s = structural_simplify(sys4) prob = ODEProblem(sys4s, [x => 1.0, D(x) => 1.0], (0, 1.0)) @test string.(unknowns(prob.f.sys)) == ["x(t)", "y(t)"] @test string.(parameters(prob.f.sys)) == ["pp"] @@ -963,7 +963,7 @@ let @parameters pp = -1 der = Differential(t) @named sys4 = ODESystem([der(x) ~ -y; der(y) ~ 1 + pp * y + x], t) - sys4s = mtkbuild(sys4) + sys4s = structural_simplify(sys4) prob = ODEProblem(sys4s, [x => 1.0, D(x) => 1.0], (0, 1.0)) @test !isnothing(prob.f.sys) end @@ -999,7 +999,7 @@ let # Issue https://github.com/SciML/ModelingToolkit.jl/issues/2322 sys = ODESystem(eqs, t; name = :kjshdf) - sys_simp = mtkbuild(sys) + sys_simp = structural_simplify(sys) @test a ∈ keys(ModelingToolkit.defaults(sys_simp)) @@ -1140,7 +1140,7 @@ orig_vars = unknowns(sys) @named outer = ODESystem( [D(y) ~ sys.x + t, 0 ~ t + y - sys.x * y], t, [y], []; systems = [sys]) @test ModelingToolkit.guesses(outer)[sys.x] == 1.0 -outer = mtkbuild(outer) +outer = structural_simplify(outer) @test ModelingToolkit.get_guesses(outer)[sys.x] == 1.0 prob = ODEProblem(outer, [outer.y => 2.0], (0.0, 10.0)) int = init(prob, Rodas4()) @@ -1176,7 +1176,7 @@ end @testset "Non-1-indexed variable array (issue #2670)" begin @variables x(t)[0:1] # 0-indexed variable array @named sys = ODESystem([x[0] ~ 0.0, D(x[1]) ~ x[0]], t, [x], []) - @test_nowarn sys = mtkbuild(sys) + @test_nowarn sys = structural_simplify(sys) @test equations(sys) == [D(x[1]) ~ 0.0] end @@ -1190,7 +1190,7 @@ end @testset "ForwardDiff through ODEProblem constructor" begin @parameters P @variables x(t) - sys = mtkbuild(ODESystem([D(x) ~ P], t, [x], [P]; name = :sys)) + sys = structural_simplify(ODESystem([D(x) ~ P], t, [x], [P]; name = :sys)) function x_at_1(P) prob = ODEProblem(sys, [x => P], (0.0, 1.0), [sys.P => P]) @@ -1203,7 +1203,7 @@ end @testset "Inplace observed functions" begin @parameters P @variables x(t) - sys = mtkbuild(ODESystem([D(x) ~ P], t, [x], [P]; name = :sys)) + sys = structural_simplify(ODESystem([D(x) ~ P], t, [x], [P]; name = :sys)) obsfn = ModelingToolkit.build_explicit_observed_function( sys, [x + 1, x + P, x + t], return_inplace = true)[2] ps = ModelingToolkit.MTKParameters(sys, [P => 2.0]) @@ -1240,7 +1240,7 @@ end initialization_eqs = [x ~ T] guesses = [x => 0.0] @named sys2 = ODESystem(eqs, T; initialization_eqs, guesses) - prob2 = ODEProblem(mtkbuild(sys2), [], (1.0, 2.0), []) + prob2 = ODEProblem(structural_simplify(sys2), [], (1.0, 2.0), []) sol2 = solve(prob2) @test all(sol2[x] .== 1.0) end @@ -1265,7 +1265,7 @@ end eqs = [D(x) ~ 0, y ~ x, D(z) ~ 0] defaults = [x => 1, z => y] @named sys = ODESystem(eqs, t; defaults) - ssys = mtkbuild(sys) + ssys = structural_simplify(sys) prob = ODEProblem(ssys, [], (0.0, 1.0), []) @test prob[x] == prob[y] == prob[z] == 1.0 @@ -1274,7 +1274,7 @@ end eqs = [D(x) ~ 0, y ~ y0 / x, D(z) ~ y] defaults = [y0 => 1, x => 1, z => y] @named sys = ODESystem(eqs, t; defaults) - ssys = mtkbuild(sys) + ssys = structural_simplify(sys) prob = ODEProblem(ssys, [], (0.0, 1.0), []) @test prob[x] == prob[y] == prob[z] == 1.0 end @@ -1285,11 +1285,11 @@ end @named sys = ODESystem( [D(u) ~ (sum(u) + sum(x) + sum(p) + sum(o)) * x, o ~ prod(u) * x], t, [u..., x..., o...], [p...]) - sys1, = mtkbuild(sys, ([x...], [])) + sys1, = structural_simplify(sys, ([x...], [])) fn1, = ModelingToolkit.generate_function(sys1; expression = Val{false}) ps = MTKParameters(sys1, [x => 2ones(2), p => 3ones(2, 2)]) @test_nowarn fn1(ones(4), ps, 4.0) - sys2, = mtkbuild(sys, ([x...], []); split = false) + sys2, = structural_simplify(sys, ([x...], []); split = false) fn2, = ModelingToolkit.generate_function(sys2; expression = Val{false}) ps = zeros(8) setp(sys2, x)(ps, 2ones(2)) @@ -1373,10 +1373,10 @@ end @named outersys = ODESystem( [D(innersys.y) ~ innersys.y + p4], t; parameter_dependencies = [p4 ~ 3p3], defaults = [p3 => 3.0, p4 => 9.0], guesses = [p4 => 10.0], systems = [innersys]) - @test_nowarn mtkbuild(outersys) + @test_nowarn structural_simplify(outersys) @parameters p5 sys2 = substitute(outersys, [p4 => p5]) - @test_nowarn mtkbuild(sys2) + @test_nowarn structural_simplify(sys2) @test length(equations(sys2)) == 2 @test length(parameters(sys2)) == 2 @test length(full_parameters(sys2)) == 4 @@ -1398,7 +1398,7 @@ end o[2] ~ sum(p) * sum(x)] @named sys = ODESystem(eqs, t, [u..., x..., o], [p...]) - sys1, = mtkbuild(sys, ([x...], [o...]), split = false) + sys1, = structural_simplify(sys, ([x...], [o...]), split = false) @test_nowarn ModelingToolkit.build_explicit_observed_function(sys1, u; inputs = [x...]) @@ -1436,7 +1436,7 @@ end @test_nowarn ODEProblem(sys, [], (0.0, 1.0)) else @test_throws [ - r"array (equations|unknowns)", "mtkbuild", "scalarize"] ODEProblem( + r"array (equations|unknowns)", "structural_simplify", "scalarize"] ODEProblem( sys, [], (0.0, 1.0)) end end @@ -1449,7 +1449,7 @@ end @test_nowarn ODEProblem(sys, [], (0.0, 1.0)) else @test_throws [ - r"array (equations|unknowns)", "mtkbuild", "scalarize"] ODEProblem( + r"array (equations|unknowns)", "structural_simplify", "scalarize"] ODEProblem( sys, [], (0.0, 1.0)) end end @@ -1632,7 +1632,7 @@ end @test lowered_vars == expected_vars end -@testset "dae_order_lowering test with mtkbuild" begin +@testset "dae_order_lowering test with structural_simplify" begin @variables x(t) y(t) z(t) @parameters M b k eqs = [ @@ -1647,7 +1647,7 @@ end default_p = [M => 1.0, b => 1.0, k => 1.0] @named dae_sys = ODESystem(eqs, t, [x, y, z], ps; defaults = [default_u0; default_p]) - simplified_dae_sys = mtkbuild(dae_sys) + simplified_dae_sys = structural_simplify(dae_sys) lowered_dae_sys = dae_order_lowering(simplified_dae_sys) lowered_dae_sys = complete(lowered_dae_sys) diff --git a/test/optimizationsystem.jl b/test/optimizationsystem.jl index 9d6053a45b..2ec9516721 100644 --- a/test/optimizationsystem.jl +++ b/test/optimizationsystem.jl @@ -85,7 +85,7 @@ end z ~ y - x^2 z^2 + y^2 ≲ 1.0] @named sys = OptimizationSystem(loss, [x, y, z], [a, b], constraints = cons) - sys = mtkbuild(sys) + sys = structural_simplify(sys) prob = OptimizationProblem(sys, [x => 0.0, y => 0.0, z => 0.0], [a => 1.0, b => 1.0], grad = true, hess = true, cons_j = true, cons_h = true) sol = solve(prob, IPNewton()) diff --git a/test/parameter_dependencies.jl b/test/parameter_dependencies.jl index beffacc83f..31881e1ca8 100644 --- a/test/parameter_dependencies.jl +++ b/test/parameter_dependencies.jl @@ -170,7 +170,7 @@ end # (https://github.com/SciML/ModelingToolkit.jl/pull/2978) @inferred ModelingToolkit.parameter_dependencies(sys1) - sys = mtkbuild(sys1) + sys = structural_simplify(sys1) prob = ODEProblem(sys, [], (0.0, 1.0)) sol = solve(prob) @@ -192,7 +192,7 @@ end eqs = [D(y) ~ i(t) + p] @named model = ODESystem(eqs, t, [y], [p, i]; parameter_dependencies = [i ~ CallableFoo(p)]) - sys = mtkbuild(model) + sys = structural_simplify(model) prob = ODEProblem(sys, [], (0.0, 1.0)) sol = solve(prob, Tsit5()) diff --git a/test/reduction.jl b/test/reduction.jl index 1f0028fc6e..fa9029a652 100644 --- a/test/reduction.jl +++ b/test/reduction.jl @@ -30,7 +30,7 @@ eqs = [D(x) ~ σ * (y - x) lorenz1 = ODESystem(eqs, t, name = :lorenz1) -lorenz1_aliased = mtkbuild(lorenz1) +lorenz1_aliased = structural_simplify(lorenz1) io = IOBuffer(); show(io, MIME("text/plain"), lorenz1_aliased); str = String(take!(io)); @@ -74,8 +74,8 @@ __x = x # Reduced Flattened System -reduced_system = mtkbuild(connected) -reduced_system2 = mtkbuild(tearing_substitution(mtkbuild(tearing_substitution(mtkbuild(connected))))) +reduced_system = structural_simplify(connected) +reduced_system2 = structural_simplify(tearing_substitution(structural_simplify(tearing_substitution(structural_simplify(connected))))) @test isempty(setdiff(unknowns(reduced_system), unknowns(reduced_system2))) @test isequal(equations(tearing_substitution(reduced_system)), equations(reduced_system2)) @@ -133,7 +133,7 @@ let pc.y_c ~ ol.y] @named connected = ODESystem(connections, t, systems = [ol, pc]) @test equations(connected) isa Vector{Equation} - reduced_sys = mtkbuild(connected) + reduced_sys = structural_simplify(connected) ref_eqs = [D(ol.x) ~ ol.a * ol.x + ol.b * ol.u 0 ~ pc.k_P * ol.y - ol.u] #@test ref_eqs == equations(reduced_sys) @@ -144,7 +144,7 @@ let @variables x(t) @named sys = ODESystem([0 ~ D(x) + x], t, [x], []) #@test_throws ModelingToolkit.InvalidSystemException ODEProblem(sys, [1.0], (0, 10.0)) - sys = mtkbuild(sys) + sys = structural_simplify(sys) #@test_nowarn ODEProblem(sys, [1.0], (0, 10.0)) end @@ -155,7 +155,7 @@ eqs = [u1 ~ u2 u3 ~ u1 + u2 + p u3 ~ hypot(u1, u2) * p] @named sys = NonlinearSystem(eqs, [u1, u2, u3], [p]) -reducedsys = mtkbuild(sys) +reducedsys = structural_simplify(sys) @test length(observed(reducedsys)) == 2 u0 = [u2 => 1] @@ -175,7 +175,7 @@ N = 5 A = reshape(1:(N^2), N, N) eqs = xs ~ A * xs @named sys′ = NonlinearSystem(eqs, [xs], []) -sys = mtkbuild(sys′) +sys = structural_simplify(sys′) @test length(equations(sys)) == 3 && length(observed(sys)) == 2 # issue 958 @@ -189,7 +189,7 @@ eqs = [D(E) ~ k₋₁ * C - k₁ * E * S E₀ ~ E + C] @named sys = ODESystem(eqs, t, [E, C, S, P], [k₁, k₂, k₋₁, E₀]) -@test_throws ModelingToolkit.ExtraEquationsSystemException mtkbuild(sys) +@test_throws ModelingToolkit.ExtraEquationsSystemException structural_simplify(sys) # Example 5 from Pantelides' original paper params = collect(@parameters y1(t) y2(t)) @@ -198,7 +198,7 @@ eqs = [0 ~ x + sin(u1 + u2) D(x) ~ x + y1 cos(x) ~ sin(y2)] @named sys = ODESystem(eqs, t, sts, params) -@test_throws ModelingToolkit.InvalidSystemException mtkbuild(sys) +@test_throws ModelingToolkit.InvalidSystemException structural_simplify(sys) # issue #963 @variables v47(t) v57(t) v66(t) v25(t) i74(t) i75(t) i64(t) i71(t) v1(t) v2(t) @@ -215,7 +215,7 @@ eq = [v47 ~ v1 0 ~ i64 + i71] @named sys0 = ODESystem(eq, t) -sys = mtkbuild(sys0) +sys = structural_simplify(sys0) @test length(equations(sys)) == 1 eq = equations(tearing_substitution(sys))[1] vv = only(unknowns(sys)) @@ -233,7 +233,7 @@ eqs = [D(x) ~ σ * (y - x) u ~ z + a] lorenz1 = ODESystem(eqs, t, name = :lorenz1) -lorenz1_reduced, _ = mtkbuild(lorenz1, ([z], [])) +lorenz1_reduced, _ = structural_simplify(lorenz1, ([z], [])) @test z in Set(parameters(lorenz1_reduced)) # #2064 @@ -242,7 +242,7 @@ eqs = [D(x) ~ x D(y) ~ y D(z) ~ t] @named model = ODESystem(eqs, t) -sys = mtkbuild(model) +sys = structural_simplify(model) Js = ModelingToolkit.jacobian_sparsity(sys) @test size(Js) == (3, 3) @test Js == Diagonal([1, 1, 0]) @@ -275,7 +275,7 @@ new_sys = alias_elimination(sys) eqs = [x ~ 0 D(x) ~ x + y] @named sys = ODESystem(eqs, t, [x, y], []) -ss = mtkbuild(sys) +ss = structural_simplify(sys) @test isempty(equations(ss)) @test sort(string.(observed(ss))) == ["x(t) ~ 0.0" "xˍt(t) ~ 0.0" @@ -285,5 +285,5 @@ eqs = [D(D(x)) ~ -x] @named sys = ODESystem(eqs, t, [x], []) ss = alias_elimination(sys) @test length(equations(ss)) == length(unknowns(ss)) == 1 -ss = mtkbuild(sys) +ss = structural_simplify(sys) @test length(equations(ss)) == length(unknowns(ss)) == 2 diff --git a/test/scc_nonlinear_problem.jl b/test/scc_nonlinear_problem.jl index ecd5af5f8a..b2b326d090 100644 --- a/test/scc_nonlinear_problem.jl +++ b/test/scc_nonlinear_problem.jl @@ -22,9 +22,9 @@ using ModelingToolkit: t_nounits as t, D_nounits as D eqs = 0 .~ eqs @named model = NonlinearSystem(eqs) @test_throws ["simplified", "required"] SCCNonlinearProblem(model, []) - _model = mtkbuild(model; split = false) + _model = structural_simplify(model; split = false) @test_throws ["not compatible"] SCCNonlinearProblem(_model, []) - model = mtkbuild(model) + model = structural_simplify(model) prob = NonlinearProblem(model, [u => zeros(8)]) sccprob = SCCNonlinearProblem(model, [u => zeros(8)]) sol1 = solve(prob, NewtonRaphson()) diff --git a/test/sciml_problem_inputs.jl b/test/sciml_problem_inputs.jl index 671cf8832e..bfa560cda3 100644 --- a/test/sciml_problem_inputs.jl +++ b/test/sciml_problem_inputs.jl @@ -37,7 +37,7 @@ begin MassActionJump(k2, [Z => 1], [Y => 1, Z => -1]) ] - # Create systems (without mtkbuild, since that might modify systems to affect intended tests). + # Create systems (without structural_simplify, since that might modify systems to affect intended tests). osys = complete(ODESystem(diff_eqs, t; name = :osys)) ssys = complete(SDESystem( diff_eqs, noise_eqs, t, [X, Y, Z], [kp, kd, k1, k2]; name = :ssys)) diff --git a/test/sdesystem.jl b/test/sdesystem.jl index 05d424efba..b031a2f5ab 100644 --- a/test/sdesystem.jl +++ b/test/sdesystem.jl @@ -596,7 +596,7 @@ eqs = [D(x) ~ σ * (y - x) + x * β, D(y) ~ x * (ρ - z) - y + y * β + x * η, D(z) ~ x * y - β * z + (x * z) * β] @named sys1 = System(eqs, tt) -sys1 = mtkbuild(sys1) +sys1 = structural_simplify(sys1) drift_eqs = [D(x) ~ σ * (y - x), D(y) ~ x * (ρ - z) - y, @@ -794,13 +794,13 @@ end input ~ 0.0] sys = System(eqs, t, sts, ps; name = :name) - sys = mtkbuild(sys) + sys = structural_simplify(sys) @test ModelingToolkit.get_noiseeqs(sys) ≈ [1.0] prob = SDEProblem(sys, [], (0.0, 1.0), []) @test_nowarn solve(prob, RKMil()) end -@testset "Observed variables retained after `mtkbuild`" begin +@testset "Observed variables retained after `structural_simplify`" begin @variables x(t) y(t) z(t) @brownian a @mtkbuild sys = System([D(x) ~ x + a, D(y) ~ y + a, z ~ x + y], t) @@ -859,7 +859,7 @@ end end end -@testset "`mtkbuild(::SDESystem)`" begin +@testset "`structural_simplify(::SDESystem)`" begin @variables x(t) y(t) @mtkbuild sys = SDESystem( [D(x) ~ x, y ~ 2x], [x, 0], t, [x, y], []; is_scalar_noise = true) @@ -947,7 +947,7 @@ end @test ssys1 !== ssys2 end -@testset "Error when constructing SDESystem without `mtkbuild`" begin +@testset "Error when constructing SDESystem without `structural_simplify`" begin @parameters σ ρ β @variables x(tt) y(tt) z(tt) @brownian a @@ -961,8 +961,8 @@ end u0map = [x => 1.0, y => 0.0, z => 0.0] parammap = [σ => 10.0, β => 26.0, ρ => 2.33] - @test_throws ErrorException("SDESystem constructed by defining Brownian variables with @brownian must be simplified by calling `mtkbuild` before a SDEProblem can be constructed.") SDEProblem( + @test_throws ErrorException("SDESystem constructed by defining Brownian variables with @brownian must be simplified by calling `structural_simplify` before a SDEProblem can be constructed.") SDEProblem( de, u0map, (0.0, 100.0), parammap) - de = mtkbuild(de) + de = structural_simplify(de) @test SDEProblem(de, u0map, (0.0, 100.0), parammap) isa SDEProblem end diff --git a/test/split_parameters.jl b/test/split_parameters.jl index 74286e2843..18fdb49a48 100644 --- a/test/split_parameters.jl +++ b/test/split_parameters.jl @@ -82,7 +82,7 @@ eqs = [y ~ src.output.u @named sys = ODESystem(eqs, t, vars, []; systems = [int, src]) s = complete(sys) -sys = mtkbuild(sys) +sys = structural_simplify(sys) prob = ODEProblem( sys, [], (0.0, t_end), [s.src.interpolator => Interpolator(x, dt)]; tofloat = false) @@ -108,7 +108,7 @@ eqs = [D(y) ~ dy * a ddy ~ sin(t) * c] @named model = ODESystem(eqs, t, vars, pars) -sys = mtkbuild(model; split = false) +sys = structural_simplify(model; split = false) tspan = (0.0, t_end) prob = ODEProblem(sys, [], tspan, []; build_initializeprob = false) diff --git a/test/state_selection.jl b/test/state_selection.jl index f0b5a89e59..a8d3e57773 100644 --- a/test/state_selection.jl +++ b/test/state_selection.jl @@ -118,7 +118,7 @@ let @named system = System(L = 10) @unpack supply_pipe, return_pipe = system - sys = mtkbuild(system) + sys = structural_simplify(system) u0 = [ sys.supply_pipe.v => 0.1, sys.return_pipe.v => 0.1, D(supply_pipe.v) => 0.0, D(return_pipe.fluid_port_a.m) => 0.0, @@ -169,7 +169,7 @@ let @named trans = ODESystem(eqs, t) - sys = mtkbuild(trans) + sys = structural_simplify(trans) n = 3 u = 0 * ones(n) @@ -274,7 +274,7 @@ let # solution ------------------------------------------------------------------- @named catapult = ODESystem(eqs, t, vars, params, defaults = defs) - sys = mtkbuild(catapult) + sys = structural_simplify(catapult) prob = ODEProblem(sys, [], (0.0, 0.1), [l_2f => 0.55, damp => 1e7]; jac = true) @test solve(prob, Rodas4()).retcode == ReturnCode.Success end diff --git a/test/static_arrays.jl b/test/static_arrays.jl index e7807694a4..61177e5ab2 100644 --- a/test/static_arrays.jl +++ b/test/static_arrays.jl @@ -9,7 +9,7 @@ eqs = [D(D(x)) ~ σ * (y - x), D(z) ~ x * y - β * z] @named sys = ODESystem(eqs, t) -sys = mtkbuild(sys) +sys = structural_simplify(sys) u0 = @SVector [D(x) => 2.0, x => 1.0, diff --git a/test/stream_connectors.jl b/test/stream_connectors.jl index e76a9fa4b4..834ebce1a7 100644 --- a/test/stream_connectors.jl +++ b/test/stream_connectors.jl @@ -134,7 +134,7 @@ eqns = [domain_connect(fluid, n1m1.port_a) @named n1m1Test = ODESystem(eqns, t, [], []; systems = [fluid, n1m1, pipe, sink]) -@test_nowarn mtkbuild(n1m1Test) +@test_nowarn structural_simplify(n1m1Test) @unpack source, port_a = n1m1 ssort(eqs) = sort(eqs, by = string) @test ssort(equations(expand_connections(n1m1))) == ssort([0 ~ port_a.m_flow @@ -205,7 +205,7 @@ eqns = [connect(n1m2.port_a, sink1.port) @named sys = ODESystem(eqns, t) @named n1m2Test = compose(sys, n1m2, sink1, sink2) -@test_nowarn mtkbuild(n1m2Test) +@test_nowarn structural_simplify(n1m2Test) @named n1m2 = N1M2() @named pipe1 = AdiabaticStraightPipe() @@ -220,7 +220,7 @@ eqns = [connect(n1m2.port_a, pipe1.port_a) @named sys = ODESystem(eqns, t) @named n1m2AltTest = compose(sys, n1m2, pipe1, pipe2, sink1, sink2) -@test_nowarn mtkbuild(n1m2AltTest) +@test_nowarn structural_simplify(n1m2AltTest) # N2M2 model and test code. function N2M2(; name, @@ -249,7 +249,7 @@ eqns = [connect(source.port, n2m2.port_a) @named sys = ODESystem(eqns, t) @named n2m2Test = compose(sys, n2m2, source, sink) -@test_nowarn mtkbuild(n2m2Test) +@test_nowarn structural_simplify(n2m2Test) # stream var @named sp1 = TwoPhaseFluidPort() @@ -472,7 +472,7 @@ csys = complete(two_fluid_system) @test Symbol(sys_defs[csys.volume_a.H.rho]) == Symbol(csys.fluid_a.rho) @test Symbol(sys_defs[csys.volume_b.H.rho]) == Symbol(csys.fluid_b.rho) -@test_nowarn mtkbuild(two_fluid_system) +@test_nowarn structural_simplify(two_fluid_system) function OneFluidSystem(; name) pars = [] @@ -510,4 +510,4 @@ csys = complete(one_fluid_system) @test Symbol(sys_defs[csys.volume_a.H.rho]) == Symbol(csys.fluid.rho) @test Symbol(sys_defs[csys.volume_b.H.rho]) == Symbol(csys.fluid.rho) -@test_nowarn mtkbuild(one_fluid_system) +@test_nowarn structural_simplify(one_fluid_system) diff --git a/test/structural_transformation/index_reduction.jl b/test/structural_transformation/index_reduction.jl index a3634e7a28..f9d6037022 100644 --- a/test/structural_transformation/index_reduction.jl +++ b/test/structural_transformation/index_reduction.jl @@ -133,7 +133,7 @@ let pss_pendulum = partial_state_selection(pendulum) @test_broken length(equations(pss_pendulum)) == 3 end -let sys = mtkbuild(pendulum2) +let sys = structural_simplify(pendulum2) @test length(equations(sys)) == 5 @test length(unknowns(sys)) == 5 @@ -160,7 +160,7 @@ let D(D(y)) ~ λ * y - g x^2 + y^2 ~ 1] @named pend = ODESystem(eqs, t) - sys = complete(mtkbuild(pend; dummy_derivative = false)) + sys = complete(structural_simplify(pend; dummy_derivative = false)) prob = ODEProblem( sys, [x => 1, y => 0, D(x) => 0.0], (0.0, 10.0), [g => 1], guesses = [λ => 0.0]) sol = solve(prob, Rodas5P()) diff --git a/test/structural_transformation/tearing.jl b/test/structural_transformation/tearing.jl index 9e83c117e7..e9cd92ec94 100644 --- a/test/structural_transformation/tearing.jl +++ b/test/structural_transformation/tearing.jl @@ -148,7 +148,7 @@ eqs = [D(x) ~ z * h 0 ~ x - y 0 ~ sin(z) + y - p * t] @named daesys = ODESystem(eqs, t) -newdaesys = mtkbuild(daesys) +newdaesys = structural_simplify(daesys) @test equations(newdaesys) == [D(x) ~ z; 0 ~ y + sin(z) - p * t] @test equations(tearing_substitution(newdaesys)) == [D(x) ~ z; 0 ~ x + sin(z) - p * t] @test isequal(unknowns(newdaesys), [x, z]) @@ -165,7 +165,7 @@ prob.f(du, u, pr, tt) # test the initial guess is respected @named sys = ODESystem(eqs, t, defaults = Dict(z => NaN)) -infprob = ODEProblem(mtkbuild(sys), [x => 1.0], (0, 1.0), [p => 0.2]) +infprob = ODEProblem(structural_simplify(sys), [x => 1.0], (0, 1.0), [p => 0.2]) infprob.f(du, infprob.u0, pr, tt) @test any(isnan, du) @@ -196,7 +196,7 @@ calculate_tgrad(ms_model) u0 = [mass.s => 0.0 mass.v => 1.0] -sys = mtkbuild(ms_model) +sys = structural_simplify(ms_model) @test ModelingToolkit.get_jac(sys)[] === ModelingToolkit.EMPTY_JAC @test ModelingToolkit.get_tgrad(sys)[] === ModelingToolkit.EMPTY_TGRAD prob_complex = ODEProblem(sys, u0, (0, 1.0)) diff --git a/test/structural_transformation/utils.jl b/test/structural_transformation/utils.jl index 347756caba..b5335ad6b1 100644 --- a/test/structural_transformation/utils.jl +++ b/test/structural_transformation/utils.jl @@ -122,14 +122,14 @@ end @named sys = ODESystem( [D(x) ~ z[1] + z[2] + foo(z)[1], y[1] ~ 2t, y[2] ~ 3t, z ~ foo(y)], t) - sys1 = mtkbuild(sys; cse_hack = false) + sys1 = structural_simplify(sys; cse_hack = false) @test length(observed(sys1)) == 6 @test !any(observed(sys1)) do eq iscall(eq.rhs) && operation(eq.rhs) == StructuralTransformations.getindex_wrapper end - sys2 = mtkbuild(sys; array_hack = false) + sys2 = structural_simplify(sys; array_hack = false) @test length(observed(sys2)) == 5 @test !any(observed(sys2)) do eq iscall(eq.rhs) && operation(eq.rhs) == StructuralTransformations.change_origin @@ -143,14 +143,14 @@ end @named sys = ODESystem( [D(x) ~ z[1] + z[2] + foo(z)[1] + w, y[1] ~ 2t, y[2] ~ 3t, z ~ foo(y)], t) - sys1 = mtkbuild(sys; cse_hack = false, fully_determined = false) + sys1 = structural_simplify(sys; cse_hack = false, fully_determined = false) @test length(observed(sys1)) == 6 @test !any(observed(sys1)) do eq iscall(eq.rhs) && operation(eq.rhs) == StructuralTransformations.getindex_wrapper end - sys2 = mtkbuild(sys; array_hack = false, fully_determined = false) + sys2 = structural_simplify(sys; array_hack = false, fully_determined = false) @test length(observed(sys2)) == 5 @test !any(observed(sys2)) do eq iscall(eq.rhs) && operation(eq.rhs) == StructuralTransformations.change_origin @@ -163,7 +163,7 @@ end @named sys = ODESystem([D(x) ~ x, y ~ x + t], t) value = Ref(0) pass(sys; kwargs...) = (value[] += 1; return sys) - mtkbuild(sys; additional_passes = [pass]) + structural_simplify(sys; additional_passes = [pass]) @test value[] == 1 end diff --git a/test/substitute_component.jl b/test/substitute_component.jl index ad458d34cd..9fb254136b 100644 --- a/test/substitute_component.jl +++ b/test/substitute_component.jl @@ -59,8 +59,8 @@ end @named reference = RC() - sys1 = mtkbuild(rcsys) - sys2 = mtkbuild(reference) + sys1 = structural_simplify(rcsys) + sys2 = structural_simplify(reference) @test isequal(unknowns(sys1), unknowns(sys2)) @test isequal(equations(sys1), equations(sys2)) diff --git a/test/symbolic_events.jl b/test/symbolic_events.jl index cc576ce0be..9099d32d14 100644 --- a/test/symbolic_events.jl +++ b/test/symbolic_events.jl @@ -445,7 +445,7 @@ affect = [v ~ -v] @test getfield(ball, :continuous_events)[] == SymbolicContinuousCallback(Equation[x ~ 0], Equation[v ~ -v]) -ball = mtkbuild(ball) +ball = structural_simplify(ball) @test length(ModelingToolkit.continuous_events(ball)) == 1 @@ -468,8 +468,8 @@ continuous_events = [[x ~ 0] => [vx ~ -vx] D(vy) ~ -0.01vy], t; continuous_events) _ball = ball -ball = mtkbuild(_ball) -ball_nosplit = mtkbuild(_ball; split = false) +ball = structural_simplify(_ball) +ball_nosplit = structural_simplify(_ball; split = false) tspan = (0.0, 5.0) prob = ODEProblem(ball, Pair[], tspan) @@ -511,8 +511,8 @@ continuous_events = [ D(vx) ~ -1 D(vy) ~ 0], t; continuous_events) -ball_nosplit = mtkbuild(ball) -ball = mtkbuild(ball) +ball_nosplit = structural_simplify(ball) +ball = structural_simplify(ball) tspan = (0.0, 5.0) prob = ODEProblem(ball, Pair[], tspan) @@ -537,7 +537,7 @@ eq = [vs ~ sin(2pi * t) D(vmeasured) ~ 0.0] ev = [sin(20pi * t) ~ 0.0] => [vmeasured ~ v] @named sys = ODESystem(eq, t, continuous_events = ev) -sys = mtkbuild(sys) +sys = structural_simplify(sys) prob = ODEProblem(sys, zeros(2), (0.0, 5.1)) sol = solve(prob, Tsit5()) @test all(minimum((0:0.05:5) .- sol.t', dims = 2) .< 0.0001) # test that the solver stepped every 0.05s as dictated by event @@ -584,7 +584,7 @@ function Model(u, d = 0) @named model = compose(_model, mass1, mass2, sd) end model = Model(sin(30t)) -sys = mtkbuild(model) +sys = structural_simplify(model) @test isempty(ModelingToolkit.continuous_events(sys)) let @@ -823,7 +823,7 @@ let eqs = [oscce.F ~ 0] @named eqs_sys = ODESystem(eqs, t) @named oneosc_ce = compose(eqs_sys, oscce) - oneosc_ce_simpl = mtkbuild(oneosc_ce) + oneosc_ce_simpl = structural_simplify(oneosc_ce) prob = ODEProblem(oneosc_ce_simpl, [], (0.0, 2.0), []) sol = solve(prob, Tsit5(), saveat = 0.1) @@ -845,7 +845,7 @@ end evt2 = ModelingToolkit.SymbolicContinuousCallback( [c2 ~ 0], (record_crossings, [c2 => :v], [], [], cr2)) @named trigsys = ODESystem(eqs, t; continuous_events = [evt1, evt2]) - trigsys_ss = mtkbuild(trigsys) + trigsys_ss = structural_simplify(trigsys) prob = ODEProblem(trigsys_ss, [], (0.0, 2π)) sol = solve(prob, Tsit5()) required_crossings_c1 = [π / 2, 3 * π / 2] @@ -867,7 +867,7 @@ end [c2 ~ 0], (record_crossings, [c2 => :v], [], [], cr2p); affect_neg = (record_crossings, [c2 => :v], [], [], cr2n)) @named trigsys = ODESystem(eqs, t; continuous_events = [evt1, evt2]) - trigsys_ss = mtkbuild(trigsys) + trigsys_ss = structural_simplify(trigsys) prob = ODEProblem(trigsys_ss, [], (0.0, 2π)) sol = solve(prob, Tsit5(); dtmax = 0.01) c1_pc = filter((<=)(0) ∘ sin, required_crossings_c1) @@ -891,7 +891,7 @@ end evt2 = ModelingToolkit.SymbolicContinuousCallback( [c2 ~ 0], (record_crossings, [c2 => :v], [], [], cr2p); affect_neg = nothing) @named trigsys = ODESystem(eqs, t; continuous_events = [evt1, evt2]) - trigsys_ss = mtkbuild(trigsys) + trigsys_ss = structural_simplify(trigsys) prob = ODEProblem(trigsys_ss, [], (0.0, 2π)) sol = solve(prob, Tsit5(); dtmax = 0.01) @test maximum(abs.(c1_pc .- first.(cr1p))) < 1e-5 @@ -910,7 +910,7 @@ end [c2 ~ 0], (record_crossings, [c2 => :v], [], [], cr2p); affect_neg = (record_crossings, [c2 => :v], [], [], cr2n)) @named trigsys = ODESystem(eqs, t; continuous_events = [evt1, evt2]) - trigsys_ss = mtkbuild(trigsys) + trigsys_ss = structural_simplify(trigsys) prob = ODEProblem(trigsys_ss, [], (0.0, 2π)) sol = solve(prob, Tsit5(); dtmax = 0.01) c1_pc = filter((<=)(0) ∘ sin, required_crossings_c1) @@ -934,7 +934,7 @@ end [c2 ~ 0], (record_crossings, [c2 => :v], [], [], cr2); rootfind = SciMLBase.RightRootFind) @named trigsys = ODESystem(eqs, t; continuous_events = [evt1, evt2]) - trigsys_ss = mtkbuild(trigsys) + trigsys_ss = structural_simplify(trigsys) prob = ODEProblem(trigsys_ss, [], (0.0, 2π)) sol = solve(prob, Tsit5(); dtmax = 0.01) required_crossings_c1 = [π / 2, 3 * π / 2] @@ -954,7 +954,7 @@ end [c2 ~ 0], (record_crossings, [c2 => :v], [], [], cr2); rootfind = SciMLBase.RightRootFind) @named trigsys = ODESystem(eqs, t; continuous_events = [evt1, evt2]) - trigsys_ss = mtkbuild(trigsys) + trigsys_ss = structural_simplify(trigsys) prob = ODEProblem(trigsys_ss, [], (0.0, 2π)) sol = solve(prob, Tsit5()) @test maximum(abs.(first.(cr1) .- required_crossings_c1)) < 1e-4 @@ -972,7 +972,7 @@ end [c2 ~ 0], (record_crossings, [c2 => :v], [], [], cr2); rootfind = SciMLBase.RightRootFind) @named trigsys = ODESystem(eqs, t; continuous_events = [evt2, evt1]) - trigsys_ss = mtkbuild(trigsys) + trigsys_ss = structural_simplify(trigsys) prob = ODEProblem(trigsys_ss, [], (0.0, 2π)) sol = solve(prob, Tsit5()) @test maximum(abs.(first.(cr1) .- required_crossings_c1)) < 1e-4 @@ -1102,7 +1102,7 @@ end end) @named sys = ODESystem( eqs, t, [temp], params; continuous_events = [furnace_off, furnace_enable]) - ss = mtkbuild(sys) + ss = structural_simplify(sys) prob = ODEProblem(ss, [temp => 0.0, furnace_on => true], (0.0, 100.0)) sol = solve(prob, Tsit5(); dtmax = 0.01) @test all(sol[temp][sol.t .> 1.0] .<= 0.79) && all(sol[temp][sol.t .> 1.0] .>= 0.49) @@ -1122,7 +1122,7 @@ end end) @named sys = ODESystem( eqs, t, [temp], params; continuous_events = [furnace_off, furnace_enable]) - ss = mtkbuild(sys) + ss = structural_simplify(sys) prob = ODEProblem(ss, [temp => 0.0, furnace_on => true], (0.0, 100.0)) sol = solve(prob, Tsit5(); dtmax = 0.01) @test all(sol[temp][sol.t .> 1.0] .<= 0.79) && all(sol[temp][sol.t .> 1.0] .>= 0.49) @@ -1143,7 +1143,7 @@ end @set! x.furnace_on = false end) @named sys = ODESystem(eqs, t, [temp], params; continuous_events = [furnace_off]) - ss = mtkbuild(sys) + ss = structural_simplify(sys) @test_logs (:warn, "The symbols Any[:furnace_on] are declared as both observed and modified; this is a code smell because it becomes easy to confuse them and assign/not assign a value.") prob=ODEProblem( ss, [temp => 0.0, furnace_on => true], (0.0, 100.0)) @@ -1160,7 +1160,7 @@ end end) @named sys = ODESystem( eqs, t, [temp, tempsq], params; continuous_events = [furnace_off]) - ss = mtkbuild(sys) + ss = structural_simplify(sys) @test_throws "refers to missing variable(s)" prob=ODEProblem( ss, [temp => 0.0, furnace_on => true], (0.0, 100.0)) @@ -1173,7 +1173,7 @@ end end) @named sys = ODESystem( eqs, t, [temp, tempsq], params; continuous_events = [furnace_off]) - ss = mtkbuild(sys) + ss = structural_simplify(sys) @test_throws "refers to missing variable(s)" prob=ODEProblem( ss, [temp => 0.0, furnace_on => true], (0.0, 100.0)) @@ -1185,7 +1185,7 @@ end end) @named sys = ODESystem( eqs, t, [temp, tempsq], params; continuous_events = [furnace_off]) - ss = mtkbuild(sys) + ss = structural_simplify(sys) prob = ODEProblem( ss, [temp => 0.0, furnace_on => true], (0.0, 100.0)) @test_throws "Tried to write back to" solve(prob, Tsit5()) @@ -1245,7 +1245,7 @@ end end; rootfind = SciMLBase.RightRootFind) @named sys = ODESystem( eqs, t, [theta, omega], params; continuous_events = [qAevt, qBevt]) - ss = mtkbuild(sys) + ss = structural_simplify(sys) prob = ODEProblem(ss, [theta => 1e-5], (0.0, pi)) sol = solve(prob, Tsit5(); dtmax = 0.01) @test getp(sol, cnt)(sol) == 198 # we get 2 pulses per phase cycle (cos 0 crossing) and we go to 100 cycles; we miss a few due to the initial state @@ -1415,13 +1415,13 @@ end @named wd1 = weird1(0.021) @named wd2 = weird2(0.021) - sys1 = mtkbuild(ODESystem([], t; name = :parent, + sys1 = structural_simplify(ODESystem([], t; name = :parent, discrete_events = [0.01 => ModelingToolkit.ImperativeAffect( modified = (; θs = reduce(vcat, [[wd1.θ]])), ctx = [1]) do m, o, c, i @set! m.θs[1] = c[] += 1 end], systems = [wd1])) - sys2 = mtkbuild(ODESystem([], t; name = :parent, + sys2 = structural_simplify(ODESystem([], t; name = :parent, discrete_events = [0.01 => ModelingToolkit.ImperativeAffect( modified = (; θs = reduce(vcat, [[wd2.θ]])), ctx = [1]) do m, o, c, i @set! m.θs[1] = c[] += 1 diff --git a/test/symbolic_indexing_interface.jl b/test/symbolic_indexing_interface.jl index 39ea2e84c2..8b3da5fd72 100644 --- a/test/symbolic_indexing_interface.jl +++ b/test/symbolic_indexing_interface.jl @@ -220,7 +220,7 @@ end @variables x(t) y(t) z(t) @parameters a @named sys = ODESystem([D(x) ~ a * x, y ~ 2x, z ~ 0.0], t) - sys = mtkbuild(sys, split = false) + sys = structural_simplify(sys, split = false) for sym in [x, y, z, x + y, x + a, y / x] @test only(get_all_timeseries_indexes(sys, sym)) == ContinuousTimeseries() end diff --git a/test/units.jl b/test/units.jl index d9a29547a9..ff0cd42ac3 100644 --- a/test/units.jl +++ b/test/units.jl @@ -140,24 +140,24 @@ D = Differential(t) eqs = [D(L) ~ v, V ~ L^3] @named sys = ODESystem(eqs, t) -sys_simple = mtkbuild(sys) +sys_simple = structural_simplify(sys) eqs = [D(V) ~ r, V ~ L^3] @named sys = ODESystem(eqs, t) -sys_simple = mtkbuild(sys) +sys_simple = structural_simplify(sys) @variables V [unit = u"m"^3] L [unit = u"m"] @parameters v [unit = u"m/s"] r [unit = u"m"^3 / u"s"] t [unit = u"s"] eqs = [V ~ r * t, V ~ L^3] @named sys = NonlinearSystem(eqs, [V, L], [t, r]) -sys_simple = mtkbuild(sys) +sys_simple = structural_simplify(sys) eqs = [L ~ v * t, V ~ L^3] @named sys = NonlinearSystem(eqs, [V, L], [t, r]) -sys_simple = mtkbuild(sys) +sys_simple = structural_simplify(sys) #Jump System @parameters β [unit = u"(mol^2*s)^-1"] γ [unit = u"(mol*s)^-1"] t [unit = u"s"] jumpmol [ diff --git a/test/variable_scope.jl b/test/variable_scope.jl index 1df75c0bf1..59647bf441 100644 --- a/test/variable_scope.jl +++ b/test/variable_scope.jl @@ -125,7 +125,7 @@ defs = ModelingToolkit.defaults(bar) sys4 = complete(sys3) @test length(unknowns(sys4)) == 3 @test length(parameters(sys4)) == 4 - sys5 = mtkbuild(sys3) + sys5 = structural_simplify(sys3) @test length(unknowns(sys5)) == 4 @test any(isequal(x4), unknowns(sys5)) @test length(parameters(sys5)) == 4 From 6e3216e28852a04a017b0157de7166bb4ae8ce6a Mon Sep 17 00:00:00 2001 From: vyudu Date: Tue, 22 Apr 2025 00:31:56 -0400 Subject: [PATCH 016/122] revert more renames --- src/linearization.jl | 2 +- src/systems/analysis_points.jl | 4 ---- src/systems/systems.jl | 2 +- test/downstream/linearize.jl | 4 ++-- 4 files changed, 4 insertions(+), 8 deletions(-) diff --git a/src/linearization.jl b/src/linearization.jl index eb06d142f3..242ca857d8 100644 --- a/src/linearization.jl +++ b/src/linearization.jl @@ -46,7 +46,7 @@ function linearization_function(sys::AbstractSystem, inputs, warn_empty_op = true, kwargs...) if !iscomplete(sys) - sys = mtkbuild(sys; inputs, outputs) + sys = structural_simplify(sys; inputs, outputs) end op = Dict(op) if isempty(op) && warn_empty_op diff --git a/src/systems/analysis_points.jl b/src/systems/analysis_points.jl index ce2e778b93..0d1a2830cf 100644 --- a/src/systems/analysis_points.jl +++ b/src/systems/analysis_points.jl @@ -596,13 +596,9 @@ Add an input without an additional output variable. PerturbOutput(ap::AnalysisPoint) = PerturbOutput(ap, false) function apply_transformation(tf::PerturbOutput, sys::AbstractSystem) - @show "ok" - @show tf.ap modify_nested_subsystem(sys, tf.ap) do ap_sys # get analysis point - @show tf.ap ap_idx = analysis_point_index(ap_sys, tf.ap) - @show ap_idx ap_idx === nothing && error("Analysis point $(nameof(tf.ap)) not found in system $(nameof(sys)).") # modified equations diff --git a/src/systems/systems.jl b/src/systems/systems.jl index 7e47a21e75..bbf6cb17c1 100644 --- a/src/systems/systems.jl +++ b/src/systems/systems.jl @@ -26,7 +26,7 @@ topological sort of the observed equations in `sys`. + `fully_determined=true` controls whether or not an error will be thrown if the number of equations don't match the number of inputs, outputs, and equations. + `sort_eqs=true` controls whether equations are sorted lexicographically before simplification or not. """ -function mtkbuild( +function structural_simplify( sys::AbstractSystem; additional_passes = [], simplify = false, split = true, allow_symbolic = false, allow_parameter = true, conservative = false, fully_determined = true, inputs = Any[], outputs = Any[], diff --git a/test/downstream/linearize.jl b/test/downstream/linearize.jl index 40cb277236..bc675986e2 100644 --- a/test/downstream/linearize.jl +++ b/test/downstream/linearize.jl @@ -114,7 +114,7 @@ Nd = 10 @named pid = LimPID(; k, Ti, Td, Nd) @unpack reference, measurement, ctr_output = pid -pid = mtkbuild(pid, inputs = [reference.u, measurement.u], outputs = [ctr_output.u]) +pid = structural_simplify(pid, inputs = [reference.u, measurement.u], outputs = [ctr_output.u]) lsys0 = linearize(pid, [reference.u, measurement.u], [ctr_output.u]; op = Dict(reference.u => 0.0, measurement.u => 0.0)) @unpack int, der = pid @@ -185,7 +185,7 @@ function saturation(; y_max, y_min = y_max > 0 ? -y_max : -Inf, name) ODESystem(eqs, t, name = name) end @named sat = saturation(; y_max = 1) -sat = mtkbuild(sat, inputs = [u], outputs = [y]) +sat = structural_simplify(sat, inputs = [u], outputs = [y]) # inside the linear region, the function is identity @unpack u, y = sat lsys = linearize(sat, [u], [y]) From 22cdc69e4f0f0b51af00e00c88dfe3245435fe70 Mon Sep 17 00:00:00 2001 From: vyudu Date: Tue, 22 Apr 2025 12:51:03 -0400 Subject: [PATCH 017/122] correct tests --- src/linearization.jl | 18 ++++++------ src/systems/systems.jl | 8 +++--- test/downstream/linearize.jl | 54 +++++++++++++++++++----------------- 3 files changed, 42 insertions(+), 38 deletions(-) diff --git a/src/linearization.jl b/src/linearization.jl index 242ca857d8..264d6b79a8 100644 --- a/src/linearization.jl +++ b/src/linearization.jl @@ -29,8 +29,8 @@ The `simplified_sys` has undergone [`structural_simplify`](@ref) and had any occ See also [`linearize`](@ref) which provides a higher-level interface. """ -function linearization_function(sys::AbstractSystem, inputs, - outputs; +function linearization_function(sys::AbstractSystem, inputs = inputs(sys), + outputs = outputs(sys); initialize = true, initializealg = nothing, initialization_abstol = 1e-5, @@ -396,7 +396,7 @@ Construct a `LinearizationProblem` for linearizing the system `sys` with the giv All other keyword arguments are forwarded to `linearization_function`. """ -function LinearizationProblem(sys::AbstractSystem, inputs, outputs; t = 0.0, kwargs...) +function LinearizationProblem(sys::AbstractSystem, inputs = inputs(sys), outputs = outputs(sys); t = 0.0, kwargs...) linfun = linearization_function(sys, inputs, outputs; kwargs...) return LinearizationProblem(linfun, t) end @@ -484,8 +484,8 @@ y &= h(x, z, u) ``` where `x` are differential unknown variables, `z` algebraic variables, `u` inputs and `y` outputs. """ -function linearize_symbolic(sys::AbstractSystem, inputs, - outputs; allow_input_derivatives = false, +function linearize_symbolic(sys::AbstractSystem, inputs = inputs(sys), + outputs = outputs(sys); allow_input_derivatives = false, eval_expression = false, eval_module = @__MODULE__, kwargs...) diff_idxs, alge_idxs = eq_idxs(sys) @@ -546,7 +546,7 @@ function linearize_symbolic(sys::AbstractSystem, inputs, end end - (; A, B, C, D, f_x, f_z, g_x, g_z, f_u, g_u, h_x, h_z, h_u), sys + (; A, B, C, D, f_x, f_z, g_x, g_z, f_u, g_u, h_x, h_z, h_u) end function markio!(state, orig_inputs, inputs, outputs; check = true) @@ -713,17 +713,17 @@ function linearize(sys, lin_fun::LinearizationFunction; t = 0.0, return solve(prob; allow_input_derivatives) end -function linearize(sys, inputs, outputs; op = Dict(), t = 0.0, +function linearize(sys, inputs = inputs(sys), outputs = outputs(sys); op = Dict(), t = 0.0, allow_input_derivatives = false, zero_dummy_der = false, kwargs...) - lin_fun, ssys = linearization_function(sys, + lin_fun = linearization_function(sys, inputs, outputs; zero_dummy_der, op, kwargs...) - linearize(ssys, lin_fun; op, t, allow_input_derivatives), ssys + linearize(sys, lin_fun; op, t, allow_input_derivatives) end """ diff --git a/src/systems/systems.jl b/src/systems/systems.jl index bbf6cb17c1..3ed8df6e99 100644 --- a/src/systems/systems.jl +++ b/src/systems/systems.jl @@ -33,7 +33,7 @@ function structural_simplify( disturbance_inputs = Any[], kwargs...) isscheduled(sys) && throw(RepeatedStructuralSimplificationError()) - newsys′ = __structural_simplify(sys; simplify, + newsys′ = __structural_simplification(sys; simplify, allow_symbolic, allow_parameter, conservative, fully_determined, inputs, outputs, disturbance_inputs, kwargs...) @@ -65,12 +65,12 @@ function structural_simplify( end end -function __structural_simplify(sys::JumpSystem, args...; kwargs...) +function __structural_simplification(sys::JumpSystem, args...; kwargs...) return sys end -function __structural_simplify(sys::SDESystem, args...; kwargs...) - return __structural_simplify(ODESystem(sys), args...; kwargs...) +function __structural_simplification(sys::SDESystem, args...; kwargs...) + return __structural_simplification(ODESystem(sys), args...; kwargs...) end function __structural_simplification(sys::AbstractSystem; simplify = false, diff --git a/test/downstream/linearize.jl b/test/downstream/linearize.jl index bc675986e2..d51b0d17d6 100644 --- a/test/downstream/linearize.jl +++ b/test/downstream/linearize.jl @@ -13,18 +13,20 @@ eqs = [u ~ kp * (r - y) y ~ x] @named sys = ODESystem(eqs, t) +sys1 = structural_simplify(sys, inputs = [r], outputs = [y]) -lsys, ssys = linearize(sys, [r], [y]) -lprob = LinearizationProblem(sys, [r], [y]) +lsys = linearize(sys1) +lprob = LinearizationProblem(sys1) lsys2 = solve(lprob) -lsys3, _ = linearize(sys, [r], [y]; autodiff = AutoFiniteDiff()) +lsys3 = linearize(sys1; autodiff = AutoFiniteDiff()) @test lsys.A[] == lsys2.A[] == lsys3.A[] == -2 @test lsys.B[] == lsys2.B[] == lsys3.B[] == 1 @test lsys.C[] == lsys2.C[] == lsys3.C[] == 1 @test lsys.D[] == lsys2.D[] == lsys3.D[] == 0 -lsys = linearize(sys, [r], [r]) +sys2 = structural_simplify(sys, inputs = [r], outputs = [r]) +lsys = linearize(sys2) @test lsys.A[] == -2 @test lsys.B[] == 1 @@ -86,8 +88,9 @@ connections = [f.y ~ c.r # filtered reference to controller reference p.y ~ c.y] @named cl = ODESystem(connections, t, systems = [f, c, p]) +cl = structural_simplify(cl, inputs = [f.u], outputs = [p.x]) -lsys0 = linearize(cl, [f.u], [p.x]) +lsys0 = linearize(cl) desired_order = [f.x, p.x] lsys = ModelingToolkit.reorder_unknowns(lsys0, unknowns(cl), desired_order) lsys1 = linearize(cl, [f.u], [p.x]; autodiff = AutoFiniteDiff()) @@ -99,7 +102,7 @@ lsys2 = ModelingToolkit.reorder_unknowns(lsys1, unknowns(cl), desired_order) @test lsys.D[] == lsys2.D[] == 0 ## Symbolic linearization -lsyss = ModelingToolkit.linearize_symbolic(cl, [f.u], [p.x]) +lsyss = ModelingToolkit.linearize_symbolic(cl) @test ModelingToolkit.fixpoint_sub(lsyss.A, ModelingToolkit.defaults(cl)) == lsys.A @test ModelingToolkit.fixpoint_sub(lsyss.B, ModelingToolkit.defaults(cl)) == lsys.B @@ -114,12 +117,11 @@ Nd = 10 @named pid = LimPID(; k, Ti, Td, Nd) @unpack reference, measurement, ctr_output = pid -pid = structural_simplify(pid, inputs = [reference.u, measurement.u], outputs = [ctr_output.u]) -lsys0 = linearize(pid, [reference.u, measurement.u], [ctr_output.u]; - op = Dict(reference.u => 0.0, measurement.u => 0.0)) -@unpack int, der = pid +pid_s = structural_simplify(pid, inputs = [reference.u, measurement.u], outputs = [ctr_output.u]) +lsys0 = linearize(pid_s; op = Dict(reference.u => 0.0, measurement.u => 0.0)) +@unpack int, der = pid_s desired_order = [int.x, der.x] -lsys = ModelingToolkit.reorder_unknowns(lsys0, unknowns(pid), desired_order) +lsys = ModelingToolkit.reorder_unknowns(lsys0, unknowns(pid_s), desired_order) @test lsys.A == [0 0; 0 -10] @test lsys.B == [2 -2; 10 -10] @@ -149,17 +151,17 @@ lsys = ModelingToolkit.reorder_unknowns(lsys, desired_order, reverse(desired_ord ## Test that there is a warning when input is misspecified if VERSION >= v"1.8" - @test_throws "Some parameters are missing from the variable map." linearize(pid, + @test_throws "Some specified inputs were not found in system. The following variables were not found " structural_simplify(pid, inputs = [ pid.reference.u, pid.measurement.u - ], [ctr_output.u]) - @test_throws "Some parameters are missing from the variable map." linearize(pid, - [ + ], outputs = [ctr_output.u]) + @test_throws "Some specified outputs were not found in system." structural_simplify(pid, + inputs = [ reference.u, measurement.u ], - [pid.ctr_output.u]) + outputs = [pid.ctr_output.u]) else # v1.6 does not have the feature to match error message @test_throws ErrorException linearize(pid, [ @@ -201,7 +203,7 @@ lsys = linearize(sat, [u], [y]) # @test substitute(lsyss.D, ModelingToolkit.defaults(sat)) == lsys.D # outside the linear region the derivative is 0 -lsys, ssys = linearize(sat, [u], [y]; op = Dict(u => 2)) +lsys = linearize(sat, [u], [y]; op = Dict(u => 2)) @test isempty(lsys.A) # there are no differential variables in this system @test isempty(lsys.B) @test isempty(lsys.C) @@ -307,12 +309,12 @@ m_ss = 2.4000000003229878 eqs = [D(x) ~ p * u, x ~ y] @named sys = ODESystem(eqs, t) -matrices1, _ = linearize(sys, [u], []; op = Dict(x => 2.0)) -matrices2, _ = linearize(sys, [u], []; op = Dict(y => 2.0)) +matrices1 = linearize(sys, [u], []; op = Dict(x => 2.0)) +matrices2 = linearize(sys, [u], []; op = Dict(y => 2.0)) @test matrices1 == matrices2 # Ensure parameter values passed as `Dict` are respected -linfun, _ = linearization_function(sys, [u], []; op = Dict(x => 2.0)) +linfun = linearization_function(sys, [u], []; op = Dict(x => 2.0)) matrices = linfun([1.0], Dict(p => 3.0), 1.0) # this would be 1 if the parameter value isn't respected @test matrices.f_u[] == 3.0 @@ -328,26 +330,28 @@ end @parameters p eqs = [0 ~ x * log(y) - p] @named sys = ODESystem(eqs, t; defaults = [p => 1.0]) - sys = complete(sys) + sys = structural_simplify(sys, inputs = [x]) @test_throws ModelingToolkit.MissingVariablesError linearize( - sys, [x], []; op = Dict(x => 1.0), allow_input_derivatives = true) + sys; op = Dict(x => 1.0), allow_input_derivatives = true) @test_nowarn linearize( - sys, [x], []; op = Dict(x => 1.0), guesses = Dict(y => 1.0), + sys; op = Dict(x => 1.0), guesses = Dict(y => 1.0), allow_input_derivatives = true) end @testset "Symbolic values for parameters in `linearize`" begin @named tank_noi = Tank_noi() @unpack md_i, h, m, ρ, A, K = tank_noi + tank_noi = structural_simplify(tank_noi, inputs = [md_i], outputs = [h]) m_ss = 2.4000000003229878 @test_nowarn linearize( - tank_noi, [md_i], [h]; op = Dict(m => m_ss, md_i => 2, ρ => A / K, A => 5)) + tank_noi; op = Dict(m => m_ss, md_i => 2, ρ => A / K, A => 5)) end @testset "Warn on empty operating point" begin @named tank_noi = Tank_noi() @unpack md_i, h, m = tank_noi + tank_noi = structural_simplify(tank_noi, inputs = [md_i], outputs = [h]) m_ss = 2.4000000003229878 @test_warn ["empty operating point", "warn_empty_op"] linearize( - tank_noi, [md_i], [h]; p = [md_i => 1.0]) + tank_noi; p = [md_i => 1.0]) end From 1956ca0fca4308b0aeba3a0efc34f1a23403c003 Mon Sep 17 00:00:00 2001 From: vyudu Date: Tue, 22 Apr 2025 15:18:23 -0400 Subject: [PATCH 018/122] fix: require simplification again --- src/linearization.jl | 2 +- src/systems/analysis_points.jl | 9 +++++++-- src/systems/systems.jl | 4 ++++ test/downstream/linearize.jl | 7 +++++-- 4 files changed, 17 insertions(+), 5 deletions(-) diff --git a/src/linearization.jl b/src/linearization.jl index 264d6b79a8..097e05e79f 100644 --- a/src/linearization.jl +++ b/src/linearization.jl @@ -46,7 +46,7 @@ function linearization_function(sys::AbstractSystem, inputs = inputs(sys), warn_empty_op = true, kwargs...) if !iscomplete(sys) - sys = structural_simplify(sys; inputs, outputs) + error("A simplified `ODESystem` is required. Call `structural_simplify` on the system with the inputs and outputs before creating the linearization function.") end op = Dict(op) if isempty(op) && warn_empty_op diff --git a/src/systems/analysis_points.jl b/src/systems/analysis_points.jl index 0d1a2830cf..4e98592f43 100644 --- a/src/systems/analysis_points.jl +++ b/src/systems/analysis_points.jl @@ -928,8 +928,7 @@ function open_loop(sys, ap::Union{Symbol, AnalysisPoint}; system_modifier = iden return system_modifier(sys), vars end -function linearization_function(sys::AbstractSystem, - inputs::Union{Symbol, Vector{Symbol}, AnalysisPoint, Vector{AnalysisPoint}}, +function ap_preprocessing(sys::AbstractSystem, inputs::Union{Symbol, Vector{Symbol}, AnalysisPoint, Vector{AnalysisPoint}}, outputs; loop_openings = [], system_modifier = identity, kwargs...) loop_openings = Set(map(nameof, canonicalize_ap(sys, loop_openings))) inputs = canonicalize_ap(sys, inputs) @@ -958,7 +957,13 @@ function linearization_function(sys::AbstractSystem, end sys = handle_loop_openings(sys, map(AnalysisPoint, collect(loop_openings))) + sys, input_vars, output_vars +end +function linearization_function(sys::AbstractSystem, + inputs::Union{Symbol, Vector{Symbol}, AnalysisPoint, Vector{AnalysisPoint}}, + outputs; loop_openings = [], system_modifier = identity, kwargs...) + sys, inputs, outputs = ap_preprocessing(sys; inputs, outputs, loop_openings, system_modifier, kwargs...) return linearization_function(system_modifier(sys), input_vars, output_vars; kwargs...) end diff --git a/src/systems/systems.jl b/src/systems/systems.jl index 3ed8df6e99..755cb8c15f 100644 --- a/src/systems/systems.jl +++ b/src/systems/systems.jl @@ -33,6 +33,10 @@ function structural_simplify( disturbance_inputs = Any[], kwargs...) isscheduled(sys) && throw(RepeatedStructuralSimplificationError()) + if inputs isa Union{Symbol, Vector{Symbol}, AnalysisPoint, Vector{AnalysisPoint}} + sys, inputs, outputs = ap_preprocessing(sys, inputs, outputs, kwargs...) + end + newsys′ = __structural_simplification(sys; simplify, allow_symbolic, allow_parameter, conservative, fully_determined, inputs, outputs, disturbance_inputs, diff --git a/test/downstream/linearize.jl b/test/downstream/linearize.jl index d51b0d17d6..48e98b3efc 100644 --- a/test/downstream/linearize.jl +++ b/test/downstream/linearize.jl @@ -33,7 +33,7 @@ lsys = linearize(sys2) @test lsys.C[] == 0 @test lsys.D[] == 1 -lsys = linearize(sys, r, r) # Test allow scalars +lsys = linearize(sys2, r, r) # Test allow scalars @test lsys.A[] == -2 @test lsys.B[] == 1 @@ -270,7 +270,8 @@ closed_loop = ODESystem(connections, t, systems = [model, pid, filt, sensor, r, filt.xd => 0.0 ]) -@test_nowarn linearize(closed_loop, :r, :y; warn_empty_op = false) +closed_loop = structural_simplify(closed_loop, inputs = :r, outputs = :y) +linearize(closed_loop; warn_empty_op = false) # https://discourse.julialang.org/t/mtk-change-in-linearize/115760/3 @mtkmodel Tank_noi begin @@ -300,6 +301,7 @@ end @named tank_noi = Tank_noi() @unpack md_i, h, m = tank_noi +tank_noi = structural_simplify(tank_noi, inputs = [md_i], outputs = [h]) m_ss = 2.4000000003229878 @test_nowarn linearize(tank_noi, [md_i], [h]; op = Dict(m => m_ss, md_i => 2)) @@ -308,6 +310,7 @@ m_ss = 2.4000000003229878 @parameters p = 1.0 eqs = [D(x) ~ p * u, x ~ y] @named sys = ODESystem(eqs, t) +sys = structural_simplify(sys, inputs = [u]) matrices1 = linearize(sys, [u], []; op = Dict(x => 2.0)) matrices2 = linearize(sys, [u], []; op = Dict(y => 2.0)) From 5feffd85778cb1067c3f6b66be775fa484669c7c Mon Sep 17 00:00:00 2001 From: vyudu Date: Wed, 23 Apr 2025 10:49:28 -0400 Subject: [PATCH 019/122] reset test file --- test/downstream/linearize.jl | 71 ++++++++++++++++-------------------- 1 file changed, 31 insertions(+), 40 deletions(-) diff --git a/test/downstream/linearize.jl b/test/downstream/linearize.jl index 48e98b3efc..16df29f834 100644 --- a/test/downstream/linearize.jl +++ b/test/downstream/linearize.jl @@ -13,27 +13,25 @@ eqs = [u ~ kp * (r - y) y ~ x] @named sys = ODESystem(eqs, t) -sys1 = structural_simplify(sys, inputs = [r], outputs = [y]) -lsys = linearize(sys1) -lprob = LinearizationProblem(sys1) +lsys, ssys = linearize(sys, [r], [y]) +lprob = LinearizationProblem(sys, [r], [y]) lsys2 = solve(lprob) -lsys3 = linearize(sys1; autodiff = AutoFiniteDiff()) +lsys3, _ = linearize(sys, [r], [y]; autodiff = AutoFiniteDiff()) @test lsys.A[] == lsys2.A[] == lsys3.A[] == -2 @test lsys.B[] == lsys2.B[] == lsys3.B[] == 1 @test lsys.C[] == lsys2.C[] == lsys3.C[] == 1 @test lsys.D[] == lsys2.D[] == lsys3.D[] == 0 -sys2 = structural_simplify(sys, inputs = [r], outputs = [r]) -lsys = linearize(sys2) +lsys, ssys = linearize(sys, [r], [r]) @test lsys.A[] == -2 @test lsys.B[] == 1 @test lsys.C[] == 0 @test lsys.D[] == 1 -lsys = linearize(sys2, r, r) # Test allow scalars +lsys, ssys = linearize(sys, r, r) # Test allow scalars @test lsys.A[] == -2 @test lsys.B[] == 1 @@ -88,13 +86,12 @@ connections = [f.y ~ c.r # filtered reference to controller reference p.y ~ c.y] @named cl = ODESystem(connections, t, systems = [f, c, p]) -cl = structural_simplify(cl, inputs = [f.u], outputs = [p.x]) -lsys0 = linearize(cl) +lsys0, ssys = linearize(cl, [f.u], [p.x]) desired_order = [f.x, p.x] -lsys = ModelingToolkit.reorder_unknowns(lsys0, unknowns(cl), desired_order) -lsys1 = linearize(cl, [f.u], [p.x]; autodiff = AutoFiniteDiff()) -lsys2 = ModelingToolkit.reorder_unknowns(lsys1, unknowns(cl), desired_order) +lsys = ModelingToolkit.reorder_unknowns(lsys0, unknowns(ssys), desired_order) +lsys1, ssys = linearize(cl, [f.u], [p.x]; autodiff = AutoFiniteDiff()) +lsys2 = ModelingToolkit.reorder_unknowns(lsys1, unknowns(ssys), desired_order) @test lsys.A == lsys2.A == [-2 0; 1 -2] @test lsys.B == lsys2.B == reshape([1, 0], 2, 1) @@ -102,7 +99,7 @@ lsys2 = ModelingToolkit.reorder_unknowns(lsys1, unknowns(cl), desired_order) @test lsys.D[] == lsys2.D[] == 0 ## Symbolic linearization -lsyss = ModelingToolkit.linearize_symbolic(cl) +lsyss, _ = ModelingToolkit.linearize_symbolic(cl, [f.u], [p.x]) @test ModelingToolkit.fixpoint_sub(lsyss.A, ModelingToolkit.defaults(cl)) == lsys.A @test ModelingToolkit.fixpoint_sub(lsyss.B, ModelingToolkit.defaults(cl)) == lsys.B @@ -117,11 +114,11 @@ Nd = 10 @named pid = LimPID(; k, Ti, Td, Nd) @unpack reference, measurement, ctr_output = pid -pid_s = structural_simplify(pid, inputs = [reference.u, measurement.u], outputs = [ctr_output.u]) -lsys0 = linearize(pid_s; op = Dict(reference.u => 0.0, measurement.u => 0.0)) -@unpack int, der = pid_s +lsys0, ssys = linearize(pid, [reference.u, measurement.u], [ctr_output.u]; + op = Dict(reference.u => 0.0, measurement.u => 0.0)) +@unpack int, der = pid desired_order = [int.x, der.x] -lsys = ModelingToolkit.reorder_unknowns(lsys0, unknowns(pid_s), desired_order) +lsys = ModelingToolkit.reorder_unknowns(lsys0, unknowns(ssys), desired_order) @test lsys.A == [0 0; 0 -10] @test lsys.B == [2 -2; 10 -10] @@ -151,17 +148,17 @@ lsys = ModelingToolkit.reorder_unknowns(lsys, desired_order, reverse(desired_ord ## Test that there is a warning when input is misspecified if VERSION >= v"1.8" - @test_throws "Some specified inputs were not found in system. The following variables were not found " structural_simplify(pid, inputs = + @test_throws "Some specified inputs were not found" linearize(pid, [ pid.reference.u, pid.measurement.u - ], outputs = [ctr_output.u]) - @test_throws "Some specified outputs were not found in system." structural_simplify(pid, - inputs = [ + ], [ctr_output.u]) + @test_throws "Some specified outputs were not found" linearize(pid, + [ reference.u, measurement.u ], - outputs = [pid.ctr_output.u]) + [pid.ctr_output.u]) else # v1.6 does not have the feature to match error message @test_throws ErrorException linearize(pid, [ @@ -187,23 +184,22 @@ function saturation(; y_max, y_min = y_max > 0 ? -y_max : -Inf, name) ODESystem(eqs, t, name = name) end @named sat = saturation(; y_max = 1) -sat = structural_simplify(sat, inputs = [u], outputs = [y]) # inside the linear region, the function is identity @unpack u, y = sat -lsys = linearize(sat, [u], [y]) +lsys, ssys = linearize(sat, [u], [y]) @test isempty(lsys.A) # there are no differential variables in this system @test isempty(lsys.B) @test isempty(lsys.C) @test lsys.D[] == 1 -@test_skip lsyss = ModelingToolkit.linearize_symbolic(sat, [u], [y]) # Code gen replaces ifelse with if statements causing symbolic evaluation to fail +@test_skip lsyss, _ = ModelingToolkit.linearize_symbolic(sat, [u], [y]) # Code gen replaces ifelse with if statements causing symbolic evaluation to fail # @test substitute(lsyss.A, ModelingToolkit.defaults(sat)) == lsys.A # @test substitute(lsyss.B, ModelingToolkit.defaults(sat)) == lsys.B # @test substitute(lsyss.C, ModelingToolkit.defaults(sat)) == lsys.C # @test substitute(lsyss.D, ModelingToolkit.defaults(sat)) == lsys.D # outside the linear region the derivative is 0 -lsys = linearize(sat, [u], [y]; op = Dict(u => 2)) +lsys, ssys = linearize(sat, [u], [y]; op = Dict(u => 2)) @test isempty(lsys.A) # there are no differential variables in this system @test isempty(lsys.B) @test isempty(lsys.C) @@ -270,8 +266,7 @@ closed_loop = ODESystem(connections, t, systems = [model, pid, filt, sensor, r, filt.xd => 0.0 ]) -closed_loop = structural_simplify(closed_loop, inputs = :r, outputs = :y) -linearize(closed_loop; warn_empty_op = false) +@test_nowarn linearize(closed_loop, :r, :y; warn_empty_op = false) # https://discourse.julialang.org/t/mtk-change-in-linearize/115760/3 @mtkmodel Tank_noi begin @@ -301,7 +296,6 @@ end @named tank_noi = Tank_noi() @unpack md_i, h, m = tank_noi -tank_noi = structural_simplify(tank_noi, inputs = [md_i], outputs = [h]) m_ss = 2.4000000003229878 @test_nowarn linearize(tank_noi, [md_i], [h]; op = Dict(m => m_ss, md_i => 2)) @@ -310,14 +304,13 @@ m_ss = 2.4000000003229878 @parameters p = 1.0 eqs = [D(x) ~ p * u, x ~ y] @named sys = ODESystem(eqs, t) -sys = structural_simplify(sys, inputs = [u]) -matrices1 = linearize(sys, [u], []; op = Dict(x => 2.0)) -matrices2 = linearize(sys, [u], []; op = Dict(y => 2.0)) +matrices1, _ = linearize(sys, [u], []; op = Dict(x => 2.0)) +matrices2, _ = linearize(sys, [u], []; op = Dict(y => 2.0)) @test matrices1 == matrices2 # Ensure parameter values passed as `Dict` are respected -linfun = linearization_function(sys, [u], []; op = Dict(x => 2.0)) +linfun, _ = linearization_function(sys, [u], []; op = Dict(x => 2.0)) matrices = linfun([1.0], Dict(p => 3.0), 1.0) # this would be 1 if the parameter value isn't respected @test matrices.f_u[] == 3.0 @@ -333,28 +326,26 @@ end @parameters p eqs = [0 ~ x * log(y) - p] @named sys = ODESystem(eqs, t; defaults = [p => 1.0]) - sys = structural_simplify(sys, inputs = [x]) + sys = complete(sys) @test_throws ModelingToolkit.MissingVariablesError linearize( - sys; op = Dict(x => 1.0), allow_input_derivatives = true) + sys, [x], []; op = Dict(x => 1.0), allow_input_derivatives = true) @test_nowarn linearize( - sys; op = Dict(x => 1.0), guesses = Dict(y => 1.0), + sys, [x], []; op = Dict(x => 1.0), guesses = Dict(y => 1.0), allow_input_derivatives = true) end @testset "Symbolic values for parameters in `linearize`" begin @named tank_noi = Tank_noi() @unpack md_i, h, m, ρ, A, K = tank_noi - tank_noi = structural_simplify(tank_noi, inputs = [md_i], outputs = [h]) m_ss = 2.4000000003229878 @test_nowarn linearize( - tank_noi; op = Dict(m => m_ss, md_i => 2, ρ => A / K, A => 5)) + tank_noi, [md_i], [h]; op = Dict(m => m_ss, md_i => 2, ρ => A / K, A => 5)) end @testset "Warn on empty operating point" begin @named tank_noi = Tank_noi() @unpack md_i, h, m = tank_noi - tank_noi = structural_simplify(tank_noi, inputs = [md_i], outputs = [h]) m_ss = 2.4000000003229878 @test_warn ["empty operating point", "warn_empty_op"] linearize( - tank_noi; p = [md_i => 1.0]) + tank_noi, [md_i], [h]; p = [md_i => 1.0]) end From e6eab844fcc6b9d80ecc65ebc597152f858c796d Mon Sep 17 00:00:00 2001 From: vyudu Date: Wed, 23 Apr 2025 11:07:16 -0400 Subject: [PATCH 020/122] revert src/linearization --- src/linearization.jl | 47 +++++++++++++++++++--------------- src/systems/analysis_points.jl | 10 ++------ 2 files changed, 29 insertions(+), 28 deletions(-) diff --git a/src/linearization.jl b/src/linearization.jl index 097e05e79f..451f262476 100644 --- a/src/linearization.jl +++ b/src/linearization.jl @@ -1,5 +1,5 @@ """ - lin_fun, simplified_sys = linearization_function(sys::AbstractSystem, inputs, outputs; initialize = true, initialization_solver_alg = TrustRegion(), kwargs...) + lin_fun, simplified_sys = linearization_function(sys::AbstractSystem, inputs, outputs; simplify = false, initialize = true, initialization_solver_alg = TrustRegion(), kwargs...) Return a function that linearizes the system `sys`. The function [`linearize`](@ref) provides a higher-level and easier to use interface. @@ -22,6 +22,7 @@ The `simplified_sys` has undergone [`structural_simplify`](@ref) and had any occ - `sys`: An [`ODESystem`](@ref). This function will automatically apply simplification passes on `sys` and return the resulting `simplified_sys`. - `inputs`: A vector of variables that indicate the inputs of the linearized input-output model. - `outputs`: A vector of variables that indicate the outputs of the linearized input-output model. + - `simplify`: Apply simplification in tearing. - `initialize`: If true, a check is performed to ensure that the operating point is consistent (satisfies algebraic equations). If the op is not consistent, initialization is performed. - `initialization_solver_alg`: A NonlinearSolve algorithm to use for solving for a feasible set of state and algebraic variables that satisfies the specified operating point. - `autodiff`: An `ADType` supported by DifferentiationInterface.jl to use for calculating the necessary jacobians. Defaults to using `AutoForwardDiff()` @@ -29,8 +30,8 @@ The `simplified_sys` has undergone [`structural_simplify`](@ref) and had any occ See also [`linearize`](@ref) which provides a higher-level interface. """ -function linearization_function(sys::AbstractSystem, inputs = inputs(sys), - outputs = outputs(sys); +function linearization_function(sys::AbstractSystem, inputs, + outputs; simplify = false, initialize = true, initializealg = nothing, initialization_abstol = 1e-5, @@ -45,9 +46,6 @@ function linearization_function(sys::AbstractSystem, inputs = inputs(sys), guesses = Dict(), warn_empty_op = true, kwargs...) - if !iscomplete(sys) - error("A simplified `ODESystem` is required. Call `structural_simplify` on the system with the inputs and outputs before creating the linearization function.") - end op = Dict(op) if isempty(op) && warn_empty_op @warn "An empty operating point was passed to `linearization_function`. An operating point containing the variables that will be changed in `linearize` should be provided. Disable this warning by passing `warn_empty_op = false`." @@ -60,7 +58,15 @@ function linearization_function(sys::AbstractSystem, inputs = inputs(sys), outputs = mapreduce(vcat, outputs; init = []) do var symbolic_type(var) == ArraySymbolic() ? collect(var) : [var] end - diff_idxs, alge_idxs = eq_idxs(sys) + ssys = structural_simplify(sys; inputs, outputs, simplify, kwargs...) + diff_idxs, alge_idxs = eq_idxs(ssys) + if zero_dummy_der + dummyder = setdiff(unknowns(ssys), unknowns(sys)) + defs = Dict(x => 0.0 for x in dummyder) + @set! ssys.defaults = merge(defs, defaults(ssys)) + op = merge(defs, op) + end + sys = ssys if initializealg === nothing initializealg = initialize ? OverrideInit() : NoInit() @@ -123,7 +129,7 @@ function linearization_function(sys::AbstractSystem, inputs = inputs(sys), diff_idxs, alge_idxs, inputs, length(unknowns(sys)), prob, h, u0 === nothing ? nothing : similar(u0), uf_jac, h_jac, pf_jac, hp_jac, initializealg, initialization_kwargs) - return lin_fun + return lin_fun, sys end function eq_idxs(sys::AbstractSystem) @@ -197,7 +203,7 @@ A callable struct which linearizes a system. $(TYPEDFIELDS) """ struct LinearizationFunction{ - DI <: AbstractVector{Int}, AI <: AbstractVector{Int}, II, P <: ODEProblem, + DI <: AbstractVector{Int}, AI <: AbstractVector{Int}, I, P <: ODEProblem, H, C, J1, J2, J3, J4, IA <: SciMLBase.DAEInitializationAlgorithm, IK} """ The indexes of differential equations in the linearized system. @@ -211,7 +217,7 @@ struct LinearizationFunction{ The indexes of parameters in the linearized system which represent input variables. """ - inputs::II + inputs::I """ The number of unknowns in the linearized system. """ @@ -396,8 +402,8 @@ Construct a `LinearizationProblem` for linearizing the system `sys` with the giv All other keyword arguments are forwarded to `linearization_function`. """ -function LinearizationProblem(sys::AbstractSystem, inputs = inputs(sys), outputs = outputs(sys); t = 0.0, kwargs...) - linfun = linearization_function(sys, inputs, outputs; kwargs...) +function LinearizationProblem(sys::AbstractSystem, inputs, outputs; t = 0.0, kwargs...) + linfun, _ = linearization_function(sys, inputs, outputs; kwargs...) return LinearizationProblem(linfun, t) end @@ -467,7 +473,7 @@ function CommonSolve.solve(prob::LinearizationProblem; allow_input_derivatives = end """ - (; A, B, C, D), simplified_sys = linearize_symbolic(sys::AbstractSystem, inputs, outputs; allow_input_derivatives = false, kwargs...) + (; A, B, C, D), simplified_sys = linearize_symbolic(sys::AbstractSystem, inputs, outputs; simplify = false, allow_input_derivatives = false, kwargs...) Similar to [`linearize`](@ref), but returns symbolic matrices `A,B,C,D` rather than numeric. While `linearize` uses ForwardDiff to perform the linearization, this function uses `Symbolics.jacobian`. @@ -484,10 +490,11 @@ y &= h(x, z, u) ``` where `x` are differential unknown variables, `z` algebraic variables, `u` inputs and `y` outputs. """ -function linearize_symbolic(sys::AbstractSystem, inputs = inputs(sys), - outputs = outputs(sys); allow_input_derivatives = false, +function linearize_symbolic(sys::AbstractSystem, inputs, + outputs; simplify = false, allow_input_derivatives = false, eval_expression = false, eval_module = @__MODULE__, kwargs...) + sys = structural_simplify(sys; inputs, outputs, simplify, kwargs...) diff_idxs, alge_idxs = eq_idxs(sys) sts = unknowns(sys) t = get_iv(sys) @@ -546,7 +553,7 @@ function linearize_symbolic(sys::AbstractSystem, inputs = inputs(sys), end end - (; A, B, C, D, f_x, f_z, g_x, g_z, f_u, g_u, h_x, h_z, h_u) + (; A, B, C, D, f_x, f_z, g_x, g_z, f_u, g_u, h_x, h_z, h_u), sys end function markio!(state, orig_inputs, inputs, outputs; check = true) @@ -713,17 +720,17 @@ function linearize(sys, lin_fun::LinearizationFunction; t = 0.0, return solve(prob; allow_input_derivatives) end -function linearize(sys, inputs = inputs(sys), outputs = outputs(sys); op = Dict(), t = 0.0, +function linearize(sys, inputs, outputs; op = Dict(), t = 0.0, allow_input_derivatives = false, zero_dummy_der = false, kwargs...) - lin_fun = linearization_function(sys, + lin_fun, ssys = linearization_function(sys, inputs, outputs; zero_dummy_der, op, kwargs...) - linearize(sys, lin_fun; op, t, allow_input_derivatives) + linearize(ssys, lin_fun; op, t, allow_input_derivatives), ssys end """ @@ -761,7 +768,7 @@ Permute the state representation of `sys` obtained from [`linearize`](@ref) so t Example: ``` -lsys = linearize(pid, [reference.u, measurement.u], [ctr_output.u]) +lsys, ssys = linearize(pid, [reference.u, measurement.u], [ctr_output.u]) desired_order = [int.x, der.x] # Unknowns that are present in unknowns(ssys) lsys = ModelingToolkit.reorder_unknowns(lsys, unknowns(ssys), desired_order) ``` diff --git a/src/systems/analysis_points.jl b/src/systems/analysis_points.jl index 4e98592f43..29f1e777ac 100644 --- a/src/systems/analysis_points.jl +++ b/src/systems/analysis_points.jl @@ -928,7 +928,8 @@ function open_loop(sys, ap::Union{Symbol, AnalysisPoint}; system_modifier = iden return system_modifier(sys), vars end -function ap_preprocessing(sys::AbstractSystem, inputs::Union{Symbol, Vector{Symbol}, AnalysisPoint, Vector{AnalysisPoint}}, +function linearization_function(sys::AbstractSystem, + inputs::Union{Symbol, Vector{Symbol}, AnalysisPoint, Vector{AnalysisPoint}}, outputs; loop_openings = [], system_modifier = identity, kwargs...) loop_openings = Set(map(nameof, canonicalize_ap(sys, loop_openings))) inputs = canonicalize_ap(sys, inputs) @@ -957,13 +958,6 @@ function ap_preprocessing(sys::AbstractSystem, inputs::Union{Symbol, Vector{Symb end sys = handle_loop_openings(sys, map(AnalysisPoint, collect(loop_openings))) - sys, input_vars, output_vars -end - -function linearization_function(sys::AbstractSystem, - inputs::Union{Symbol, Vector{Symbol}, AnalysisPoint, Vector{AnalysisPoint}}, - outputs; loop_openings = [], system_modifier = identity, kwargs...) - sys, inputs, outputs = ap_preprocessing(sys; inputs, outputs, loop_openings, system_modifier, kwargs...) return linearization_function(system_modifier(sys), input_vars, output_vars; kwargs...) end From 4e253631603853bf30cf8f07c1074691dbc300d4 Mon Sep 17 00:00:00 2001 From: vyudu Date: Wed, 23 Apr 2025 11:10:29 -0400 Subject: [PATCH 021/122] reset doc file --- docs/src/basics/Linearization.md | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/docs/src/basics/Linearization.md b/docs/src/basics/Linearization.md index 78b6d5925d..1c06ce72d4 100644 --- a/docs/src/basics/Linearization.md +++ b/docs/src/basics/Linearization.md @@ -15,7 +15,7 @@ y &= Cx + Du \end{aligned} ``` -The `linearize` function expects the user to specify the inputs ``u`` and the outputs ``y`` using the syntax shown in the example below. +The `linearize` function expects the user to specify the inputs ``u`` and the outputs ``y`` using the syntax shown in the example below. The system model is *not* supposed to be simplified before calling `linearize`: ## Example @@ -29,7 +29,7 @@ eqs = [u ~ kp * (r - y) # P controller D(x) ~ -x + u # First-order plant y ~ x] # Output equation -@mtkbuild sys = ODESystem(eqs, t) # Do not call @mtkbuild when linearizing +@named sys = ODESystem(eqs, t) # Do not call @mtkbuild when linearizing matrices, simplified_sys = linearize(sys, [r], [y]) # Linearize from r to y matrices ``` @@ -45,6 +45,10 @@ using ModelingToolkit: inputs, outputs The model above has 4 variables but only three equations, there is no equation specifying the value of `r` since `r` is an input. This means that only unbalanced models can be linearized, or in other words, models that are balanced and can be simulated _cannot_ be linearized. To learn more about this, see [How to linearize a ModelingToolkit model (YouTube)](https://www.youtube.com/watch?v=-XOux-2XDGI&t=395s). Also see [ModelingToolkitStandardLibrary: Linear analysis](https://docs.sciml.ai/ModelingToolkit/stable/tutorials/linear_analysis/) for utilities that make linearization of completed models easier. +!!! note "Un-simplified system" + + Linearization expects `sys` to be un-simplified, i.e., `structural_simplify` or `@mtkbuild` should not be called on the system before linearizing. + ## Operating point The operating point to linearize around can be specified with the keyword argument `op` like this: `op = Dict(x => 1, r => 2)`. The operating point may include specification of unknown variables, input variables and parameters. For variables that are not specified in `op`, the default value specified in the model will be used if available, if no value is specified, an error is thrown. @@ -75,15 +79,14 @@ eqs = [D(x) ~ v y.u ~ x] @named duffing = ODESystem(eqs, t, systems = [y, u], defaults = [u.u => 0]) -duffing = structural_simplify(duffing, inputs = [u.u], outputs = [y.u]) # pass a constant value for `x`, since it is the variable we will change in operating points -linfun = linearization_function(duffing, [u.u], [y.u]; op = Dict(x => NaN)); +linfun, simplified_sys = linearization_function(duffing, [u.u], [y.u]; op = Dict(x => NaN)); -println(linearize(duffing, linfun; op = Dict(x => 1.0))) -println(linearize(duffing, linfun; op = Dict(x => 0.0))) +println(linearize(simplified_sys, linfun; op = Dict(x => 1.0))) +println(linearize(simplified_sys, linfun; op = Dict(x => 0.0))) -@time linearize(duffing, linfun; op = Dict(x => 0.0)) +@time linearize(simplified_sys, linfun; op = Dict(x => 0.0)) nothing # hide ``` From f4723b87f45071b26bc79587516f1322b9c9703d Mon Sep 17 00:00:00 2001 From: vyudu Date: Wed, 23 Apr 2025 11:15:54 -0400 Subject: [PATCH 022/122] revert rename --- src/systems/systems.jl | 14 +++++--------- src/systems/systemstructure.jl | 1 - 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/src/systems/systems.jl b/src/systems/systems.jl index 755cb8c15f..e61a4471a6 100644 --- a/src/systems/systems.jl +++ b/src/systems/systems.jl @@ -33,11 +33,7 @@ function structural_simplify( disturbance_inputs = Any[], kwargs...) isscheduled(sys) && throw(RepeatedStructuralSimplificationError()) - if inputs isa Union{Symbol, Vector{Symbol}, AnalysisPoint, Vector{AnalysisPoint}} - sys, inputs, outputs = ap_preprocessing(sys, inputs, outputs, kwargs...) - end - - newsys′ = __structural_simplification(sys; simplify, + newsys′ = __structural_simplify(sys; simplify, allow_symbolic, allow_parameter, conservative, fully_determined, inputs, outputs, disturbance_inputs, kwargs...) @@ -69,15 +65,15 @@ function structural_simplify( end end -function __structural_simplification(sys::JumpSystem, args...; kwargs...) +function __structural_simplify(sys::JumpSystem, args...; kwargs...) return sys end -function __structural_simplification(sys::SDESystem, args...; kwargs...) - return __structural_simplification(ODESystem(sys), args...; kwargs...) +function __structural_simplify(sys::SDESystem, args...; kwargs...) + return __structural_simplify(ODESystem(sys), args...; kwargs...) end -function __structural_simplification(sys::AbstractSystem; simplify = false, +function __structural_simplify(sys::AbstractSystem; simplify = false, inputs = Any[], outputs = Any[], disturbance_inputs = Any[], kwargs...) diff --git a/src/systems/systemstructure.jl b/src/systems/systemstructure.jl index bbad46217e..e39ed38657 100644 --- a/src/systems/systemstructure.jl +++ b/src/systems/systemstructure.jl @@ -731,7 +731,6 @@ function _structural_simplify!(state::TearingState; simplify = false, ModelingToolkit.markio!(state, orig_inputs, inputs, outputs) state = ModelingToolkit.inputs_to_parameters!(state, inputs) end - sys, mm = ModelingToolkit.alias_elimination!(state; kwargs...) if check_consistency fully_determined = ModelingToolkit.check_consistency( From 81fc5b53686fd23d33223885402168f8cb280f88 Mon Sep 17 00:00:00 2001 From: vyudu Date: Thu, 24 Apr 2025 15:37:58 -0400 Subject: [PATCH 023/122] test: test updates --- src/systems/systems.jl | 4 +--- test/clock.jl | 5 ++--- test/code_generation.jl | 2 +- test/input_output_handling.jl | 8 ++++---- test/odesystem.jl | 6 +++--- test/reduction.jl | 2 +- 6 files changed, 12 insertions(+), 15 deletions(-) diff --git a/src/systems/systems.jl b/src/systems/systems.jl index e61a4471a6..7b40a46a01 100644 --- a/src/systems/systems.jl +++ b/src/systems/systems.jl @@ -17,13 +17,11 @@ $(SIGNATURES) Structurally simplify algebraic equations in a system and compute the topological sort of the observed equations in `sys`. -### Optional Arguments: -+ optional argument `io` may take a tuple `(inputs, outputs)`. This will convert all `inputs` to parameters and allow them to be unconnected, i.e., simplification will allow models where `n_unknowns = n_equations - n_inputs`. - ### Optional Keyword Arguments: + When `simplify=true`, the `simplify` function will be applied during the tearing process. + `allow_symbolic=false`, `allow_parameter=true`, and `conservative=false` limit the coefficient types during tearing. In particular, `conservative=true` limits tearing to only solve for trivial linear systems where the coefficient has the absolute value of ``1``. + `fully_determined=true` controls whether or not an error will be thrown if the number of equations don't match the number of inputs, outputs, and equations. ++ `inputs`, `outputs` and `disturbance_inputs` are passed as keyword arguments.` All inputs` get converted to parameters and are allowed to be unconnected, allowing models where `n_unknowns = n_equations - n_inputs`. + `sort_eqs=true` controls whether equations are sorted lexicographically before simplification or not. """ function structural_simplify( diff --git a/test/clock.jl b/test/clock.jl index c6051a52a8..4fb9341d87 100644 --- a/test/clock.jl +++ b/test/clock.jl @@ -65,10 +65,9 @@ By inference: ci, varmap = infer_clocks(sys) eqmap = ci.eq_domain tss, inputs, continuous_id = ModelingToolkit.split_system(deepcopy(ci)) -sss, = ModelingToolkit._structural_simplify!( - deepcopy(tss[continuous_id]), (inputs[continuous_id], ())) +sss = ModelingToolkit._structural_simplify!(deepcopy(tss[continuous_id]), inputs = inputs[continuous_id], outputs = []) @test equations(sss) == [D(x) ~ u - x] -sss, = ModelingToolkit._structural_simplify!(deepcopy(tss[1]), (inputs[1], ())) +sss = ModelingToolkit._structural_simplify!(deepcopy(tss[1]), inputs = inputs[1], outputs = []) @test isempty(equations(sss)) d = Clock(dt) k = ShiftIndex(d) diff --git a/test/code_generation.jl b/test/code_generation.jl index cf3d660b81..3ef5ac3e11 100644 --- a/test/code_generation.jl +++ b/test/code_generation.jl @@ -70,7 +70,7 @@ end @parameters p[1:2] (f::Function)(..) @named sys = ODESystem( [D(x[0]) ~ p[1] * x[0] + x[2], D(x[1]) ~ p[2] * f(x) + x[2]], t) - sys, = structural_simplify(sys, ([x[2]], [])) + sys, = structural_simplify(sys, inputs = [x[2]], outputs = []) @test is_parameter(sys, x[2]) prob = ODEProblem(sys, [x[0] => 1.0, x[1] => 1.0], (0.0, 1.0), [p => ones(2), f => sum, x[2] => 2.0]) diff --git a/test/input_output_handling.jl b/test/input_output_handling.jl index 693a00b9ad..ee107dff4a 100644 --- a/test/input_output_handling.jl +++ b/test/input_output_handling.jl @@ -50,7 +50,7 @@ end @test !is_bound(sys31, sys1.v[2]) # simplification turns input variables into parameters -ssys, _ = structural_simplify(sys, ([u], [])) +ssys = structural_simplify(sys, inputs = [u], outputs = []) @test ModelingToolkit.isparameter(unbound_inputs(ssys)[]) @test !is_bound(ssys, u) @test u ∈ Set(unbound_inputs(ssys)) @@ -281,7 +281,7 @@ i = findfirst(isequal(u[1]), out) @variables x(t) u(t) [input = true] eqs = [D(x) ~ u] @named sys = ODESystem(eqs, t) -@test_nowarn structural_simplify(sys, ([u], [])) +@test_nowarn structural_simplify(sys, inputs = [u], outputs = []) #= ## Disturbance input handling @@ -366,9 +366,9 @@ eqs = [D(y₁) ~ -k₁ * y₁ + k₃ * y₂ * y₃ + u1 @named sys = ODESystem(eqs, t) m_inputs = [u[1], u[2]] m_outputs = [y₂] -sys_simp, input_idxs = structural_simplify(sys, (; inputs = m_inputs, outputs = m_outputs)) +sys_simp = structural_simplify(sys, inputs = m_inputs, outputs = m_outputs) @test isequal(unknowns(sys_simp), collect(x[1:2])) -@test length(input_idxs) == 2 +@test length(inputs(sys_simp)) == 2 # https://github.com/SciML/ModelingToolkit.jl/issues/1577 @named c = Constant(; k = 2) diff --git a/test/odesystem.jl b/test/odesystem.jl index f5fef1fd6f..08c37d30bc 100644 --- a/test/odesystem.jl +++ b/test/odesystem.jl @@ -1285,11 +1285,11 @@ end @named sys = ODESystem( [D(u) ~ (sum(u) + sum(x) + sum(p) + sum(o)) * x, o ~ prod(u) * x], t, [u..., x..., o...], [p...]) - sys1, = structural_simplify(sys, ([x...], [])) + sys1 = structural_simplify(sys, inputs = [x...], outputs = []) fn1, = ModelingToolkit.generate_function(sys1; expression = Val{false}) ps = MTKParameters(sys1, [x => 2ones(2), p => 3ones(2, 2)]) @test_nowarn fn1(ones(4), ps, 4.0) - sys2, = structural_simplify(sys, ([x...], []); split = false) + sys2 = structural_simplify(sys, inputs = [x...], outputs = [], split = false) fn2, = ModelingToolkit.generate_function(sys2; expression = Val{false}) ps = zeros(8) setp(sys2, x)(ps, 2ones(2)) @@ -1398,7 +1398,7 @@ end o[2] ~ sum(p) * sum(x)] @named sys = ODESystem(eqs, t, [u..., x..., o], [p...]) - sys1, = structural_simplify(sys, ([x...], [o...]), split = false) + sys1 = structural_simplify(sys, inputs = [x...], outputs = [o...], split = false) @test_nowarn ModelingToolkit.build_explicit_observed_function(sys1, u; inputs = [x...]) diff --git a/test/reduction.jl b/test/reduction.jl index fa9029a652..11992d29d6 100644 --- a/test/reduction.jl +++ b/test/reduction.jl @@ -233,7 +233,7 @@ eqs = [D(x) ~ σ * (y - x) u ~ z + a] lorenz1 = ODESystem(eqs, t, name = :lorenz1) -lorenz1_reduced, _ = structural_simplify(lorenz1, ([z], [])) +lorenz1_reduced, _ = structural_simplify(lorenz1, inputs = [z], outputs = []) @test z in Set(parameters(lorenz1_reduced)) # #2064 From a86e5533efbe5bcc2df77fe8258d727107cc50b8 Mon Sep 17 00:00:00 2001 From: vyudu Date: Thu, 24 Apr 2025 17:35:48 -0400 Subject: [PATCH 024/122] fix input output tests --- src/inputoutput.jl | 13 +++++++------ src/linearization.jl | 9 ++++++++- src/systems/systems.jl | 1 - src/systems/systemstructure.jl | 4 ++-- src/variables.jl | 2 ++ test/input_output_handling.jl | 29 ++++++++++++++++------------- test/reduction.jl | 2 +- 7 files changed, 36 insertions(+), 24 deletions(-) diff --git a/src/inputoutput.jl b/src/inputoutput.jl index c03da22abf..3dd96a03cf 100644 --- a/src/inputoutput.jl +++ b/src/inputoutput.jl @@ -197,10 +197,10 @@ function generate_control_function(sys::AbstractODESystem, inputs = unbound_inpu simplify = false, eval_expression = false, eval_module = @__MODULE__, + check_simplified = true, kwargs...) - # Remove this when the ControlFunction gets merged. - if !iscomplete(sys) + if check_simplified && !iscomplete(sys) error("A completed `ODESystem` is required. Call `complete` or `structural_simplify` on the system before creating the control function.") end isempty(inputs) && @warn("No unbound inputs were found in system.") @@ -259,7 +259,7 @@ end """ Turn input variables into parameters of the system. """ -function inputs_to_parameters!(state::TransformationState, inputsyms) +function inputs_to_parameters!(state::TransformationState, inputsyms; is_disturbance = false) check_bound = inputsyms === nothing @unpack structure, fullvars, sys = state @unpack var_to_diff, graph, solvable_graph = structure @@ -414,7 +414,7 @@ function add_input_disturbance(sys, dist::DisturbanceModel, inputs = Any[]; kwar @variables u(t)=0 [input = true] # New system input dsys = get_disturbance_system(dist) - if inputs === nothing + if isempty(inputs) all_inputs = [u] else i = findfirst(isequal(dist.input), inputs) @@ -429,8 +429,9 @@ function add_input_disturbance(sys, dist::DisturbanceModel, inputs = Any[]; kwar dist.input ~ u + dsys.output.u[1]] augmented_sys = ODESystem(eqs, t, systems = [dsys], name = gensym(:outer)) augmented_sys = extend(augmented_sys, sys) + ssys = structural_simplify(augmented_sys, inputs = all_inputs, disturbance_inputs = [d]) - (f_oop, f_ip), dvs, p, io_sys = generate_control_function(augmented_sys, all_inputs, - [d]; kwargs...) + (f_oop, f_ip), dvs, p, io_sys = generate_control_function(ssys, all_inputs, + [d]; check_simplified = false, kwargs...) (f_oop, f_ip), augmented_sys, dvs, p, io_sys end diff --git a/src/linearization.jl b/src/linearization.jl index 451f262476..efbb3c3ab4 100644 --- a/src/linearization.jl +++ b/src/linearization.jl @@ -556,10 +556,11 @@ function linearize_symbolic(sys::AbstractSystem, inputs, (; A, B, C, D, f_x, f_z, g_x, g_z, f_u, g_u, h_x, h_z, h_u), sys end -function markio!(state, orig_inputs, inputs, outputs; check = true) +function markio!(state, orig_inputs, inputs, outputs, disturbances; check = true) fullvars = get_fullvars(state) inputset = Dict{Any, Bool}(i => false for i in inputs) outputset = Dict{Any, Bool}(o => false for o in outputs) + disturbanceset = Dict{Any, Bool}(d => false for d in disturbances) for (i, v) in enumerate(fullvars) if v in keys(inputset) if v in keys(outputset) @@ -581,6 +582,12 @@ function markio!(state, orig_inputs, inputs, outputs; check = true) v = setio(v, false, false) fullvars[i] = v end + + if v in keys(disturbanceset) + v = setio(v, true, false) + v = setdisturbance(v, true) + fullvars[i] = v + end end if check ikeys = keys(filter(!last, inputset)) diff --git a/src/systems/systems.jl b/src/systems/systems.jl index 7b40a46a01..acd2b4efc9 100644 --- a/src/systems/systems.jl +++ b/src/systems/systems.jl @@ -124,7 +124,6 @@ function __structural_simplify(sys::AbstractSystem; simplify = false, for (i, v) in enumerate(fullvars) if !iszero(new_idxs[i]) && invview(var_to_diff)[i] === nothing] - # TODO: IO is not handled. ode_sys = structural_simplify(sys; simplify, inputs, outputs, disturbance_inputs, kwargs...) eqs = equations(ode_sys) sorted_g_rows = zeros(Num, length(eqs), size(g, 2)) diff --git a/src/systems/systemstructure.jl b/src/systems/systemstructure.jl index e39ed38657..2bae98cbdd 100644 --- a/src/systems/systemstructure.jl +++ b/src/systems/systemstructure.jl @@ -728,8 +728,8 @@ function _structural_simplify!(state::TearingState; simplify = false, has_io = inputs !== nothing || outputs !== nothing orig_inputs = Set() if has_io - ModelingToolkit.markio!(state, orig_inputs, inputs, outputs) - state = ModelingToolkit.inputs_to_parameters!(state, inputs) + ModelingToolkit.markio!(state, orig_inputs, inputs, outputs, disturbance_inputs) + state = ModelingToolkit.inputs_to_parameters!(state, [inputs; disturbance_inputs]) end sys, mm = ModelingToolkit.alias_elimination!(state; kwargs...) if check_consistency diff --git a/src/variables.jl b/src/variables.jl index 83e72cea35..4cd08dc96a 100644 --- a/src/variables.jl +++ b/src/variables.jl @@ -349,6 +349,8 @@ function isdisturbance(x) Symbolics.getmetadata(x, VariableDisturbance, false) end +setdisturbance(x, v) = setmetadata(x, VariableDisturbance, v) + function disturbances(sys) [filter(isdisturbance, unknowns(sys)); filter(isdisturbance, parameters(sys))] end diff --git a/test/input_output_handling.jl b/test/input_output_handling.jl index ee107dff4a..1a1c9a6b65 100644 --- a/test/input_output_handling.jl +++ b/test/input_output_handling.jl @@ -7,10 +7,10 @@ using ModelingToolkit: t_nounits as t, D_nounits as D @variables xx(t) some_input(t) [input = true] eqs = [D(xx) ~ some_input] @named model = ODESystem(eqs, t) -@test_throws ExtraVariablesSystemException structural_simplify(model, ((), ())) +@test_throws ExtraVariablesSystemException structural_simplify(model) if VERSION >= v"1.8" err = "In particular, the unset input(s) are:\n some_input(t)" - @test_throws err structural_simplify(model, ((), ())) + @test_throws err structural_simplify(model) end # Test input handling @@ -88,7 +88,7 @@ fsys4 = flatten(sys4) @variables x(t) y(t) [output = true] @test isoutput(y) @named sys = ODESystem([D(x) ~ -x, y ~ x], t) # both y and x are unbound -syss = structural_simplify(sys) # This makes y an observed variable +syss = structural_simplify(sys, outputs = [y]) # This makes y an observed variable @named sys2 = ODESystem([D(x) ~ -sys.x, y ~ sys.y], t, systems = [sys]) @@ -106,7 +106,7 @@ syss = structural_simplify(sys) # This makes y an observed variable @test isequal(unbound_outputs(sys2), [y]) @test isequal(bound_outputs(sys2), [sys.y]) -syss = structural_simplify(sys2) +syss = structural_simplify(sys2, outputs = [sys.y]) @test !is_bound(syss, y) @test !is_bound(syss, x) @@ -165,6 +165,7 @@ end ] @named sys = ODESystem(eqs, t) + sys = structural_simplify(sys, inputs = [u]) f, dvs, ps, io_sys = ModelingToolkit.generate_control_function(sys; simplify, split) @test isequal(dvs[], x) @@ -182,8 +183,8 @@ end ] @named sys = ODESystem(eqs, t) - f, dvs, ps, io_sys = ModelingToolkit.generate_control_function( - sys, [u], [d]; simplify, split) + sys = structural_simplify(sys, inputs = [u], disturbance_inputs = [d]) + f, dvs, ps, io_sys = ModelingToolkit.generate_control_function(sys; simplify, split) @test isequal(dvs[], x) @test isempty(ps) @@ -200,8 +201,8 @@ end ] @named sys = ODESystem(eqs, t) - f, dvs, ps, io_sys = ModelingToolkit.generate_control_function( - sys, [u], [d]; simplify, split, disturbance_argument = true) + sys = structural_simplify(sys, inputs = [u], disturbance_inputs = [d]) + f, dvs, ps, io_sys = ModelingToolkit.generate_control_function(sys; simplify, split, disturbance_argument = true) @test isequal(dvs[], x) @test isempty(ps) @@ -265,9 +266,9 @@ eqs = [connect_sd(sd, mass1, mass2) @named _model = ODESystem(eqs, t) @named model = compose(_model, mass1, mass2, sd); +model = structural_simplify(model, inputs = [u]) f, dvs, ps, io_sys = ModelingToolkit.generate_control_function(model, simplify = true) @test length(dvs) == 4 -@test length(ps) == length(parameters(model)) p = MTKParameters(io_sys, [io_sys.u => NaN]) x = ModelingToolkit.varmap_to_vars( merge(ModelingToolkit.defaults(model), @@ -389,7 +390,7 @@ sys = structural_simplify(model) ## Disturbance models when plant has multiple inputs using ModelingToolkit, LinearAlgebra -using ModelingToolkit: DisturbanceModel, io_preprocessing, get_iv, get_disturbance_system +using ModelingToolkit: DisturbanceModel, get_iv, get_disturbance_system using ModelingToolkitStandardLibrary.Blocks A, C = [randn(2, 2) for i in 1:2] B = [1.0 0; 0 1.0] @@ -433,6 +434,7 @@ matrices = ModelingToolkit.reorder_unknowns( ] @named sys = ODESystem(eqs, t) + sys = structural_simplify(sys, inputs = [u]) (; io_sys,) = ModelingToolkit.generate_control_function(sys, simplify = true) obsfn = ModelingToolkit.build_explicit_observed_function( io_sys, [x + u * t]; inputs = [u]) @@ -444,9 +446,9 @@ end @constants c = 2.0 @variables x(t) eqs = [D(x) ~ c * x] - @named sys = ODESystem(eqs, t, [x], []) + @mtkbuild sys = ODESystem(eqs, t, [x], []) - f, dvs, ps, io_sys = ModelingToolkit.generate_control_function(sys, simplify = true) + f, dvs, ps, io_sys = ModelingToolkit.generate_control_function(sys) @test f[1]([0.5], nothing, MTKParameters(io_sys, []), 0.0) ≈ [1.0] end @@ -455,7 +457,8 @@ end @parameters p(::Real) = (x -> 2x) eqs = [D(x) ~ -x + p(u)] @named sys = ODESystem(eqs, t) - f, dvs, ps, io_sys = ModelingToolkit.generate_control_function(sys, simplify = true) + sys = structural_simplify(sys, inputs = [u]) + f, dvs, ps, io_sys = ModelingToolkit.generate_control_function(sys) p = MTKParameters(io_sys, []) u = [1.0] x = [1.0] diff --git a/test/reduction.jl b/test/reduction.jl index 11992d29d6..9a515614d5 100644 --- a/test/reduction.jl +++ b/test/reduction.jl @@ -233,7 +233,7 @@ eqs = [D(x) ~ σ * (y - x) u ~ z + a] lorenz1 = ODESystem(eqs, t, name = :lorenz1) -lorenz1_reduced, _ = structural_simplify(lorenz1, inputs = [z], outputs = []) +lorenz1_reduced = structural_simplify(lorenz1, inputs = [z], outputs = []) @test z in Set(parameters(lorenz1_reduced)) # #2064 From e31b6a70595e0b1318afd4f27a13ea82281bfd8a Mon Sep 17 00:00:00 2001 From: vyudu Date: Fri, 25 Apr 2025 11:20:18 -0400 Subject: [PATCH 025/122] more test fixes --- src/inputoutput.jl | 2 +- test/code_generation.jl | 2 +- test/extensions/test_infiniteopt.jl | 9 +++++---- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/inputoutput.jl b/src/inputoutput.jl index 3dd96a03cf..6110e35951 100644 --- a/src/inputoutput.jl +++ b/src/inputoutput.jl @@ -163,7 +163,7 @@ has_var(ex, x) = x ∈ Set(get_variables(ex)) (f_oop, f_ip), x_sym, p_sym, io_sys = generate_control_function( sys::AbstractODESystem, inputs = unbound_inputs(sys), - disturbance_inputs = Any[]; + disturbance_inputs = disturbances(sys); implicit_dae = false, simplify = false, ) diff --git a/test/code_generation.jl b/test/code_generation.jl index 3ef5ac3e11..2fbf8f13d7 100644 --- a/test/code_generation.jl +++ b/test/code_generation.jl @@ -70,7 +70,7 @@ end @parameters p[1:2] (f::Function)(..) @named sys = ODESystem( [D(x[0]) ~ p[1] * x[0] + x[2], D(x[1]) ~ p[2] * f(x) + x[2]], t) - sys, = structural_simplify(sys, inputs = [x[2]], outputs = []) + sys = structural_simplify(sys, inputs = [x[2]], outputs = []) @test is_parameter(sys, x[2]) prob = ODEProblem(sys, [x[0] => 1.0, x[1] => 1.0], (0.0, 1.0), [p => ones(2), f => sum, x[2] => 2.0]) diff --git a/test/extensions/test_infiniteopt.jl b/test/extensions/test_infiniteopt.jl index e45aa0f2fd..f64f9c18f5 100644 --- a/test/extensions/test_infiniteopt.jl +++ b/test/extensions/test_infiniteopt.jl @@ -22,13 +22,14 @@ using ModelingToolkit: D_nounits as D, t_nounits as t, varmap_to_vars end @named model = Pendulum() model = complete(model) - inputs = [model.τ] +outputs = [model.y] + +model = structural_simplify(model; inputs, outputs) (f_oop, f_ip), dvs, psym, io_sys = ModelingToolkit.generate_control_function( - model, inputs, split = false) + model, split = false) -outputs = [model.y] -f_obs = ModelingToolkit.build_explicit_observed_function(io_sys, outputs; inputs = inputs) +f_obs = ModelingToolkit.build_explicit_observed_function(io_sys, [model.y]; inputs) expected_state_order = [model.θ, model.ω] permutation = [findfirst(isequal(x), expected_state_order) for x in dvs] # This maps our expected state order to the actual state order From f1b7ceba7a7d58f42c337161f20ab9cb88bcf788 Mon Sep 17 00:00:00 2001 From: vyudu Date: Mon, 28 Apr 2025 16:12:50 -0400 Subject: [PATCH 026/122] fix: fix sort_eqs and check distrubances in markio --- src/inputoutput.jl | 15 ++------------- src/linearization.jl | 28 ++++++++++++++++++---------- src/systems/systems.jl | 1 + src/systems/systemstructure.jl | 2 +- 4 files changed, 22 insertions(+), 24 deletions(-) diff --git a/src/inputoutput.jl b/src/inputoutput.jl index 6110e35951..0163c6d4df 100644 --- a/src/inputoutput.jl +++ b/src/inputoutput.jl @@ -259,7 +259,7 @@ end """ Turn input variables into parameters of the system. """ -function inputs_to_parameters!(state::TransformationState, inputsyms; is_disturbance = false) +function inputs_to_parameters!(state::TransformationState, inputsyms) check_bound = inputsyms === nothing @unpack structure, fullvars, sys = state @unpack var_to_diff, graph, solvable_graph = structure @@ -318,18 +318,7 @@ function inputs_to_parameters!(state::TransformationState, inputsyms; is_disturb @set! sys.unknowns = setdiff(unknowns(sys), keys(input_to_parameters)) ps = parameters(sys) - if inputsyms !== nothing - # Change order of new parameters to correspond to user-provided order in argument `inputs` - d = Dict{Any, Int}() - for (i, inp) in enumerate(new_parameters) - d[inp] = i - end - permutation = [d[i] for i in inputsyms] - new_parameters = new_parameters[permutation] - end - @set! sys.ps = [ps; new_parameters] - @set! state.sys = sys @set! state.fullvars = new_fullvars @set! state.structure = structure @@ -432,6 +421,6 @@ function add_input_disturbance(sys, dist::DisturbanceModel, inputs = Any[]; kwar ssys = structural_simplify(augmented_sys, inputs = all_inputs, disturbance_inputs = [d]) (f_oop, f_ip), dvs, p, io_sys = generate_control_function(ssys, all_inputs, - [d]; check_simplified = false, kwargs...) + [d]; kwargs...) (f_oop, f_ip), augmented_sys, dvs, p, io_sys end diff --git a/src/linearization.jl b/src/linearization.jl index efbb3c3ab4..12340435b8 100644 --- a/src/linearization.jl +++ b/src/linearization.jl @@ -132,14 +132,13 @@ function linearization_function(sys::AbstractSystem, inputs, return lin_fun, sys end +""" +Return the set of indexes of differential equations and algebraic equations in the simplified system. +""" function eq_idxs(sys::AbstractSystem) eqs = equations(sys) - alg_start_idx = findfirst(!isdiffeq, eqs) - if alg_start_idx === nothing - alg_start_idx = length(eqs) + 1 - end - diff_idxs = 1:(alg_start_idx - 1) - alge_idxs = alg_start_idx:length(eqs) + alge_idxs = findall(!isdiffeq, eqs) + diff_idxs = setdiff(1:length(eqs), alge_idxs) diff_idxs, alge_idxs end @@ -556,6 +555,9 @@ function linearize_symbolic(sys::AbstractSystem, inputs, (; A, B, C, D, f_x, f_z, g_x, g_z, f_u, g_u, h_x, h_z, h_u), sys end +""" +Modify the variable metadata of system variables to indicate which ones are inputs, outputs, and disturbances. Needed for `inputs`, `outputs`, `disturbances`, `unbound_inputs`, `unbound_outputs` to return the proper subsets. +""" function markio!(state, orig_inputs, inputs, outputs, disturbances; check = true) fullvars = get_fullvars(state) inputset = Dict{Any, Bool}(i => false for i in inputs) @@ -586,6 +588,7 @@ function markio!(state, orig_inputs, inputs, outputs, disturbances; check = true if v in keys(disturbanceset) v = setio(v, true, false) v = setdisturbance(v, true) + disturbanceset[v] = true fullvars[i] = v end end @@ -596,11 +599,16 @@ function markio!(state, orig_inputs, inputs, outputs, disturbances; check = true "Some specified inputs were not found in system. The following variables were not found ", ikeys) end - end - check && (all(values(outputset)) || - error( - "Some specified outputs were not found in system. The following Dict indicates the found variables ", + dkeys = keys(filter(!last, disturbanceset)) + if !isempty(dkeys) + error( + "Specified disturbance inputs were not found in system. The following variables were not found ", + ikeys) + end + (all(values(outputset)) || error( + "Some specified outputs were not found in system. The following Dict indicates the found variables ", outputset)) + end state, orig_inputs end diff --git a/src/systems/systems.jl b/src/systems/systems.jl index acd2b4efc9..59d2af6526 100644 --- a/src/systems/systems.jl +++ b/src/systems/systems.jl @@ -74,6 +74,7 @@ end function __structural_simplify(sys::AbstractSystem; simplify = false, inputs = Any[], outputs = Any[], disturbance_inputs = Any[], + sort_eqs = true, kwargs...) sys = expand_connections(sys) state = TearingState(sys; sort_eqs) diff --git a/src/systems/systemstructure.jl b/src/systems/systemstructure.jl index 2bae98cbdd..e0502d8e2f 100644 --- a/src/systems/systemstructure.jl +++ b/src/systems/systemstructure.jl @@ -725,7 +725,7 @@ function _structural_simplify!(state::TearingState; simplify = false, else check_consistency = true end - has_io = inputs !== nothing || outputs !== nothing + has_io = !isempty(inputs) || !isempty(outputs) !== nothing || !isempty(disturbance_inputs) orig_inputs = Set() if has_io ModelingToolkit.markio!(state, orig_inputs, inputs, outputs, disturbance_inputs) From 47b7dd8e684628340bcf5aecd06a45b1408c7fd3 Mon Sep 17 00:00:00 2001 From: vyudu Date: Fri, 2 May 2025 12:49:43 -0400 Subject: [PATCH 027/122] format --- src/inputoutput.jl | 2 +- src/linearization.jl | 2 +- src/systems/systems.jl | 6 ++++-- src/systems/systemstructure.jl | 6 +++--- test/clock.jl | 6 ++++-- test/input_output_handling.jl | 3 ++- 6 files changed, 15 insertions(+), 10 deletions(-) diff --git a/src/inputoutput.jl b/src/inputoutput.jl index 0163c6d4df..cc6eeab5e3 100644 --- a/src/inputoutput.jl +++ b/src/inputoutput.jl @@ -200,7 +200,7 @@ function generate_control_function(sys::AbstractODESystem, inputs = unbound_inpu check_simplified = true, kwargs...) # Remove this when the ControlFunction gets merged. - if check_simplified && !iscomplete(sys) + if check_simplified && !iscomplete(sys) error("A completed `ODESystem` is required. Call `complete` or `structural_simplify` on the system before creating the control function.") end isempty(inputs) && @warn("No unbound inputs were found in system.") diff --git a/src/linearization.jl b/src/linearization.jl index 12340435b8..0c18cbcb5c 100644 --- a/src/linearization.jl +++ b/src/linearization.jl @@ -607,7 +607,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/systems.jl b/src/systems/systems.jl index 59d2af6526..e417337a95 100644 --- a/src/systems/systems.jl +++ b/src/systems/systems.jl @@ -93,7 +93,8 @@ function __structural_simplify(sys::AbstractSystem; simplify = false, end end if isempty(brown_vars) - return structural_simplify!(state; simplify, inputs, outputs, disturbance_inputs, kwargs...) + return structural_simplify!( + state; simplify, inputs, outputs, disturbance_inputs, kwargs...) else Is = Int[] Js = Int[] @@ -125,7 +126,8 @@ function __structural_simplify(sys::AbstractSystem; simplify = false, for (i, v) in enumerate(fullvars) if !iszero(new_idxs[i]) && invview(var_to_diff)[i] === nothing] - ode_sys = structural_simplify(sys; simplify, inputs, outputs, disturbance_inputs, kwargs...) + ode_sys = structural_simplify( + sys; simplify, inputs, outputs, disturbance_inputs, kwargs...) eqs = equations(ode_sys) sorted_g_rows = zeros(Num, length(eqs), size(g, 2)) for (i, eq) in enumerate(eqs) diff --git a/src/systems/systemstructure.jl b/src/systems/systemstructure.jl index e0502d8e2f..08c95fbbb2 100644 --- a/src/systems/systemstructure.jl +++ b/src/systems/systemstructure.jl @@ -659,10 +659,9 @@ end function structural_simplify!(state::TearingState; simplify = false, check_consistency = true, fully_determined = true, warn_initialize_determined = true, - inputs = Any[], outputs = Any[], + inputs = Any[], outputs = Any[], disturbance_inputs = Any[], kwargs...) - if state.sys isa ODESystem ci = ModelingToolkit.ClockInference(state) ci = ModelingToolkit.infer_clocks!(ci) @@ -725,7 +724,8 @@ function _structural_simplify!(state::TearingState; simplify = false, else check_consistency = true end - has_io = !isempty(inputs) || !isempty(outputs) !== nothing || !isempty(disturbance_inputs) + has_io = !isempty(inputs) || !isempty(outputs) !== nothing || + !isempty(disturbance_inputs) orig_inputs = Set() if has_io ModelingToolkit.markio!(state, orig_inputs, inputs, outputs, disturbance_inputs) diff --git a/test/clock.jl b/test/clock.jl index 4fb9341d87..c4c64dbf90 100644 --- a/test/clock.jl +++ b/test/clock.jl @@ -65,9 +65,11 @@ By inference: ci, varmap = infer_clocks(sys) eqmap = ci.eq_domain tss, inputs, continuous_id = ModelingToolkit.split_system(deepcopy(ci)) -sss = ModelingToolkit._structural_simplify!(deepcopy(tss[continuous_id]), inputs = inputs[continuous_id], outputs = []) +sss = ModelingToolkit._structural_simplify!( + deepcopy(tss[continuous_id]), inputs = inputs[continuous_id], outputs = []) @test equations(sss) == [D(x) ~ u - x] -sss = ModelingToolkit._structural_simplify!(deepcopy(tss[1]), inputs = inputs[1], outputs = []) +sss = ModelingToolkit._structural_simplify!( + deepcopy(tss[1]), inputs = inputs[1], outputs = []) @test isempty(equations(sss)) d = Clock(dt) k = ShiftIndex(d) diff --git a/test/input_output_handling.jl b/test/input_output_handling.jl index 1a1c9a6b65..3efeb46868 100644 --- a/test/input_output_handling.jl +++ b/test/input_output_handling.jl @@ -202,7 +202,8 @@ end @named sys = ODESystem(eqs, t) sys = structural_simplify(sys, inputs = [u], disturbance_inputs = [d]) - f, dvs, ps, io_sys = ModelingToolkit.generate_control_function(sys; simplify, split, disturbance_argument = true) + f, dvs, ps, io_sys = ModelingToolkit.generate_control_function( + sys; simplify, split, disturbance_argument = true) @test isequal(dvs[], x) @test isempty(ps) From 0cf319ccf1da0941a6074ac44de2b501ab0a55ed Mon Sep 17 00:00:00 2001 From: vyudu Date: Sat, 1 Mar 2025 17:35:49 -0500 Subject: [PATCH 028/122] init --- src/systems/callbacks.jl | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/src/systems/callbacks.jl b/src/systems/callbacks.jl index 7d542d9bd0..d94106cc9d 100644 --- a/src/systems/callbacks.jl +++ b/src/systems/callbacks.jl @@ -1,4 +1,4 @@ -#################################### system operations ##################################### +#################################### System operations ##################################### has_continuous_events(sys::AbstractSystem) = isdefined(sys, :continuous_events) function get_continuous_events(sys::AbstractSystem) has_continuous_events(sys) || return SymbolicContinuousCallback[] @@ -11,6 +11,35 @@ function get_discrete_events(sys::AbstractSystem) getfield(sys, :discrete_events) end +struct Callback + eqs::Vector{Equation} + initialize::Union{ImplicitDiscreteSystem, FunctionalAffect, ImperativeAffect} + finalize::ImplicitDiscreteSystem + affect::ImplicitDiscreteSystem + affect_neg::ImplicitDiscreteSystem + rootfind::Union{Nothing, SciMLBase.RootfindOpt} +end + +# Callbacks: +# mapping (cond) => ImplicitDiscreteSystem +function generate_continuous_callbacks(events, sys) + algeeqs = alg_equations(sys) + callbacks = Callback[] + for (cond, aff) in events + @mtkbuild affect = ImplicitDiscreteSystem([aff, algeeqs], t) + push!(callbacks, Callback(cond, NULL_AFFECT, NULL_AFFECT, affect, affect, SciMLBase.LeftRootFind)) + end + callbacks +end + +function generate_discrete_callback_system(events, sys) +end + +function generate_callback_function() + +end + +############# Old implementation ### struct FunctionalAffect f::Any sts::Vector From 37f050e5823b1b3a869623a14d6fea18c195721c Mon Sep 17 00:00:00 2001 From: vyudu Date: Mon, 3 Mar 2025 17:07:52 -0500 Subject: [PATCH 029/122] refactor: refactor affect codegen --- src/ModelingToolkit.jl | 10 +-- src/systems/callbacks.jl | 164 +++++++++++++++++++++++++++++++++------ 2 files changed, 145 insertions(+), 29 deletions(-) diff --git a/src/ModelingToolkit.jl b/src/ModelingToolkit.jl index 198a84d48b..d58a7a7179 100644 --- a/src/ModelingToolkit.jl +++ b/src/ModelingToolkit.jl @@ -158,7 +158,6 @@ include("systems/model_parsing.jl") include("systems/connectors.jl") include("systems/analysis_points.jl") include("systems/imperative_affect.jl") -include("systems/callbacks.jl") include("systems/codegen_utils.jl") include("systems/problem_utils.jl") include("linearization.jl") @@ -168,19 +167,20 @@ include("systems/optimization/optimizationsystem.jl") include("systems/optimization/modelingtoolkitize.jl") include("systems/nonlinear/nonlinearsystem.jl") -include("systems/nonlinear/homotopy_continuation.jl") +include("systems/discrete_system/discrete_system.jl") +include("systems/discrete_system/implicit_discrete_system.jl") +include("systems/callbacks.jl") + include("systems/diffeqs/odesystem.jl") include("systems/diffeqs/sdesystem.jl") include("systems/diffeqs/abstractodesystem.jl") +include("systems/nonlinear/homotopy_continuation.jl") include("systems/nonlinear/modelingtoolkitize.jl") include("systems/nonlinear/initializesystem.jl") include("systems/diffeqs/first_order_transform.jl") include("systems/diffeqs/modelingtoolkitize.jl") include("systems/diffeqs/basic_transformations.jl") -include("systems/discrete_system/discrete_system.jl") -include("systems/discrete_system/implicit_discrete_system.jl") - include("systems/jumps/jumpsystem.jl") include("systems/pde/pdesystem.jl") diff --git a/src/systems/callbacks.jl b/src/systems/callbacks.jl index d94106cc9d..3c80582be3 100644 --- a/src/systems/callbacks.jl +++ b/src/systems/callbacks.jl @@ -11,35 +11,139 @@ function get_discrete_events(sys::AbstractSystem) getfield(sys, :discrete_events) end -struct Callback - eqs::Vector{Equation} - initialize::Union{ImplicitDiscreteSystem, FunctionalAffect, ImperativeAffect} - finalize::ImplicitDiscreteSystem - affect::ImplicitDiscreteSystem - affect_neg::ImplicitDiscreteSystem - rootfind::Union{Nothing, SciMLBase.RootfindOpt} -end +abstract type Callback end + +const Affect = Union{ImplicitDiscreteSystem, FunctionalAffect, ImperativeAffect} # Callbacks: # mapping (cond) => ImplicitDiscreteSystem function generate_continuous_callbacks(events, sys) algeeqs = alg_equations(sys) - callbacks = Callback[] - for (cond, aff) in events - @mtkbuild affect = ImplicitDiscreteSystem([aff, algeeqs], t) - push!(callbacks, Callback(cond, NULL_AFFECT, NULL_AFFECT, affect, affect, SciMLBase.LeftRootFind)) + callbacks = MTKContinuousCallback[] + for (cond, affs) in events + @mtkbuild affect = ImplicitDiscreteSystem([affs, algeeqs], t) + push!(callbacks, MTKContinuousCallback(cond, NULL_AFFECT, NULL_AFFECT, affect, affect, SciMLBase.LeftRootFind)) end callbacks end -function generate_discrete_callback_system(events, sys) +function generate_discrete_callbacks(events, sys) + algeeqs = alg_equations(sys) + callbacks = MTKDiscreteCallback[] + for (cond, affs) in events + @mtkbuild affect = ImplicitDiscreteSystem([affs, algeeqs], t) + push!(callbacks, MTKDiscreteCallback(cond, NULL_AFFECT, NULL_AFFECT, affect)) + end + callbacks end -function generate_callback_function() - +""" +Create a DifferentialEquations callback. A set of continuous callbacks becomes a VectorContinuousCallback. +""" +function create_callback(cbs::Vector{MTKContinuousCallback}, sys; is_discrete = false) + eqs = flatten_equations(cbs) + _, f_iip = generate_custom_function( + sys, [eq.lhs - eq.rhs for eq in eqs], unknowns(sys), parameters(sys); + expression = Val{false}) + trigger = (out, u, t, integ) -> f_iip(out, u, parameter_values(integ), t) + + affects = [] + affect_negs = [] + inits = [] + finals = [] + for cb in cbs + affect = compile_affect(cb.affect) + push!(affects, affect) + isnothing(cb.affect_neg) ? push!(affect_negs, affect) : push!(affect_negs, compile_affect(cb.affect_neg)) + push!(inits, compile_affect(cb.initialize, default = SciMLBase.INITALIZE_DEFAULT)) + push!(finals, compile_affect(cb.finalize, default = SciMLBase.FINALIZE_DEFAULT)) + end + + # since there may be different number of conditions and affects, + # we build a map that translates the condition eq. number to the affect number + num_eqs = length.(eqs) + eq2affect = reduce(vcat, + [fill(i, num_eqs[i]) for i in eachindex(affects)]) + @assert length(eq2affect) == length(eqs) + @assert maximum(eq2affect) == length(affect_functions) + + affect = function (integ, idx) + affects[eq2affect[idx]](integ) + end + affect_neg = function (integ, idx) + f = affect_negs[eq2affect[idx]] + isnothing(f) && return + f(integ) + end + initialize = compile_optional_setup(inits, SciMLBase.INITIALIZE_DEFAULT) + finalize = compile_optional_setup(finals, SciMLBase.FINALIZE_DEFAULT) + + return VectorContinuousCallback(trigger, affect; affect_neg, initialize, finalize, rootfind = callback.rootfind, initializealg = SciMLBase.NoInit) +end + +function create_callback(cb, sys; is_discrete = false) + is_timed = is_timed_condition(cb) + + trigger = if is_discrete + is_timed ? condition(cb) : + compile_condition(callback, sys, unknowns(sys), parameters(sys)) + else + _, f_iip = generate_custom_function( + sys, [eq.rhs - eq.lhs for eq in equations(cb)], unknowns(sys), parameters(sys); + expression = Val{false}) + (out, u, t, integ) -> f_iip(out, u, parameter_values(integ), t) + end + + affect = compile_affect(cb.affect) + affect_neg = isnothing(cb.affect_neg) ? affect_fn : compile_affect(cb.affect_neg) + initialize = compile_affect(cb.initialize, default = SciMLBase.INITIALIZE_DEFAULT) + finalize = compile_affect(cb.finalize, default = SciMLBase.FINALIZE_DEFAULT) + + if is_discrete + if is_timed && condition(cb) isa AbstractVector + return PresetTimeCallback(trigger, affect; affect_neg, initialize, finalize, initializealg = SciMLBase.NoInit) + elseif is_timed + return PeriodicCallback(affect, trigger; initialize, finalize) + else + return DiscreteCallback(trigger, affect; affect_neg, initialize, finalize, initializealg = SciMLBase.NoInit) + end + else + return ContinuousCallback(trigger, affect; affect_neg, initialize, finalize, rootfind = callback.rootfind, initializealg = SciMLBase.NoInit) + end +end + +function compile_affect(aff; default = nothing) + if aff isa ImplicitDiscreteSystem + function affect!(integrator) + u0map = [u => integrator[u] for u in unknowns(aff)] + pmap = [p => integrator[p] for p in parameters(aff)] + prob = ImplicitDiscreteProblem(aff, u0map, (0, 1), pmap) + sol = solve(prob) + for u in unknowns(aff) + integrator[u] = sol[u][end] + end + for p in parameters(aff) + integrator[p] = sol[p][end] + end + end + elseif aff isa FunctionalAffect || aff isa ImperativeAffect + compile_user_affect(aff, callback, sys, unknowns(sys), parameters(sys)) + else + default + end +end + +function compile_setup_funcs(funs, default) + all(isnothing, funs) && return default + return let funs = funs + function (cb, u, t, integ) + for func in funs + isnothing(func) ? continue : func(integ) + end + end + end end -############# Old implementation ### struct FunctionalAffect f::Any sts::Vector @@ -50,6 +154,22 @@ struct FunctionalAffect ctx::Any end +struct MTKContinuousCallback <: Callback + eqs::Vector{Equation} + initialize::Union{Affect, Nothing} + finalize::Union{Affect, Nothing} + affect::Affect + affect_neg::Union{Affect, Nothing} + rootfind::Union{Nothing, SciMLBase.RootfindOpt} +end + +struct MTKDiscreteCallback <: Callback + conds::Vector{Equation} + initialize::Union{Affect, Nothing} + finalize::Union{Affect, Nothing} + affect::Affect +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] @@ -67,7 +187,7 @@ function FunctionalAffect(; f, sts, pars, discretes, ctx = nothing) FunctionalAffect(f, sts, pars, discretes, ctx) end -func(f::FunctionalAffect) = f.f +func(a::FunctionalAffect) = a.f context(a::FunctionalAffect) = a.ctx parameters(a::FunctionalAffect) = a.pars parameters_syms(a::FunctionalAffect) = a.pars_syms @@ -899,13 +1019,7 @@ function compile_affect_fn(cb, sys::AbstractTimeDependentSystem, dvs, ps, kwargs eq_aff = affects(cb) eq_neg_aff = affect_negs(cb) affect = compile_affect(eq_aff, cb, sys, dvs, ps; expression = Val{false}, kwargs...) - function compile_optional_affect(aff, default = nothing) - if isnothing(aff) || aff == default - return nothing - else - return compile_affect(aff, cb, sys, dvs, ps; expression = Val{false}, kwargs...) - end - end + if eq_neg_aff === eq_aff affect_neg = affect else @@ -1047,6 +1161,7 @@ end function compile_affect(affect::FunctionalAffect, cb, sys, dvs, ps; kwargs...) compile_user_affect(affect, cb, sys, dvs, ps; kwargs...) end + function _compile_optional_affect(default, aff, cb, sys, dvs, ps; kwargs...) if isnothing(aff) || aff == default return nothing @@ -1054,6 +1169,7 @@ function _compile_optional_affect(default, aff, cb, sys, dvs, ps; kwargs...) return compile_affect(aff, cb, sys, dvs, ps; expression = Val{false}, kwargs...) end end + function generate_timed_callback(cb, sys, dvs, ps; postprocess_affect_expr! = nothing, kwargs...) cond = condition(cb) From 9795eebfb00254e546526de66edd576bce12484a Mon Sep 17 00:00:00 2001 From: vyudu Date: Wed, 5 Mar 2025 15:22:28 -0500 Subject: [PATCH 030/122] feat: correct affect system generation --- src/systems/callbacks.jl | 1369 +++++++++--------------------- src/systems/diffeqs/odesystem.jl | 6 +- src/systems/imperative_affect.jl | 45 +- src/systems/systems.jl | 8 +- 4 files changed, 448 insertions(+), 980 deletions(-) diff --git a/src/systems/callbacks.jl b/src/systems/callbacks.jl index 3c80582be3..cb0daed803 100644 --- a/src/systems/callbacks.jl +++ b/src/systems/callbacks.jl @@ -1,148 +1,4 @@ -#################################### System operations ##################################### -has_continuous_events(sys::AbstractSystem) = isdefined(sys, :continuous_events) -function get_continuous_events(sys::AbstractSystem) - has_continuous_events(sys) || return SymbolicContinuousCallback[] - getfield(sys, :continuous_events) -end - -has_discrete_events(sys::AbstractSystem) = isdefined(sys, :discrete_events) -function get_discrete_events(sys::AbstractSystem) - has_discrete_events(sys) || return SymbolicDiscreteCallback[] - getfield(sys, :discrete_events) -end - -abstract type Callback end - -const Affect = Union{ImplicitDiscreteSystem, FunctionalAffect, ImperativeAffect} - -# Callbacks: -# mapping (cond) => ImplicitDiscreteSystem -function generate_continuous_callbacks(events, sys) - algeeqs = alg_equations(sys) - callbacks = MTKContinuousCallback[] - for (cond, affs) in events - @mtkbuild affect = ImplicitDiscreteSystem([affs, algeeqs], t) - push!(callbacks, MTKContinuousCallback(cond, NULL_AFFECT, NULL_AFFECT, affect, affect, SciMLBase.LeftRootFind)) - end - callbacks -end - -function generate_discrete_callbacks(events, sys) - algeeqs = alg_equations(sys) - callbacks = MTKDiscreteCallback[] - for (cond, affs) in events - @mtkbuild affect = ImplicitDiscreteSystem([affs, algeeqs], t) - push!(callbacks, MTKDiscreteCallback(cond, NULL_AFFECT, NULL_AFFECT, affect)) - end - callbacks -end - -""" -Create a DifferentialEquations callback. A set of continuous callbacks becomes a VectorContinuousCallback. -""" -function create_callback(cbs::Vector{MTKContinuousCallback}, sys; is_discrete = false) - eqs = flatten_equations(cbs) - _, f_iip = generate_custom_function( - sys, [eq.lhs - eq.rhs for eq in eqs], unknowns(sys), parameters(sys); - expression = Val{false}) - trigger = (out, u, t, integ) -> f_iip(out, u, parameter_values(integ), t) - - affects = [] - affect_negs = [] - inits = [] - finals = [] - for cb in cbs - affect = compile_affect(cb.affect) - push!(affects, affect) - isnothing(cb.affect_neg) ? push!(affect_negs, affect) : push!(affect_negs, compile_affect(cb.affect_neg)) - push!(inits, compile_affect(cb.initialize, default = SciMLBase.INITALIZE_DEFAULT)) - push!(finals, compile_affect(cb.finalize, default = SciMLBase.FINALIZE_DEFAULT)) - end - - # since there may be different number of conditions and affects, - # we build a map that translates the condition eq. number to the affect number - num_eqs = length.(eqs) - eq2affect = reduce(vcat, - [fill(i, num_eqs[i]) for i in eachindex(affects)]) - @assert length(eq2affect) == length(eqs) - @assert maximum(eq2affect) == length(affect_functions) - - affect = function (integ, idx) - affects[eq2affect[idx]](integ) - end - affect_neg = function (integ, idx) - f = affect_negs[eq2affect[idx]] - isnothing(f) && return - f(integ) - end - initialize = compile_optional_setup(inits, SciMLBase.INITIALIZE_DEFAULT) - finalize = compile_optional_setup(finals, SciMLBase.FINALIZE_DEFAULT) - - return VectorContinuousCallback(trigger, affect; affect_neg, initialize, finalize, rootfind = callback.rootfind, initializealg = SciMLBase.NoInit) -end - -function create_callback(cb, sys; is_discrete = false) - is_timed = is_timed_condition(cb) - - trigger = if is_discrete - is_timed ? condition(cb) : - compile_condition(callback, sys, unknowns(sys), parameters(sys)) - else - _, f_iip = generate_custom_function( - sys, [eq.rhs - eq.lhs for eq in equations(cb)], unknowns(sys), parameters(sys); - expression = Val{false}) - (out, u, t, integ) -> f_iip(out, u, parameter_values(integ), t) - end - - affect = compile_affect(cb.affect) - affect_neg = isnothing(cb.affect_neg) ? affect_fn : compile_affect(cb.affect_neg) - initialize = compile_affect(cb.initialize, default = SciMLBase.INITIALIZE_DEFAULT) - finalize = compile_affect(cb.finalize, default = SciMLBase.FINALIZE_DEFAULT) - - if is_discrete - if is_timed && condition(cb) isa AbstractVector - return PresetTimeCallback(trigger, affect; affect_neg, initialize, finalize, initializealg = SciMLBase.NoInit) - elseif is_timed - return PeriodicCallback(affect, trigger; initialize, finalize) - else - return DiscreteCallback(trigger, affect; affect_neg, initialize, finalize, initializealg = SciMLBase.NoInit) - end - else - return ContinuousCallback(trigger, affect; affect_neg, initialize, finalize, rootfind = callback.rootfind, initializealg = SciMLBase.NoInit) - end -end - -function compile_affect(aff; default = nothing) - if aff isa ImplicitDiscreteSystem - function affect!(integrator) - u0map = [u => integrator[u] for u in unknowns(aff)] - pmap = [p => integrator[p] for p in parameters(aff)] - prob = ImplicitDiscreteProblem(aff, u0map, (0, 1), pmap) - sol = solve(prob) - for u in unknowns(aff) - integrator[u] = sol[u][end] - end - for p in parameters(aff) - integrator[p] = sol[p][end] - end - end - elseif aff isa FunctionalAffect || aff isa ImperativeAffect - compile_user_affect(aff, callback, sys, unknowns(sys), parameters(sys)) - else - default - end -end - -function compile_setup_funcs(funs, default) - all(isnothing, funs) && return default - return let funs = funs - function (cb, u, t, integ) - for func in funs - isnothing(func) ? continue : func(integ) - end - end - end -end +abstract type AbstractCallback end struct FunctionalAffect f::Any @@ -154,22 +10,6 @@ struct FunctionalAffect ctx::Any end -struct MTKContinuousCallback <: Callback - eqs::Vector{Equation} - initialize::Union{Affect, Nothing} - finalize::Union{Affect, Nothing} - affect::Affect - affect_neg::Union{Affect, Nothing} - rootfind::Union{Nothing, SciMLBase.RootfindOpt} -end - -struct MTKDiscreteCallback <: Callback - conds::Vector{Equation} - initialize::Union{Affect, Nothing} - finalize::Union{Affect, Nothing} - affect::Affect -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] @@ -211,31 +51,16 @@ function Base.hash(a::FunctionalAffect, s::UInt) hash(a.ctx, s) end -namespace_affect(affect, s) = namespace_equation(affect, s) -function namespace_affect(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 has_functional_affect(cb) (affects(cb) isa FunctionalAffect || affects(cb) isa ImperativeAffect) end -function vars!(vars, aff::FunctionalAffect; op = Differential) - for var in Iterators.flatten((unknowns(aff), parameters(aff), discretes(aff))) - vars!(vars, var) - end - return vars -end -#################################### continuous events ##################################### +############################### +###### Continuous events ###### +############################### +const Affect = Union{ImplicitDiscreteSystem, FunctionalAffect, ImperativeAffect} -const NULL_AFFECT = Equation[] """ SymbolicContinuousCallback(eqs::Vector{Equation}, affect, affect_neg, rootfind) @@ -277,54 +102,73 @@ Affects (i.e. `affect` and `affect_neg`) can be specified as either: + `ctx` is a user-defined context object passed to `f!` when invoked. This value is aliased for each problem. * A [`ImperativeAffect`](@ref); refer to its documentation for details. -DAEs will be reinitialized using `reinitializealg` (which defaults to `SciMLBase.CheckInit`) after callbacks are applied. -This reinitialization algorithm ensures that the DAE is satisfied after the callback runs. The default value of `CheckInit` will simply validate -that the newly-assigned values indeed satisfy the algebraic system; see the documentation on DAE initialization for a more detailed discussion of -initialization. +DAEs will automatically be reinitialized. Initial and final affects can also be specified with SCC, which are specified identically to positive and negative edge affects. Initialization affects will run as soon as the solver starts, while finalization affects will be executed after termination. """ -struct SymbolicContinuousCallback - eqs::Vector{Equation} - initialize::Union{Vector{Equation}, FunctionalAffect, ImperativeAffect} - finalize::Union{Vector{Equation}, FunctionalAffect, ImperativeAffect} - affect::Union{Vector{Equation}, FunctionalAffect, ImperativeAffect} - affect_neg::Union{Vector{Equation}, FunctionalAffect, ImperativeAffect, Nothing} - rootfind::SciMLBase.RootfindOpt - reinitializealg::SciMLBase.DAEInitializationAlgorithm - function SymbolicContinuousCallback(; - eqs::Vector{Equation}, - affect = NULL_AFFECT, +struct SymbolicContinuousCallback <: AbstractCallback + conditions::Vector{Equation} + affect::Union{Affect, Nothing} + affect_neg::Union{Affect, Nothing} + initialize::Union{Affect, Nothing} + finalize::Union{Affect, Nothing} + rootfind::Union{Nothing, SciMLBase.RootfindOpt} + + function SymbolicContinuousCallback( + conditions::Vector{Equation}, + affect = nothing; affect_neg = affect, - initialize = NULL_AFFECT, - finalize = NULL_AFFECT, - rootfind = SciMLBase.LeftRootFind, - reinitializealg = SciMLBase.CheckInit()) + initialize = nothing, + finalize = nothing, + rootfind = SciMLBase.LeftRootFind) new(eqs, initialize, finalize, make_affect(affect), - make_affect(affect_neg), rootfind, reinitializealg) + make_affect(affect_neg), rootfind) end # Default affect to nothing end -make_affect(affect) = affect -make_affect(affect::Tuple) = FunctionalAffect(affect...) -make_affect(affect::NamedTuple) = FunctionalAffect(; affect...) -function Base.:(==)(e1::SymbolicContinuousCallback, e2::SymbolicContinuousCallback) - isequal(e1.eqs, e2.eqs) && isequal(e1.affect, e2.affect) && - isequal(e1.initialize, e2.initialize) && isequal(e1.finalize, e2.finalize) && - isequal(e1.affect_neg, e2.affect_neg) && isequal(e1.rootfind, e2.rootfind) +make_affect(affect::Tuple, iv) = FunctionalAffect(affects...) +make_affect(affect::NamedTuple, iv) = FunctionalAffect(; affects...) +make_affect(affect::FunctionalAffect, iv) = affect + +# Default behavior: if no shifts are provided, then it is assumed that the RHS is the previous. +function make_affect(affect::Vector{Equation}, iv) + affect = scalarize(affect) + unknowns = OrderedSet() + params = OrderedSet() + for eq in affect + collect_vars!(unknowns, params, eq, iv) + end + affect = map(affect) do eq + ModelingToolkit.hasshift(eq) ? eq : + eq.lhs ~ distribute_shift(Prev(eq.rhs)) + end + params = map(params) do p + p = value(p) + Sym{FnType{Tuple{symtype(iv)}, Real}}(nameof(p))(iv) + end + + @mtkbuild affect = ImplicitDiscreteSystem(affect, iv, vcat(unknowns, params), []) end -Base.isempty(cb::SymbolicContinuousCallback) = isempty(cb.eqs) -function Base.hash(cb::SymbolicContinuousCallback, s::UInt) - hash_affect(affect::AbstractVector, s) = foldr(hash, affect, init = s) - hash_affect(affect, s) = hash(affect, s) - s = foldr(hash, cb.eqs, init = s) - s = hash_affect(cb.affect, s) - s = hash_affect(cb.affect_neg, s) - s = hash_affect(cb.initialize, s) - s = hash_affect(cb.finalize, s) - s = hash(cb.reinitializealg, s) - hash(cb.rootfind, s) + +make_affect(affect, iv) = error("Malformed affect $(affect). This should be a vector of equations or a tuple specifying a functional affect.") + +""" +Generate continuous callbacks. +""" +function SymbolicContinuousCallbacks(events, algeeqs, iv) + callbacks = MTKContinuousCallback[] + (isnothing(events) || isempty(events)) && return callbacks + + events isa AbstractVector || (events = [events]) + for (cond, affs) in events + if affs isa AbstractVector + affs = vcat(affs, algeeqs) + end + affect = make_affect(affs, iv) + push!(callbacks, SymbolicContinuousCallback(cond, affect, affect, nothing, nothing, SciMLBase.LeftRootFind)) + end + callbacks end function Base.show(io::IO, cb::SymbolicContinuousCallback) @@ -385,326 +229,192 @@ function Base.show(io::IO, mime::MIME"text/plain", cb::SymbolicContinuousCallbac end end -to_equation_vector(eq::Equation) = [eq] -to_equation_vector(eqs::Vector{Equation}) = eqs -function to_equation_vector(eqs::Vector{Any}) - isempty(eqs) || error("This should never happen") - Equation[] -end +################################ +######## Discrete events ####### +################################ -function SymbolicContinuousCallback(args...) - SymbolicContinuousCallback(to_equation_vector.(args)...) -end # wrap eq in vector -SymbolicContinuousCallback(p::Pair) = SymbolicContinuousCallback(p[1], p[2]) -SymbolicContinuousCallback(cb::SymbolicContinuousCallback) = cb # passthrough -function SymbolicContinuousCallback(eqs::Equation, affect = NULL_AFFECT; - initialize = NULL_AFFECT, finalize = NULL_AFFECT, - affect_neg = affect, rootfind = SciMLBase.LeftRootFind) - SymbolicContinuousCallback( - eqs = [eqs], affect = affect, affect_neg = affect_neg, - initialize = initialize, finalize = finalize, rootfind = rootfind) -end -function SymbolicContinuousCallback(eqs::Vector{Equation}, affect = NULL_AFFECT; - affect_neg = affect, initialize = NULL_AFFECT, finalize = NULL_AFFECT, - rootfind = SciMLBase.LeftRootFind) - SymbolicContinuousCallback( - eqs = eqs, affect = affect, affect_neg = affect_neg, - initialize = initialize, finalize = finalize, rootfind = rootfind) -end +# TODO: Iterative callbacks +""" + SymbolicDiscreteCallback(conditions::Vector{Equation}, affect) -SymbolicContinuousCallbacks(cb::SymbolicContinuousCallback) = [cb] -SymbolicContinuousCallbacks(cbs::Vector{<:SymbolicContinuousCallback}) = cbs -SymbolicContinuousCallbacks(cbs::Vector) = SymbolicContinuousCallback.(cbs) -function SymbolicContinuousCallbacks(ve::Vector{Equation}) - SymbolicContinuousCallbacks(SymbolicContinuousCallback(ve)) -end -function SymbolicContinuousCallbacks(others) - SymbolicContinuousCallbacks(SymbolicContinuousCallback(others)) -end -SymbolicContinuousCallbacks(::Nothing) = SymbolicContinuousCallback[] +A callback that triggers at the first timestep that the conditions are satisfied. -equations(cb::SymbolicContinuousCallback) = cb.eqs -function equations(cbs::Vector{<:SymbolicContinuousCallback}) - mapreduce(equations, vcat, cbs, init = Equation[]) -end +The condition can be one of: +- Real - periodic events with period Δt +- Vector{Real} - events trigger at these preset times +- Vector{Equation} - events trigger when the condition evaluates to true +""" +struct SymbolicDiscreteCallback{R} <: AbstractCallback where R <: Real + conditions::Union{R, Vector{R}, Vector{Equation}} + affect::Affect + initialize::Union{Affect, Nothing} + finalize::Union{Affect, Nothing} -affects(cb::SymbolicContinuousCallback) = cb.affect -function affects(cbs::Vector{SymbolicContinuousCallback}) - mapreduce(affects, vcat, cbs, init = Equation[]) + function SymbolicDiscreteCallback( + condition, affect = nothing; + initialize = nothing, finalize = nothing) + c = is_timed_condition(condition) ? condition : value(scalarize(condition)) + new(c, make_affect(affect), make_affect(initialize), + make_affect(finalize)) + end # Default affect to nothing end -affect_negs(cb::SymbolicContinuousCallback) = cb.affect_neg -function affect_negs(cbs::Vector{SymbolicContinuousCallback}) - mapreduce(affect_negs, vcat, cbs, init = Equation[]) +""" +Generate discrete callbacks. +""" +function SymbolicDiscreteCallbacks(events, algeeqs, iv) + callbacks = SymbolicDiscreteCallback[] + (isnothing(events) || isempty(events)) && return callbacks + events isa AbstractVector || (events = [events]) + + for (cond, aff) in events + if aff isa AbstractVector + aff = vcat(aff, algeeqs) + end + affect = make_affect(aff, iv) + push!(callbacks, SymbolicDiscreteCallback(cond, affect, nothing, nothing)) + end + callbacks end -reinitialization_alg(cb::SymbolicContinuousCallback) = cb.reinitializealg -function reinitialization_algs(cbs::Vector{SymbolicContinuousCallback}) - mapreduce( - reinitialization_alg, vcat, cbs, init = SciMLBase.DAEInitializationAlgorithm[]) +function is_timed_condition(condition::T) + if T <: Real + true + elseif T <: AbstractVector + eltype(V) <: Real + else + false + end end -initialize_affects(cb::SymbolicContinuousCallback) = cb.initialize -function initialize_affects(cbs::Vector{SymbolicContinuousCallback}) - mapreduce(initialize_affects, vcat, cbs, init = Equation[]) +function Base.show(io::IO, db::SymbolicDiscreteCallback) + indent = get(io, :indent, 0) + iio = IOContext(io, :indent => indent + 1) + println(io, "SymbolicDiscreteCallback:") + println(iio, "Conditions:") + print(iio, "; ") + if affects(db) != nothing + print(iio, "Affect:") + show(iio, affects(db)) + print(iio, ", ") + end + if affect_negs(db) != nothing + print(iio, "Negative-edge affect:") + show(iio, affect_negs(db)) + print(iio, ", ") + end + if initialize_affects(db) != nothing + print(iio, "Initialization affect:") + show(iio, initialize_affects(db)) + print(iio, ", ") + end + if finalize_affects(db) != nothing + print(iio, "Finalization affect:") + show(iio, finalize_affects(db)) + end + print(iio, ")") end -finalize_affects(cb::SymbolicContinuousCallback) = cb.finalize -function finalize_affects(cbs::Vector{SymbolicContinuousCallback}) - mapreduce(finalize_affects, vcat, cbs, init = Equation[]) +############################################ +########## Namespacing Utilities ########### +############################################ + +function namespace_affect(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 -namespace_affects(af::Vector, s) = Equation[namespace_affect(a, s) for a in af] -namespace_affects(af::FunctionalAffect, s) = namespace_affect(af, s) -namespace_affects(::Nothing, s) = nothing +function namespace_affects(af::Affect, s) + if af isa ImplicitDiscreteSystem + af + elseif af isa FunctionalAffect || af isa ImperativeAffect + namespace_affect(af, s) + else + nothing + end +end function namespace_callback(cb::SymbolicContinuousCallback, s)::SymbolicContinuousCallback - SymbolicContinuousCallback(; - eqs = namespace_equation.(equations(cb), (s,)), - affect = namespace_affects(affects(cb), s), - affect_neg = namespace_affects(affect_negs(cb), s), - initialize = namespace_affects(initialize_affects(cb), s), - finalize = namespace_affects(finalize_affects(cb), s), - rootfind = cb.rootfind) + SymbolicContinuousCallback( + namespace_equation.(equations(cb), (s,)), + namespace_affects(affects(cb), s), + namespace_affects(affect_negs(cb), s), + namespace_affects(initialize_affects(cb), s), + namespace_affects(finalize_affects(cb), s), + cb.rootfind) end -""" - continuous_events(sys::AbstractSystem)::Vector{SymbolicContinuousCallback} +function namespace_condition(condition, s) + is_timed_condition(condition) ? condition : namespace_expr(condition, s) +end -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`. -""" -function continuous_events(sys::AbstractSystem) - cbs = get_continuous_events(sys) - filter(!isempty, cbs) +function namespace_callback(cb::SymbolicDiscreteCallback, s)::SymbolicDiscreteCallback + SymbolicDiscreteCallback( + namespace_condition(condition(cb), s), + namespace_affects(affects(cb), s), + namespace_affects(initialize_affects(cb), s), + namespace_affects(finalize_affects(cb), s)) +end - systems = get_systems(sys) - cbs = [cbs; - reduce(vcat, - (map(cb -> namespace_callback(cb, s), continuous_events(s)) - for s in systems), - init = SymbolicContinuousCallback[])] - filter(!isempty, cbs) +function Base.hash(cb::SymbolicContinuousCallback, s::UInt) + s = foldr(hash, cb.eqs, init = s) + s = hash(cb.affect, s) + s = hash(cb.affect_neg, s) + s = hash(cb.initialize, s) + s = hash(cb.finalize, s) + hash(cb.rootfind, s) end -function vars!(vars, cb::SymbolicContinuousCallback; op = Differential) - for eq in equations(cb) - vars!(vars, eq; op) - end - for aff in (affects(cb), affect_negs(cb), initialize_affects(cb), finalize_affects(cb)) - if aff isa Vector{Equation} - for eq in aff - vars!(vars, eq; op) - end - elseif aff !== nothing - vars!(vars, aff; op) - end - end - return vars +function Base.hash(cb::SymbolicDiscreteCallback, s::UInt) + s = hash(cb.condition, s) + s = hash(cb.affects, s) + s = hash(cb.initialize, s) + hash(cb.finalize, s) end -""" - continuous_events_toplevel(sys::AbstractSystem) +########################### +######### Helpers ######### +########################### -Replicates the behaviour of `continuous_events`, but ignores events of subsystems. +conditions(cb::AbstractCallback) = cb.conditions +conditions(cbs::Vector{<:AbstractCallback}) = reduce(vcat, conditions(cb) for cb in cbs) +equations(cb::AbstractCallback) = conditions(cb) +equations(cb::Vector{<:AbstractCallback}) = conditions(cb) -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 +affects(cb::AbstractCallback) = cb.affect +affects(cbs::Vector{<:AbstractCallback}) = reduce(vcat, affects(cb) for cb in cbs; init = []) -#################################### discrete events ##################################### +affect_negs(cb::SymbolicContinuousCallback) = cb.affect_neg +affect_negs(cbs::Vector{SymbolicContinuousCallback})= mapreduce(affect_negs, vcat, cbs, init = Equation[]) -struct SymbolicDiscreteCallback - # condition can be one of: - # Δt::Real - Periodic with period Δt - # Δts::Vector{Real} - events trigger in this times (Preset) - # condition::Vector{Equation} - event triggered when condition is true - # TODO: Iterative - condition::Any - affects::Any - initialize::Any - finalize::Any - reinitializealg::SciMLBase.DAEInitializationAlgorithm +initialize_affects(cb::AbstractCallback) = cb.initialize +initialize_affects(cbs::Vector{<:AbstractCallback}) = mapreduce(initialize_affects, vcat, cbs, init = Equation[]) - function SymbolicDiscreteCallback( - condition, affects = NULL_AFFECT; reinitializealg = SciMLBase.CheckInit(), - initialize = NULL_AFFECT, finalize = NULL_AFFECT) - c = scalarize_condition(condition) - a = scalarize_affects(affects) - new(c, a, scalarize_affects(initialize), - scalarize_affects(finalize), reinitializealg) - end # Default affect to nothing +finalize_affects(cb::AbstractCallback) = cb.finalize +finalize_affects(cbs::Vector{<:AbstractCallback}) = mapreduce(finalize_affects, vcat, cbs, init = Equation[]) + +function Base.:(==)(e1::SymbolicDiscreteCallback, e2::SymbolicDiscreteCallback) + isequal(e1.condition, e2.condition) && isequal(e1.affects, e2.affects) && + isequal(e1.initialize, e2.initialize) && isequal(e1.finalize, e2.finalize) end -is_timed_condition(cb) = false -is_timed_condition(::R) where {R <: Real} = true -is_timed_condition(::V) where {V <: AbstractVector} = eltype(V) <: Real -is_timed_condition(::Num) = false -is_timed_condition(cb::SymbolicDiscreteCallback) = is_timed_condition(condition(cb)) - -function scalarize_condition(condition) - is_timed_condition(condition) ? condition : value(scalarize(condition)) -end -function namespace_condition(condition, s) - is_timed_condition(condition) ? condition : namespace_expr(condition, s) -end - -scalarize_affects(affects) = scalarize(affects) -scalarize_affects(affects::Tuple) = FunctionalAffect(affects...) -scalarize_affects(affects::NamedTuple) = FunctionalAffect(; affects...) -scalarize_affects(affects::FunctionalAffect) = affects - -SymbolicDiscreteCallback(p::Pair) = SymbolicDiscreteCallback(p[1], p[2]) -SymbolicDiscreteCallback(cb::SymbolicDiscreteCallback) = cb # passthrough - -function Base.show(io::IO, db::SymbolicDiscreteCallback) - println(io, "condition: ", db.condition) - println(io, "affects:") - if db.affects isa FunctionalAffect || db.affects isa ImperativeAffect - # TODO - println(io, " ", db.affects) - else - for affect in db.affects - println(io, " ", affect) - end - end -end - -function Base.:(==)(e1::SymbolicDiscreteCallback, e2::SymbolicDiscreteCallback) - isequal(e1.condition, e2.condition) && isequal(e1.affects, e2.affects) && - isequal(e1.initialize, e2.initialize) && isequal(e1.finalize, e2.finalize) -end -function Base.hash(cb::SymbolicDiscreteCallback, s::UInt) - s = hash(cb.condition, s) - s = cb.affects isa AbstractVector ? foldr(hash, cb.affects, init = s) : - hash(cb.affects, s) - s = cb.initialize isa AbstractVector ? foldr(hash, cb.initialize, init = s) : - hash(cb.initialize, s) - s = cb.finalize isa AbstractVector ? foldr(hash, cb.finalize, init = s) : - hash(cb.finalize, s) - s = hash(cb.reinitializealg, s) - return s -end - -condition(cb::SymbolicDiscreteCallback) = cb.condition -function conditions(cbs::Vector{<:SymbolicDiscreteCallback}) - reduce(vcat, condition(cb) for cb in cbs) -end - -affects(cb::SymbolicDiscreteCallback) = cb.affects - -function affects(cbs::Vector{SymbolicDiscreteCallback}) - reduce(vcat, affects(cb) for cb in cbs; init = []) -end - -reinitialization_alg(cb::SymbolicDiscreteCallback) = cb.reinitializealg -function reinitialization_algs(cbs::Vector{SymbolicDiscreteCallback}) - mapreduce( - reinitialization_alg, vcat, cbs, init = SciMLBase.DAEInitializationAlgorithm[]) -end - -initialize_affects(cb::SymbolicDiscreteCallback) = cb.initialize -function initialize_affects(cbs::Vector{SymbolicDiscreteCallback}) - mapreduce(initialize_affects, vcat, cbs, init = Equation[]) -end - -finalize_affects(cb::SymbolicDiscreteCallback) = cb.finalize -function finalize_affects(cbs::Vector{SymbolicDiscreteCallback}) - mapreduce(finalize_affects, vcat, cbs, init = Equation[]) -end - -function namespace_callback(cb::SymbolicDiscreteCallback, s)::SymbolicDiscreteCallback - function namespace_affects(af) - return af isa AbstractVector ? namespace_affect.(af, Ref(s)) : - namespace_affect(af, s) - end - SymbolicDiscreteCallback( - namespace_condition(condition(cb), s), namespace_affects(affects(cb)), - reinitializealg = cb.reinitializealg, initialize = namespace_affects(initialize_affects(cb)), - finalize = namespace_affects(finalize_affects(cb))) -end - -SymbolicDiscreteCallbacks(cb::Pair) = SymbolicDiscreteCallback[SymbolicDiscreteCallback(cb)] -SymbolicDiscreteCallbacks(cbs::Vector) = SymbolicDiscreteCallback.(cbs) -SymbolicDiscreteCallbacks(cb::SymbolicDiscreteCallback) = [cb] -SymbolicDiscreteCallbacks(cbs::Vector{<:SymbolicDiscreteCallback}) = cbs -SymbolicDiscreteCallbacks(::Nothing) = 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`. -""" -function discrete_events(sys::AbstractSystem) - cbs = get_discrete_events(sys) - systems = get_systems(sys) - cbs = [cbs; - reduce(vcat, - (map(cb -> namespace_callback(cb, s), discrete_events(s)) for s in systems), - init = SymbolicDiscreteCallback[])] - cbs -end - -function vars!(vars, cb::SymbolicDiscreteCallback; op = Differential) - if symbolic_type(cb.condition) == NotSymbolic - if cb.condition isa AbstractArray - for eq in cb.condition - vars!(vars, eq; op) - end - end - else - vars!(vars, cb.condition; op) - end - for aff in (cb.affects, cb.initialize, cb.finalize) - if aff isa Vector{Equation} - for eq in aff - vars!(vars, eq; op) - end - elseif aff !== nothing - vars!(vars, aff; op) - end - end - 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) +function Base.:(==)(e1::SymbolicContinuousCallback, e2::SymbolicContinuousCallback) + isequal(e1.eqs, e2.eqs) && isequal(e1.affect, e2.affect) && + isequal(e1.initialize, e2.initialize) && isequal(e1.finalize, e2.finalize) && + isequal(e1.affect_neg, e2.affect_neg) && isequal(e1.rootfind, e2.rootfind) end -################################# compilation functions #################################### - -# handles ensuring that affect! functions work with integrator arguments -function add_integrator_header( - sys::AbstractSystem, integrator = gensym(:MTKIntegrator), out = :u) - expr -> Func([DestructuredArgs(expr.args, integrator, inds = [:u, :p, :t])], [], - expr.body), - expr -> Func( - [DestructuredArgs(expr.args, integrator, inds = [out, :u, :p, :t])], [], - expr.body) -end +Base.isempty(cb::AbstractCallback) = isempty(cb.conditions) +#################################### +####### Compilation functions ###### +#################################### +# function condition_header(sys::AbstractSystem, integrator = gensym(:MTKIntegrator)) expr -> Func( [expr.args[1], expr.args[2], @@ -713,27 +423,6 @@ function condition_header(sys::AbstractSystem, integrator = gensym(:MTKIntegrato expr.body) end -function callback_save_header(sys::AbstractSystem, cb) - if !(has_index_cache(sys) && (ic = get_index_cache(sys)) !== nothing) - return (identity, identity) - end - save_idxs = get(ic.callback_to_clocks, cb, Int[]) - isempty(save_idxs) && return (identity, identity) - - wrapper = function (expr) - return Func(expr.args, [], - LiteralExpr(quote - $(expr.body) - save_idxs = $(save_idxs) - for idx in save_idxs - $(SciMLBase.save_discretes!)($(expr.args[1]), idx) - end - end)) - end - - return wrapper, wrapper -end - """ compile_condition(cb::SymbolicDiscreteCallback, sys, dvs, ps; expression, kwargs...) @@ -767,331 +456,20 @@ function compile_condition(cb::SymbolicDiscreteCallback, sys, dvs, ps; return eval_or_rgf(expr; eval_expression, eval_module) end -function compile_affect(cb::SymbolicContinuousCallback, args...; kwargs...) - compile_affect(affects(cb), cb, args...; kwargs...) -end - -""" - compile_affect(eqs::Vector{Equation}, sys, dvs, ps; expression, outputidxs, kwargs...) - compile_affect(cb::SymbolicContinuousCallback, args...; kwargs...) - -Returns a function that takes an integrator as argument and modifies the state with the -affect. The generated function has the signature `affect!(integrator)`. - -Notes - - - `expression = Val{true}`, causes the generated function to be returned as an expression. - If set to `Val{false}` a `RuntimeGeneratedFunction` will be returned. - - `outputidxs`, a vector of indices of the output variables which should correspond to - `unknowns(sys)`. If provided, checks that the LHS of affect equations are variables are - dropped, i.e. it is assumed these indices are correct and affect equations are - well-formed. - - `kwargs` are passed through to `Symbolics.build_function`. -""" -function compile_affect(eqs::Vector{Equation}, cb, sys, dvs, ps; outputidxs = nothing, - expression = Val{true}, checkvars = true, eval_expression = false, - eval_module = @__MODULE__, - postprocess_affect_expr! = nothing, kwargs...) - if isempty(eqs) - if expression == Val{true} - return :((args...) -> ()) - else - return (args...) -> () # We don't do anything in the callback, we're just after the event - end - else - eqs = flatten_equations(eqs) - rhss = map(x -> x.rhs, eqs) - outvar = :u - if outputidxs === nothing - lhss = map(x -> x.lhs, eqs) - all(isvariable, lhss) || - error("Non-variable symbolic expression found on the left hand side of an affect equation. Such equations must be of the form variable ~ symbolic expression for the new value of the variable.") - update_vars = collect(Iterators.flatten(map(ModelingToolkit.vars, lhss))) # these are the ones we're changing - length(update_vars) == length(unique(update_vars)) == length(eqs) || - error("affected variables not unique, each unknown can only be affected by one equation for a single `root_eqs => affects` pair.") - alleq = all(isequal(isparameter(first(update_vars))), - Iterators.map(isparameter, update_vars)) - if !isparameter(first(lhss)) && alleq - unknownind = Dict(reverse(en) for en in enumerate(dvs)) - update_inds = map(sym -> unknownind[sym], update_vars) - elseif isparameter(first(lhss)) && alleq - if has_index_cache(sys) && get_index_cache(sys) !== nothing - update_inds = map(update_vars) do sym - return parameter_index(sys, sym) - end - else - psind = Dict(reverse(en) for en in enumerate(ps)) - update_inds = map(sym -> psind[sym], update_vars) - end - outvar = :p - else - error("Error, building an affect function for a callback that wants to modify both parameters and unknowns. This is not currently allowed in one individual callback.") - end - else - update_inds = outputidxs - end - - _ps = ps - ps = reorder_parameters(sys, ps) - if checkvars - u = map(value, dvs) - p = map.(value, ps) - else - u = dvs - p = ps - end - t = get_iv(sys) - integ = gensym(:MTKIntegrator) - rf_oop, rf_ip = build_function_wrapper( - sys, rhss, u, p..., t; expression = Val{true}, - wrap_code = callback_save_header(sys, cb) .∘ - add_integrator_header(sys, integ, outvar), - outputidxs = update_inds, - create_bindings = false, - kwargs..., cse = false) - # applied user-provided function to the generated expression - if postprocess_affect_expr! !== nothing - postprocess_affect_expr!(rf_ip, integ) - end - if expression == Val{false} - return eval_or_rgf(rf_ip; eval_expression, eval_module) - end - return rf_ip - end -end - -function generate_rootfinding_callback(sys::AbstractTimeDependentSystem, - dvs = unknowns(sys), ps = parameters(sys; initial_parameters = true); kwargs...) - cbs = continuous_events(sys) - isempty(cbs) && return nothing - 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_rootfinding_callback and thus we can produce a ContinuousCallback instead of a VectorContinuousCallback. -""" -function generate_single_rootfinding_callback( - eq, cb, sys::AbstractTimeDependentSystem, dvs = unknowns(sys), - ps = parameters(sys; initial_parameters = true); kwargs...) - if !isequal(eq.lhs, 0) - eq = 0 ~ eq.lhs - eq.rhs - end - - rf_oop, rf_ip = generate_custom_function( - sys, [eq.rhs], dvs, ps; expression = Val{false}, kwargs..., cse = false) - affect_function = compile_affect_fn(cb, sys, dvs, ps, kwargs) - cond = function (u, t, integ) - if DiffEqBase.isinplace(integ.sol.prob) - tmp, = DiffEqBase.get_tmp_cache(integ) - rf_ip(tmp, u, parameter_values(integ), t) - tmp[1] - else - rf_oop(u, parameter_values(integ), t) - end - end - user_initfun = isnothing(affect_function.initialize) ? SciMLBase.INITIALIZE_DEFAULT : - (c, u, t, i) -> affect_function.initialize(i) - if has_index_cache(sys) && (ic = get_index_cache(sys)) !== nothing && - (save_idxs = get(ic.callback_to_clocks, cb, nothing)) !== nothing - initfn = let save_idxs = save_idxs - function (cb, u, t, integrator) - user_initfun(cb, u, t, integrator) - for idx in save_idxs - SciMLBase.save_discretes!(integrator, idx) - end - end - end - else - initfn = user_initfun - end - - return ContinuousCallback( - cond, affect_function.affect, affect_function.affect_neg, rootfind = cb.rootfind, - initialize = initfn, - finalize = isnothing(affect_function.finalize) ? SciMLBase.FINALIZE_DEFAULT : - (c, u, t, i) -> affect_function.finalize(i), - initializealg = reinitialization_alg(cb)) -end - -function generate_vector_rootfinding_callback( - cbs, sys::AbstractTimeDependentSystem, dvs = unknowns(sys), - ps = parameters(sys; initial_parameters = true); rootfind = SciMLBase.RightRootFind, - reinitialization = SciMLBase.CheckInit(), kwargs...) - eqs = map(cb -> flatten_equations(cb.eqs), cbs) - num_eqs = length.(eqs) - # fuse equations to create VectorContinuousCallback - eqs = reduce(vcat, eqs) - # rewrite all equations as 0 ~ interesting stuff - eqs = map(eqs) do eq - isequal(eq.lhs, 0) && return eq - 0 ~ eq.lhs - eq.rhs - end - - rhss = map(x -> x.rhs, eqs) - _, rf_ip = generate_custom_function( - sys, rhss, dvs, ps; expression = Val{false}, kwargs..., cse = false) - - affect_functions = @NamedTuple{ - affect::Function, - affect_neg::Union{Function, Nothing}, - initialize::Union{Function, Nothing}, - finalize::Union{Function, Nothing}}[ - compile_affect_fn(cb, sys, dvs, ps, kwargs) - for cb in cbs] - cond = function (out, u, t, integ) - rf_ip(out, u, parameter_values(integ), t) - end - - # since there may be different number of conditions and affects, - # we build a map that translates the condition eq. number to the affect number - eq_ind2affect = reduce(vcat, - [fill(i, num_eqs[i]) for i in eachindex(affect_functions)]) - @assert length(eq_ind2affect) == length(eqs) - @assert maximum(eq_ind2affect) == length(affect_functions) - - affect = let affect_functions = affect_functions, eq_ind2affect = eq_ind2affect - function (integ, eq_ind) # eq_ind refers to the equation index that triggered the event, each event has num_eqs[i] equations - affect_functions[eq_ind2affect[eq_ind]].affect(integ) - end - end - affect_neg = let affect_functions = affect_functions, eq_ind2affect = eq_ind2affect - function (integ, eq_ind) # eq_ind refers to the equation index that triggered the event, each event has num_eqs[i] equations - affect_neg = affect_functions[eq_ind2affect[eq_ind]].affect_neg - if isnothing(affect_neg) - return # skip if the neg function doesn't exist - don't want to split this into a separate VCC because that'd break ordering - end - affect_neg(integ) - end - end - function handle_optional_setup_fn(funs, default) - if all(isnothing, funs) - return default - else - return let funs = funs - function (cb, u, t, integ) - for func in funs - if isnothing(func) - continue - else - func(integ) - end - end - end - end - end - end - initialize = nothing - if has_index_cache(sys) && (ic = get_index_cache(sys)) !== nothing - initialize = handle_optional_setup_fn( - map(cbs, affect_functions) do cb, fn - if (save_idxs = get(ic.callback_to_clocks, cb, nothing)) !== nothing - let save_idxs = save_idxs - custom_init = fn.initialize - (i) -> begin - !isnothing(custom_init) && custom_init(i) - for idx in save_idxs - SciMLBase.save_discretes!(i, idx) - end - end - end - else - fn.initialize - end - end, - SciMLBase.INITIALIZE_DEFAULT) - - else - initialize = handle_optional_setup_fn( - map(fn -> fn.initialize, affect_functions), SciMLBase.INITIALIZE_DEFAULT) - end - - finalize = handle_optional_setup_fn( - map(fn -> fn.finalize, affect_functions), SciMLBase.FINALIZE_DEFAULT) - return VectorContinuousCallback( - cond, affect, affect_neg, length(eqs), rootfind = rootfind, - initialize = initialize, finalize = finalize, initializealg = reinitialization) -end - """ -Compile a single continuous callback affect function(s). +Compile user-defined functional affect. """ -function compile_affect_fn(cb, sys::AbstractTimeDependentSystem, dvs, ps, kwargs) - eq_aff = affects(cb) - eq_neg_aff = affect_negs(cb) - affect = compile_affect(eq_aff, cb, sys, dvs, ps; expression = Val{false}, kwargs...) - - if eq_neg_aff === eq_aff - affect_neg = affect - else - affect_neg = _compile_optional_affect( - NULL_AFFECT, eq_neg_aff, cb, sys, dvs, ps; kwargs...) - end - initialize = _compile_optional_affect( - NULL_AFFECT, initialize_affects(cb), cb, sys, dvs, ps; kwargs...) - finalize = _compile_optional_affect( - NULL_AFFECT, finalize_affects(cb), cb, sys, dvs, ps; kwargs...) - (affect = affect, affect_neg = affect_neg, initialize = initialize, finalize = finalize) -end - -function generate_rootfinding_callback(cbs, sys::AbstractTimeDependentSystem, - dvs = unknowns(sys), ps = parameters(sys; initial_parameters = true); kwargs...) - eqs = map(cb -> flatten_equations(cb.eqs), cbs) - num_eqs = length.(eqs) - total_eqs = sum(num_eqs) - (isempty(eqs) || total_eqs == 0) && return nothing - if total_eqs == 1 - # find the callback with only one eq - cb_ind = findfirst(>(0), num_eqs) - if isnothing(cb_ind) - error("Inconsistent state in affect compilation; one equation but no callback with equations?") - end - cb = cbs[cb_ind] - return generate_single_rootfinding_callback(cb.eqs[], cb, sys, dvs, ps; kwargs...) - end - - # group the cbs by what rootfind op they use - # groupby would be very useful here, but alas - cb_classes = Dict{ - @NamedTuple{ - rootfind::SciMLBase.RootfindOpt, - reinitialization::SciMLBase.DAEInitializationAlgorithm}, Vector{SymbolicContinuousCallback}}() - for cb in cbs - push!( - get!(() -> SymbolicContinuousCallback[], cb_classes, - ( - rootfind = cb.rootfind, - reinitialization = reinitialization_alg(cb))), - cb) - end - - # generate the callbacks out; we sort by the equivalence class to ensure a deterministic preference order - compiled_callbacks = map(collect(pairs(sort!( - OrderedDict(cb_classes); by = p -> p.rootfind)))) do (equiv_class, cbs_in_class) - return generate_vector_rootfinding_callback( - cbs_in_class, sys, dvs, ps; rootfind = equiv_class.rootfind, - reinitialization = equiv_class.reinitialization, kwargs...) - end - if length(compiled_callbacks) == 1 - return compiled_callbacks[] - else - return CallbackSet(compiled_callbacks...) - end -end - -function compile_user_affect(affect::FunctionalAffect, cb, sys, dvs, ps; kwargs...) +function compile_functional_affect(affect::FunctionalAffect, cb, sys, dvs, ps; kwargs...) dvs_ind = Dict(reverse(en) for en in enumerate(dvs)) v_inds = map(sym -> dvs_ind[sym], unknowns(affect)) if has_index_cache(sys) && get_index_cache(sys) !== nothing - p_inds = [if (pind = parameter_index(sys, sym)) === nothing - sym - else - pind - end - for sym in parameters(affect)] + p_inds = [(pind = parameter_index(sys, sym)) === nothing ? sym : pind for sym in parameters(affect)] + save_idxs = get(ic. callback_to_clocks, cb, Int[]) else ps_ind = Dict(reverse(en) for en in enumerate(ps)) p_inds = map(sym -> get(ps_ind, sym, sym), parameters(affect)) + save_idxs = Int[] end # HACK: filter out eliminated symbols. Not clear this is the right thing to do # (MTK should keep these symbols) @@ -1100,11 +478,6 @@ function compile_user_affect(affect::FunctionalAffect, cb, sys, dvs, ps; kwargs. p = filter(x -> !isnothing(x[2]), collect(zip(parameters_syms(affect), p_inds))) |> NamedTuple - if has_index_cache(sys) && (ic = get_index_cache(sys)) !== nothing - save_idxs = get(ic.callback_to_clocks, cb, Int[]) - else - save_idxs = Int[] - end let u = u, p = p, user_affect = func(affect), ctx = context(affect), save_idxs = save_idxs @@ -1117,151 +490,146 @@ function compile_user_affect(affect::FunctionalAffect, cb, sys, dvs, ps; kwargs. end end -function invalid_variables(sys, expr) - filter(x -> !any(isequal(x), all_symbols(sys)), reduce(vcat, vars(expr); init = [])) -end -function unassignable_variables(sys, expr) - assignable_syms = reduce( - vcat, Symbolics.scalarize.(vcat( - unknowns(sys), parameters(sys; initial_parameters = true))); - init = []) - written = reduce(vcat, Symbolics.scalarize.(vars(expr)); init = []) - return filter( - x -> !any(isequal(x), assignable_syms), written) -end +""" +Codegen a DifferentialEquations callback. A set of continuous callbacks becomes a VectorContinuousCallback. +Individual callbacks become DiscreteCallback, PresetTimeCallback, PeriodicCallback, or ContinuousCallback +depending on the case. +""" +function generate_callback(cbs::Vector{SymbolicContinuousCallback}, sys; is_discrete = false) + is_discrete && error() + eqs = map(cb -> flatten_equations(cb.eqs), cbs) + _, f_iip = generate_custom_function( + sys, [eq.lhs - eq.rhs for eq in eqs], unknowns(sys), parameters(sys); + expression = Val{false}) + trigger = (out, u, t, integ) -> f_iip(out, u, parameter_values(integ), t) -@generated function _generated_writeback(integ, setters::NamedTuple{NS1, <:Tuple}, - values::NamedTuple{NS2, <:Tuple}) where {NS1, NS2} - setter_exprs = [] - for name in NS2 - if !(name in NS1) - missing_name = "Tried to write back to $name from affect; only declared states ($NS1) may be written to." - error(missing_name) - end - push!(setter_exprs, :(setters.$name(integ, values.$name))) - end - return :(begin - $(setter_exprs...) - end) -end + affects = [] + affect_negs = [] + inits = [] + finals = [] + for cb in cbs + affect = compile_affect(cb.affect) + push!(affects, affect) -function check_assignable(sys, sym) - if symbolic_type(sym) == ScalarSymbolic() - is_variable(sys, sym) || is_parameter(sys, sym) - elseif symbolic_type(sym) == ArraySymbolic() - is_variable(sys, sym) || is_parameter(sys, sym) || - all(x -> check_assignable(sys, x), collect(sym)) - elseif sym isa Union{AbstractArray, Tuple} - all(x -> check_assignable(sys, x), sym) - else - false + isnothing(cb.affect_neg) ? + push!(affect_negs, affect) : + push!(affect_negs, compile_affect(cb.affect_neg)) + + push!(inits, compile_affect(cb.initialize, default = SciMLBase.INITALIZE_DEFAULT)) + push!(finals, compile_affect(cb.finalize, default = SciMLBase.FINALIZE_DEFAULT)) end -end -function compile_affect(affect::FunctionalAffect, cb, sys, dvs, ps; kwargs...) - compile_user_affect(affect, cb, sys, dvs, ps; kwargs...) -end + # Since there may be different number of conditions and affects, + # we build a map that translates the condition eq. number to the affect number + num_eqs = length.(eqs) + eq2affect = reduce(vcat, + [fill(i, num_eqs[i]) for i in eachindex(affects)]) + @assert length(eq2affect) == length(eqs) + @assert maximum(eq2affect) == length(affect_functions) -function _compile_optional_affect(default, aff, cb, sys, dvs, ps; kwargs...) - if isnothing(aff) || aff == default - return nothing - else - return compile_affect(aff, cb, sys, dvs, ps; expression = Val{false}, kwargs...) + affect = function (integ, idx) + affects[eq2affect[idx]](integ) + end + affect_neg = function (integ, idx) + f = affect_negs[eq2affect[idx]] + isnothing(f) && return + f(integ) end + initialize = compile_vector_optional_affect(inits, SciMLBase.INITIALIZE_DEFAULT) + finalize = compile_vector_optional_affect(finals, SciMLBase.FINALIZE_DEFAULT) + + return VectorContinuousCallback(trigger, affect; affect_neg, initialize, finalize, rootfind = callback.rootfind, initializealg = SciMLBase.NoInit) end -function generate_timed_callback(cb, sys, dvs, ps; postprocess_affect_expr! = nothing, - kwargs...) - cond = condition(cb) - as = compile_affect(affects(cb), cb, sys, dvs, ps; expression = Val{false}, - postprocess_affect_expr!, kwargs...) - - user_initfun = _compile_optional_affect( - NULL_AFFECT, initialize_affects(cb), cb, sys, dvs, ps; kwargs...) - user_finfun = _compile_optional_affect( - NULL_AFFECT, finalize_affects(cb), cb, sys, dvs, ps; kwargs...) - if has_index_cache(sys) && (ic = get_index_cache(sys)) !== nothing && - (save_idxs = get(ic.callback_to_clocks, cb, nothing)) !== nothing - initfn = let - save_idxs = save_idxs - initfun = user_initfun - function (cb, u, t, integrator) - if !isnothing(initfun) - initfun(integrator) - end - for idx in save_idxs - SciMLBase.save_discretes!(integrator, idx) - end - end +function generate_callback(cb, sys; is_discrete = false) + is_timed = is_timed_condition(conditions(cb)) + + trigger = if is_discrete + is_timed ? condition(cb) : + compile_condition(callback, sys, unknowns(sys), parameters(sys)) + else + _, f_iip = generate_custom_function( + sys, [eq.rhs - eq.lhs for eq in equations(cb)], unknowns(sys), parameters(sys); + expression = Val{false}) + (out, u, t, integ) -> f_iip(out, u, parameter_values(integ), t) + end + + affect = compile_affect(cb.affect) + affect_neg = isnothing(cb.affect_neg) ? affect_fn : compile_affect(cb.affect_neg) + initialize = compile_affect(cb.initialize, default = SciMLBase.INITIALIZE_DEFAULT) + finalize = compile_affect(cb.finalize, default = SciMLBase.FINALIZE_DEFAULT) + + if is_discrete + if is_timed && condition(cb) isa AbstractVector + return PresetTimeCallback(trigger, affect; affect_neg, initialize, finalize, initializealg = SciMLBase.NoInit) + elseif is_timed + return PeriodicCallback(affect, trigger; initialize, finalize) + else + return DiscreteCallback(trigger, affect; affect_neg, initialize, finalize, initializealg = SciMLBase.NoInit) end else - initfn = isnothing(user_initfun) ? SciMLBase.INITIALIZE_DEFAULT : - (_, _, _, i) -> user_initfun(i) - end - finfun = isnothing(user_finfun) ? SciMLBase.FINALIZE_DEFAULT : - (_, _, _, i) -> user_finfun(i) - if cond isa AbstractVector - # Preset Time - return PresetTimeCallback( - cond, as; initialize = initfn, finalize = finfun, - initializealg = reinitialization_alg(cb)) - else - # Periodic - return PeriodicCallback( - as, cond; initialize = initfn, finalize = finfun, - initializealg = reinitialization_alg(cb)) + return ContinuousCallback(trigger, affect; affect_neg, initialize, finalize, rootfind = callback.rootfind, initializealg = SciMLBase.NoInit) end end -function generate_discrete_callback(cb, sys, dvs, ps; postprocess_affect_expr! = nothing, - kwargs...) - if is_timed_condition(cb) - return generate_timed_callback(cb, sys, dvs, ps; postprocess_affect_expr!, - kwargs...) +""" + compile_affect(cb::AbstractCallback, sys::AbstractSystem, dvs, ps; expression, outputidxs, kwargs...) + +Returns a function that takes an integrator as argument and modifies the state with the +affect. The generated function has the signature `affect!(integrator)`. + +Notes + + - `expression = Val{true}`, causes the generated function to be returned as an expression. + If set to `Val{false}` a `RuntimeGeneratedFunction` will be returned. + - `outputidxs`, a vector of indices of the output variables which should correspond to + `unknowns(sys)`. If provided, checks that the LHS of affect equations are variables are + dropped, i.e. it is assumed these indices are correct and affect equations are + well-formed. + - `kwargs` are passed through to `Symbolics.build_function`. +""" +function compile_affect(aff::Affect, cb::AbstractCallback, sys::AbstractSystem; default = nothing) + save_idxs = if !(has_index_cache(sys) && (ic = get_index_cache(sys)) !== nothing) + Int[] else - c = compile_condition(cb, sys, dvs, ps; expression = Val{false}, kwargs...) - as = compile_affect(affects(cb), cb, sys, dvs, ps; expression = Val{false}, - postprocess_affect_expr!, kwargs...) - - user_initfun = _compile_optional_affect( - NULL_AFFECT, initialize_affects(cb), cb, sys, dvs, ps; kwargs...) - user_finfun = _compile_optional_affect( - NULL_AFFECT, finalize_affects(cb), cb, sys, dvs, ps; kwargs...) - if has_index_cache(sys) && (ic = get_index_cache(sys)) !== nothing && - (save_idxs = get(ic.callback_to_clocks, cb, nothing)) !== nothing - initfn = let save_idxs = save_idxs, initfun = user_initfun - function (cb, u, t, integrator) - if !isnothing(initfun) - initfun(integrator) - end - for idx in save_idxs - SciMLBase.save_discretes!(integrator, idx) - end - end + get(ic.callback_to_clocks, cb, Int[]) + end + + isnothing(aff) && return default + + ps = parameters(aff) + dvs = unknowns(aff) + + if aff isa ImplicitDiscreteSystem + function affect!(integrator) + u0map = [u => integrator[u] for u in dvs] + prob = ImplicitDiscreteProblem(aff, u0map, (0, 1), []) + sol = solve(prob) + for u in dvs + integrator[u] = sol[u][end] + end + + for idx in save_idxs + SciMLBase.save_discretes!(integ, idx) end - else - initfn = isnothing(user_initfun) ? SciMLBase.INITIALIZE_DEFAULT : - (_, _, _, i) -> user_initfun(i) end - finfun = isnothing(user_finfun) ? SciMLBase.FINALIZE_DEFAULT : - (_, _, _, i) -> user_finfun(i) - return DiscreteCallback( - c, as; initialize = initfn, finalize = finfun, - initializealg = reinitialization_alg(cb)) + elseif aff isa FunctionalAffect || aff isa ImperativeAffect + compile_functional_affect(aff, callback, sys, dvs, ps) end end -function generate_discrete_callbacks(sys::AbstractSystem, dvs = unknowns(sys), - ps = parameters(sys; initial_parameters = true); kwargs...) - has_discrete_events(sys) || return nothing - symcbs = discrete_events(sys) - isempty(symcbs) && return nothing - - dbs = map(symcbs) do cb - generate_discrete_callback(cb, sys, dvs, ps; kwargs...) +""" +Initialize and Finalize for VectorContinuousCallback. +""" +function compile_vector_optional_affect(funs, default) + all(isnothing, funs) && return default + return let funs = funs + function (cb, u, t, integ) + for func in funs + isnothing(func) ? continue : func(integ) + end + end end - - dbs end merge_cb(::Nothing, ::Nothing) = nothing @@ -1271,12 +639,12 @@ merge_cb(x, y) = CallbackSet(x, y) function process_events(sys; callback = nothing, kwargs...) if has_continuous_events(sys) && !isempty(continuous_events(sys)) - contin_cb = generate_rootfinding_callback(sys; kwargs...) + contin_cb = generate_callback(sys; kwargs...) else contin_cb = nothing end if has_discrete_events(sys) && !isempty(discrete_events(sys)) - discrete_cb = generate_discrete_callbacks(sys; kwargs...) + discrete_cb = generate_callback(sys; is_discrete = true, kwargs...) else discrete_cb = nothing end @@ -1284,3 +652,58 @@ function process_events(sys; callback = nothing, kwargs...) cb = merge_cb(contin_cb, callback) (discrete_cb === nothing) ? cb : CallbackSet(contin_cb, discrete_cb...) end + +""" + 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`. + +See also `get_discrete_events`, which only returns the events of the top-level system. +""" +function discrete_events(sys::AbstractSystem) + obs = get_discrete_events(sys) + systems = get_systems(sys) + cbs = [obs; + reduce(vcat, + (map(o -> namespace_callback(o, s), discrete_events(s)) for s in systems), + init = SymbolicDiscreteCallback[])] + cbs +end + +has_discrete_events(sys::AbstractSystem) = isdefined(sys, :discrete_events) +function get_discrete_events(sys::AbstractSystem) + has_discrete_events(sys) || return SymbolicDiscreteCallback[] + getfield(sys, :discrete_events) +end + +""" + 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`. + +See also `get_continuous_events`, which only returns the events of the top-level system. +""" +function continuous_events(sys::AbstractSystem) + obs = get_continuous_events(sys) + filter(!isempty, obs) + + systems = get_systems(sys) + cbs = [obs; + reduce(vcat, + (map(o -> namespace_callback(o, s), continuous_events(s)) + for s in systems), + init = SymbolicContinuousCallback[])] + filter(!isempty, cbs) +end + +has_continuous_events(sys::AbstractSystem) = isdefined(sys, :continuous_events) +function get_continuous_events(sys::AbstractSystem) + has_continuous_events(sys) || return SymbolicContinuousCallback[] + getfield(sys, :continuous_events) +end diff --git a/src/systems/diffeqs/odesystem.jl b/src/systems/diffeqs/odesystem.jl index 630ba74295..67b7164806 100644 --- a/src/systems/diffeqs/odesystem.jl +++ b/src/systems/diffeqs/odesystem.jl @@ -335,8 +335,10 @@ function ODESystem(deqs::AbstractVector{<:Equation}, iv, dvs, ps; if length(unique(sysnames)) != length(sysnames) throw(ArgumentError("System names must be unique.")) end - cont_callbacks = SymbolicContinuousCallbacks(continuous_events) - disc_callbacks = SymbolicDiscreteCallbacks(discrete_events) + + algeeqs = filter(is_alg_equation, deqs) + cont_callbacks = generate_continuous_callbacks(continuous_events, algeeqs) + disc_callbacks = generate_discrete_callbacks(discrete_events, algeeqs) if is_dde === nothing is_dde = _check_if_dde(deqs, iv′, systems) diff --git a/src/systems/imperative_affect.jl b/src/systems/imperative_affect.jl index 4c9ff3d248..a58c608233 100644 --- a/src/systems/imperative_affect.jl +++ b/src/systems/imperative_affect.jl @@ -99,7 +99,6 @@ function Base.hash(a::ImperativeAffect, s::UInt) hash(a.ctx, s) end -namespace_affects(af::ImperativeAffect, s) = namespace_affect(af, s) function namespace_affect(affect::ImperativeAffect, s) ImperativeAffect(func(affect), namespace_expr.(observed(affect), (s,)), @@ -114,6 +113,49 @@ function compile_affect(affect::ImperativeAffect, cb, sys, dvs, ps; kwargs...) compile_user_affect(affect, cb, sys, dvs, ps; kwargs...) end +function invalid_variables(sys, expr) + filter(x -> !any(isequal(x), all_symbols(sys)), reduce(vcat, vars(expr); init = [])) +end + +function unassignable_variables(sys, expr) + assignable_syms = reduce( + vcat, Symbolics.scalarize.(vcat( + unknowns(sys), parameters(sys; initial_parameters = true))); + init = []) + written = reduce(vcat, Symbolics.scalarize.(vars(expr)); init = []) + return filter( + x -> !any(isequal(x), assignable_syms), written) +end + +@generated function _generated_writeback(integ, setters::NamedTuple{NS1, <:Tuple}, + values::NamedTuple{NS2, <:Tuple}) where {NS1, NS2} + setter_exprs = [] + for name in NS2 + if !(name in NS1) + missing_name = "Tried to write back to $name from affect; only declared states ($NS1) may be written to." + error(missing_name) + end + push!(setter_exprs, :(setters.$name(integ, values.$name))) + end + return :(begin + $(setter_exprs...) + end) +end + +function check_assignable(sys, sym) + if symbolic_type(sym) == ScalarSymbolic() + is_variable(sys, sym) || is_parameter(sys, sym) + elseif symbolic_type(sym) == ArraySymbolic() + is_variable(sys, sym) || is_parameter(sys, sym) || + all(x -> check_assignable(sys, x), collect(sym)) + elseif sym isa Union{AbstractArray, Tuple} + all(x -> check_assignable(sys, x), sym) + else + false + end +end + + function compile_user_affect(affect::ImperativeAffect, cb, sys, dvs, ps; kwargs...) #= Implementation sketch: @@ -238,3 +280,4 @@ function vars!(vars, aff::ImperativeAffect; op = Differential) end return vars end + diff --git a/src/systems/systems.jl b/src/systems/systems.jl index e417337a95..7e1ce3077c 100644 --- a/src/systems/systems.jl +++ b/src/systems/systems.jl @@ -43,10 +43,10 @@ function structural_simplify( end if newsys isa DiscreteSystem && any(eq -> symbolic_type(eq.lhs) == NotSymbolic(), equations(newsys)) - error(""" - Encountered algebraic equations when simplifying discrete system. Please construct \ - an ImplicitDiscreteSystem instead. - """) + #error(""" + # Encountered algebraic equations when simplifying discrete system. Please construct \ + # an ImplicitDiscreteSystem instead. + #""") end for pass in additional_passes newsys = pass(newsys) From 811a42a567b8bc1fd91d5727321db3e75238f3ab Mon Sep 17 00:00:00 2001 From: vyudu Date: Mon, 10 Mar 2025 16:23:40 -0400 Subject: [PATCH 031/122] use Pre in the affect definition --- src/ModelingToolkit.jl | 1 + src/systems/callbacks.jl | 231 ++++++++++++++++++++++--------- src/systems/diffeqs/odesystem.jl | 4 +- src/systems/imperative_affect.jl | 4 +- 4 files changed, 169 insertions(+), 71 deletions(-) diff --git a/src/ModelingToolkit.jl b/src/ModelingToolkit.jl index d58a7a7179..05cb154cdd 100644 --- a/src/ModelingToolkit.jl +++ b/src/ModelingToolkit.jl @@ -303,6 +303,7 @@ export initialization_equations, guesses, defaults, parameter_dependencies, hier export structural_simplify, expand_connections, linearize, linearization_function, LinearizationProblem export solve +export Pre export calculate_jacobian, generate_jacobian, generate_function, generate_custom_function, generate_W diff --git a/src/systems/callbacks.jl b/src/systems/callbacks.jl index cb0daed803..df4763a0b7 100644 --- a/src/systems/callbacks.jl +++ b/src/systems/callbacks.jl @@ -55,6 +55,62 @@ function has_functional_affect(cb) (affects(cb) isa FunctionalAffect || affects(cb) isa ImperativeAffect) end +function vars!(vars, aff::FunctionalAffect; op = Differential) + for var in Iterators.flatten((unknowns(aff), parameters(aff), discretes(aff))) + vars!(vars, var) + end + return vars +end + +""" + Pre(x) + +The `Pre` operator. Used by the callback system to indicate the value of a parameter or variable +before the callback is triggered. +""" +struct Pre <: Symbolics.Operator end +Pre(x) = Pre()(x) +SymbolicUtils.promote_symtype(::Type{Pre}, T) = T +SymbolicUtils.isbinop(::Pre) = false +Base.nameof(::Pre) = :Pre +Base.show(io::IO, x::Pre) = print(io, "Pre") +input_timedomain(::Pre, _ = nothing) = ContinuousClock() +output_timedomain(::Pre, _ = nothing) = ContinuousClock() + +function (p::Pre)(x) + iw = Symbolics.iswrapped(x) + x = unwrap(x) + # non-symbolic values don't change + if symbolic_type(x) == NotSymbolic() + return x + end + # differential variables are default-toterm-ed + if iscall(x) && operation(x) isa Differential + x = default_toterm(x) + end + # don't double wrap + iscall(x) && operation(x) isa Pre && return x + result = if symbolic_type(x) == ArraySymbolic() + # create an array for `Pre(array)` + Symbolics.array_term(p, toparam(x)) + elseif iscall(x) && operation(x) == getindex + # instead of `Pre(x[1])` create `Pre(x)[1]` + # which allows parameter indexing to handle this case automatically. + arr = arguments(x)[1] + term(getindex, p(toparam(arr)), arguments(x)[2:end]...) + else + term(p, toparam(x)) + end + # the result should be a parameter + result = toparam(result) + if iw + result = wrap(result) + end + return result +end + +haspre(eq::Equation) = haspre(eq.lhs) || haspre(eq.rhs) +haspre(O) = recursive_hasoperator(Pre, O) ############################### ###### Continuous events ###### @@ -131,24 +187,30 @@ make_affect(affect::Tuple, iv) = FunctionalAffect(affects...) make_affect(affect::NamedTuple, iv) = FunctionalAffect(; affects...) make_affect(affect::FunctionalAffect, iv) = affect -# Default behavior: if no shifts are provided, then it is assumed that the RHS is the previous. -function make_affect(affect::Vector{Equation}, iv) +function make_affect(affect::Vector{Equation}, iv; warn = true) affect = scalarize(affect) unknowns = OrderedSet() params = OrderedSet() for eq in affect - collect_vars!(unknowns, params, eq, iv) + !haspre(eq) && warn && @warn "Equation $eq has no `Pre` operator. As such it will be interpreted as an algebraic equation to be satisfied after the callback. + If you intended to use the value of a variable x before the affect, use Pre(x)." + collect_vars!(unknowns, params, eq, iv; op = Pre) end - affect = map(affect) do eq - ModelingToolkit.hasshift(eq) ? eq : - eq.lhs ~ distribute_shift(Prev(eq.rhs)) - end - params = map(params) do p - p = value(p) - Sym{FnType{Tuple{symtype(iv)}, Real}}(nameof(p))(iv) + + # System parameters should become unknowns. + cb_params = OrderedSet() + sys_params = OrderedSet() + for p in params + if iscall(p) && (operator(p) isa Pre) + push!(cb_params, p) + else + p = Sym{FnType{Tuple{symtype(iv)}, Real}}(nameof(p))(iv) + p = wrap(tovar(p)) + push!(sys_params, p) + end end - @mtkbuild affect = ImplicitDiscreteSystem(affect, iv, vcat(unknowns, params), []) + @mtkbuild affect = ImplicitDiscreteSystem(affect, iv, vcat(unknowns, sys_params), cb_params) end make_affect(affect, iv) = error("Malformed affect $(affect). This should be a vector of equations or a tuple specifying a functional affect.") @@ -157,7 +219,7 @@ make_affect(affect, iv) = error("Malformed affect $(affect). This should be a ve Generate continuous callbacks. """ function SymbolicContinuousCallbacks(events, algeeqs, iv) - callbacks = MTKContinuousCallback[] + callbacks = SymbolicContinuousCallback[] (isnothing(events) || isempty(events)) && return callbacks events isa AbstractVector || (events = [events]) @@ -229,6 +291,22 @@ function Base.show(io::IO, mime::MIME"text/plain", cb::SymbolicContinuousCallbac end end +function vars!(vars, cb::SymbolicContinuousCallback; op = Differential) + for eq in equations(cb) + vars!(vars, eq; op) + end + for aff in (affects(cb), affect_negs(cb), initialize_affects(cb), finalize_affects(cb)) + if aff isa Vector{Equation} + for eq in aff + vars!(vars, eq; op) + end + elseif aff !== nothing + vars!(vars, aff; op) + end + end + return vars +end + ################################ ######## Discrete events ####### ################################ @@ -240,12 +318,12 @@ end A callback that triggers at the first timestep that the conditions are satisfied. The condition can be one of: -- Real - periodic events with period Δt -- Vector{Real} - events trigger at these preset times -- Vector{Equation} - events trigger when the condition evaluates to true +- Δt::Real - periodic events with period Δt +- ts::Vector{Real} - events trigger at these preset times given by `ts` +- eqs::Vector{Equation} - events trigger when the condition evaluates to true """ -struct SymbolicDiscreteCallback{R} <: AbstractCallback where R <: Real - conditions::Union{R, Vector{R}, Vector{Equation}} +struct SymbolicDiscreteCallback <: AbstractCallback + conditions::Any affect::Affect initialize::Union{Affect, Nothing} finalize::Union{Affect, Nothing} @@ -277,11 +355,11 @@ function SymbolicDiscreteCallbacks(events, algeeqs, iv) callbacks end -function is_timed_condition(condition::T) +function is_timed_condition(condition::T) where T if T <: Real true elseif T <: AbstractVector - eltype(V) <: Real + eltype(condition) <: Real else false end @@ -298,11 +376,6 @@ function Base.show(io::IO, db::SymbolicDiscreteCallback) show(iio, affects(db)) print(iio, ", ") end - if affect_negs(db) != nothing - print(iio, "Negative-edge affect:") - show(iio, affect_negs(db)) - print(iio, ", ") - end if initialize_affects(db) != nothing print(iio, "Initialization affect:") show(iio, initialize_affects(db)) @@ -315,6 +388,28 @@ function Base.show(io::IO, db::SymbolicDiscreteCallback) print(iio, ")") end +function vars!(vars, cb::SymbolicDiscreteCallback; op = Differential) + if symbolic_type(cb.condition) == NotSymbolic + if cb.condition isa AbstractArray + for eq in cb.condition + vars!(vars, eq; op) + end + end + else + vars!(vars, cb.condition; op) + end + for aff in (cb.affects, cb.initialize, cb.finalize) + if aff isa Vector{Equation} + for eq in aff + vars!(vars, eq; op) + end + elseif aff !== nothing + vars!(vars, aff; op) + end + end + return vars +end + ############################################ ########## Namespacing Utilities ########### ############################################ @@ -382,7 +477,7 @@ end ########################### conditions(cb::AbstractCallback) = cb.conditions -conditions(cbs::Vector{<:AbstractCallback}) = reduce(vcat, conditions(cb) for cb in cbs) +conditions(cbs::Vector{<:AbstractCallback}) = reduce(vcat, conditions(cb) for cb in cbs; init = []) equations(cb::AbstractCallback) = conditions(cb) equations(cb::Vector{<:AbstractCallback}) = conditions(cb) @@ -390,13 +485,13 @@ affects(cb::AbstractCallback) = cb.affect affects(cbs::Vector{<:AbstractCallback}) = reduce(vcat, affects(cb) for cb in cbs; init = []) affect_negs(cb::SymbolicContinuousCallback) = cb.affect_neg -affect_negs(cbs::Vector{SymbolicContinuousCallback})= mapreduce(affect_negs, vcat, cbs, init = Equation[]) +affect_negs(cbs::Vector{SymbolicContinuousCallback}) = reduce(vcat, affect_negs(cb) for cb in cbs; init = []) initialize_affects(cb::AbstractCallback) = cb.initialize -initialize_affects(cbs::Vector{<:AbstractCallback}) = mapreduce(initialize_affects, vcat, cbs, init = Equation[]) +initialize_affects(cbs::Vector{<:AbstractCallback}) = reduce(initialize_affects, vcat, cbs; init = []) finalize_affects(cb::AbstractCallback) = cb.finalize -finalize_affects(cbs::Vector{<:AbstractCallback}) = mapreduce(finalize_affects, vcat, cbs, init = Equation[]) +finalize_affects(cbs::Vector{<:AbstractCallback}) = reduce(finalize_affects, vcat, cbs; init = []) function Base.:(==)(e1::SymbolicDiscreteCallback, e2::SymbolicDiscreteCallback) isequal(e1.condition, e2.condition) && isequal(e1.affects, e2.affects) && @@ -414,7 +509,6 @@ Base.isempty(cb::AbstractCallback) = isempty(cb.conditions) #################################### ####### Compilation functions ###### #################################### -# function condition_header(sys::AbstractSystem, integrator = gensym(:MTKIntegrator)) expr -> Func( [expr.args[1], expr.args[2], @@ -424,7 +518,7 @@ function condition_header(sys::AbstractSystem, integrator = gensym(:MTKIntegrato end """ - compile_condition(cb::SymbolicDiscreteCallback, sys, dvs, ps; expression, kwargs...) + compile_condition(cb::AbstractCallback, sys, dvs, ps; expression, kwargs...) Returns a function `condition(u,t,integrator)` returning the `condition(cb)`. @@ -434,12 +528,12 @@ Notes If set to `Val{false}` a `RuntimeGeneratedFunction` will be returned. - `kwargs` are passed through to `Symbolics.build_function`. """ -function compile_condition(cb::SymbolicDiscreteCallback, sys, dvs, ps; +function compile_condition(cb::AbstractCallback, sys, dvs, ps; expression = Val{true}, eval_expression = false, eval_module = @__MODULE__, kwargs...) u = map(value, dvs) p = map.(value, reorder_parameters(sys, ps)) t = get_iv(sys) - condit = condition(cb) + condit = conditions(cb) cs = collect_constants(condit) if !isempty(cs) cmap = map(x -> x => getdefault(x), cs) @@ -490,17 +584,20 @@ function compile_functional_affect(affect::FunctionalAffect, cb, sys, dvs, ps; k end end +is_discrete(cb::AbstractCallback) = cb isa SymbolicDiscreteCallback + """ Codegen a DifferentialEquations callback. A set of continuous callbacks becomes a VectorContinuousCallback. Individual callbacks become DiscreteCallback, PresetTimeCallback, PeriodicCallback, or ContinuousCallback depending on the case. """ -function generate_callback(cbs::Vector{SymbolicContinuousCallback}, sys; is_discrete = false) - is_discrete && error() +function generate_callback(cbs::Vector{SymbolicContinuousCallback}, sys; kwargs...) + length(cbs) == 1 && return generate_callback(only(cbs), sys) eqs = map(cb -> flatten_equations(cb.eqs), cbs) + _, f_iip = generate_custom_function( sys, [eq.lhs - eq.rhs for eq in eqs], unknowns(sys), parameters(sys); - expression = Val{false}) + expression = Val{false}, kwargs...) trigger = (out, u, t, integ) -> f_iip(out, u, parameter_values(integ), t) affects = [] @@ -509,12 +606,9 @@ function generate_callback(cbs::Vector{SymbolicContinuousCallback}, sys; is_disc finals = [] for cb in cbs affect = compile_affect(cb.affect) - push!(affects, affect) - - isnothing(cb.affect_neg) ? - push!(affect_negs, affect) : - push!(affect_negs, compile_affect(cb.affect_neg)) + push!(affects, affect) + push!(affect_negs, compile_affect(cb.affect_neg, default = affect)) push!(inits, compile_affect(cb.initialize, default = SciMLBase.INITALIZE_DEFAULT)) push!(finals, compile_affect(cb.finalize, default = SciMLBase.FINALIZE_DEFAULT)) end @@ -538,28 +632,21 @@ function generate_callback(cbs::Vector{SymbolicContinuousCallback}, sys; is_disc initialize = compile_vector_optional_affect(inits, SciMLBase.INITIALIZE_DEFAULT) finalize = compile_vector_optional_affect(finals, SciMLBase.FINALIZE_DEFAULT) - return VectorContinuousCallback(trigger, affect; affect_neg, initialize, finalize, rootfind = callback.rootfind, initializealg = SciMLBase.NoInit) + return VectorContinuousCallback(trigger, affect, length(cbs); affect_neg, initialize, finalize, rootfind = callback.rootfind, initializealg = SciMLBase.NoInit) end -function generate_callback(cb, sys; is_discrete = false) +function generate_callback(cb, sys; kwargs...) is_timed = is_timed_condition(conditions(cb)) + dvs = unknowns(sys) + ps = parameters(sys; initial_parameters = true) - trigger = if is_discrete - is_timed ? condition(cb) : - compile_condition(callback, sys, unknowns(sys), parameters(sys)) - else - _, f_iip = generate_custom_function( - sys, [eq.rhs - eq.lhs for eq in equations(cb)], unknowns(sys), parameters(sys); - expression = Val{false}) - (out, u, t, integ) -> f_iip(out, u, parameter_values(integ), t) - end - + trigger = is_timed ? conditions(cb) : compile_condition(cb, sys, dvs, ps; kwargs...) affect = compile_affect(cb.affect) - affect_neg = isnothing(cb.affect_neg) ? affect_fn : compile_affect(cb.affect_neg) + affect_neg = hasfield(cb, :affect_neg) ? compile_affect(cb.affect_neg, default = affect) : nothing initialize = compile_affect(cb.initialize, default = SciMLBase.INITIALIZE_DEFAULT) finalize = compile_affect(cb.finalize, default = SciMLBase.FINALIZE_DEFAULT) - if is_discrete + if is_discrete(cb) if is_timed && condition(cb) isa AbstractVector return PresetTimeCallback(trigger, affect; affect_neg, initialize, finalize, initializealg = SciMLBase.NoInit) elseif is_timed @@ -568,7 +655,7 @@ function generate_callback(cb, sys; is_discrete = false) return DiscreteCallback(trigger, affect; affect_neg, initialize, finalize, initializealg = SciMLBase.NoInit) end else - return ContinuousCallback(trigger, affect; affect_neg, initialize, finalize, rootfind = callback.rootfind, initializealg = SciMLBase.NoInit) + return ContinuousCallback(trigger, affect; affect_neg, initialize, finalize, rootfind = cb.rootfind, initializealg = SciMLBase.NoInit) end end @@ -597,16 +684,21 @@ function compile_affect(aff::Affect, cb::AbstractCallback, sys::AbstractSystem; isnothing(aff) && return default - ps = parameters(aff) + ps = parameters(aff; initial_parameters = true) dvs = unknowns(aff) if aff isa ImplicitDiscreteSystem function affect!(integrator) - u0map = [u => integrator[u] for u in dvs] - prob = ImplicitDiscreteProblem(aff, u0map, (0, 1), []) - sol = solve(prob) + pmap = [] + for pre_p in ps + p = only(arguments(unwrap(pre_p))) + push!(pmap, pre_p => integrator[p]) + end + guesses = [u => integrator[u] for u in dvs] + prob = ImplicitDiscreteProblem(aff, [], (0, 1), pmap; guesses) + sol = init(prob, SimpleIDSolve()) for u in dvs - integrator[u] = sol[u][end] + integrator[u] = sol[u] end for idx in save_idxs @@ -614,7 +706,7 @@ function compile_affect(aff::Affect, cb::AbstractCallback, sys::AbstractSystem; end end elseif aff isa FunctionalAffect || aff isa ImperativeAffect - compile_functional_affect(aff, callback, sys, dvs, ps) + compile_functional_affect(aff, callback, sys, dvs, ps; kwargs...) end end @@ -637,20 +729,25 @@ merge_cb(::Nothing, x) = merge_cb(x, nothing) merge_cb(x, ::Nothing) = x merge_cb(x, y) = CallbackSet(x, y) +""" +Generate the CallbackSet for a ODESystem or SDESystem. +""" function process_events(sys; callback = nothing, kwargs...) if has_continuous_events(sys) && !isempty(continuous_events(sys)) - contin_cb = generate_callback(sys; kwargs...) + cbs = continuous_events(sys) + contin_cbs = generate_callback(cbs, sys; kwargs...) else - contin_cb = nothing + contin_cbs = nothing end if has_discrete_events(sys) && !isempty(discrete_events(sys)) - discrete_cb = generate_callback(sys; is_discrete = true, kwargs...) + dbs = discrete_events(sys) + discrete_cbs = [generate_callback(db, sys; kwargs...) for db in dbs] else - discrete_cb = nothing + discrete_cbs = nothing end - cb = merge_cb(contin_cb, callback) - (discrete_cb === nothing) ? cb : CallbackSet(contin_cb, discrete_cb...) + cb = merge_cb(contin_cbs, callback) + (discrete_cbs === nothing) ? cb : CallbackSet(contin_cbs, discrete_cbs...) end """ diff --git a/src/systems/diffeqs/odesystem.jl b/src/systems/diffeqs/odesystem.jl index 67b7164806..5de296c398 100644 --- a/src/systems/diffeqs/odesystem.jl +++ b/src/systems/diffeqs/odesystem.jl @@ -337,8 +337,8 @@ function ODESystem(deqs::AbstractVector{<:Equation}, iv, dvs, ps; end algeeqs = filter(is_alg_equation, deqs) - cont_callbacks = generate_continuous_callbacks(continuous_events, algeeqs) - disc_callbacks = generate_discrete_callbacks(discrete_events, algeeqs) + cont_callbacks = SymbolicContinuousCallbacks(continuous_events, algeeqs, iv) + disc_callbacks = SymbolicDiscreteCallbacks(discrete_events, algeeqs, iv) if is_dde === nothing is_dde = _check_if_dde(deqs, iv′, systems) diff --git a/src/systems/imperative_affect.jl b/src/systems/imperative_affect.jl index a58c608233..0b578f55c5 100644 --- a/src/systems/imperative_affect.jl +++ b/src/systems/imperative_affect.jl @@ -110,7 +110,7 @@ function namespace_affect(affect::ImperativeAffect, s) end function compile_affect(affect::ImperativeAffect, cb, sys, dvs, ps; kwargs...) - compile_user_affect(affect, cb, sys, dvs, ps; kwargs...) + compile_functional_affect(affect, cb, sys, dvs, ps; kwargs...) end function invalid_variables(sys, expr) @@ -156,7 +156,7 @@ function check_assignable(sys, sym) end -function compile_user_affect(affect::ImperativeAffect, cb, sys, dvs, ps; kwargs...) +function compile_functional_affect(affect::ImperativeAffect, cb, sys, dvs, ps; kwargs...) #= Implementation sketch: generate observed function (oop), should save to a component array under obs_syms From 60d53694b17568492bd6314c923f1da06ea4ebcd Mon Sep 17 00:00:00 2001 From: vyudu Date: Tue, 11 Mar 2025 18:19:58 -0400 Subject: [PATCH 032/122] refactor: correct condition generation in --- src/systems/callbacks.jl | 348 ++++++++++------- src/systems/diffeqs/odesystem.jl | 4 +- src/systems/imperative_affect.jl | 2 - src/systems/index_cache.jl | 2 + test/symbolic_events.jl | 634 +++++++++++++------------------ 5 files changed, 481 insertions(+), 509 deletions(-) diff --git a/src/systems/callbacks.jl b/src/systems/callbacks.jl index df4763a0b7..b25d64d59c 100644 --- a/src/systems/callbacks.jl +++ b/src/systems/callbacks.jl @@ -62,6 +62,26 @@ function vars!(vars, aff::FunctionalAffect; op = Differential) return vars end +struct AffectSystem + system::ImplicitDiscreteSystem + unknowns::Vector + parameters::Vector + discretes::Vector + """Maps the unknowns in the ImplicitDiscreteSystem to the corresponding parameter or unknown in the parent system.""" + affu_to_sysu::Dict +end + +system(a::AffectSystem) = a.system +discretes(a::AffectSystem) = a.discretes +unknowns(a::AffectSystem) = a.unknowns +parameters(a::AffectSystem) = a.parameters +affu_to_sysu(a::AffectSystem) = a.affu_to_sysu + +function Base.show(iio::IO, aff::AffectSystem) + eqs = vcat(equations(system(aff)), observed(system(aff))) + show(iio, eqs) +end + """ Pre(x) @@ -77,7 +97,7 @@ Base.show(io::IO, x::Pre) = print(io, "Pre") input_timedomain(::Pre, _ = nothing) = ContinuousClock() output_timedomain(::Pre, _ = nothing) = ContinuousClock() -function (p::Pre)(x) +function (p::Pre)(x) iw = Symbolics.iswrapped(x) x = unwrap(x) # non-symbolic values don't change @@ -115,7 +135,7 @@ haspre(O) = recursive_hasoperator(Pre, O) ############################### ###### Continuous events ###### ############################### -const Affect = Union{ImplicitDiscreteSystem, FunctionalAffect, ImperativeAffect} +const Affect = Union{AffectSystem, FunctionalAffect, ImperativeAffect} """ SymbolicContinuousCallback(eqs::Vector{Equation}, affect, affect_neg, rootfind) @@ -172,63 +192,87 @@ struct SymbolicContinuousCallback <: AbstractCallback rootfind::Union{Nothing, SciMLBase.RootfindOpt} function SymbolicContinuousCallback( - conditions::Vector{Equation}, + conditions::Union{Equation, Vector{Equation}}, affect = nothing; affect_neg = affect, initialize = nothing, finalize = nothing, rootfind = SciMLBase.LeftRootFind) - new(eqs, initialize, finalize, make_affect(affect), - make_affect(affect_neg), rootfind) + + conditions = (conditions isa AbstractVector) ? conditions : [conditions] + new(conditions, make_affect(affect), make_affect(affect_neg), + initialize, finalize, rootfind) end # Default affect to nothing end -make_affect(affect::Tuple, iv) = FunctionalAffect(affects...) -make_affect(affect::NamedTuple, iv) = FunctionalAffect(; affects...) -make_affect(affect::FunctionalAffect, iv) = affect +SymbolicContinuousCallback(p::Pair) = SymbolicContinuousCallback(p[1], p[2]) +SymbolicContinuousCallback(cb::SymbolicContinuousCallback, args...) = cb + +make_affect(affect::Nothing) = nothing +make_affect(affect::Tuple) = FunctionalAffect(affects...) +make_affect(affect::NamedTuple) = FunctionalAffect(; affects...) +make_affect(affect::FunctionalAffect) = affect +make_affect(affect::AffectSystem) = affect -function make_affect(affect::Vector{Equation}, iv; warn = true) +function make_affect(affect::Vector{Equation}; warn = true) affect = scalarize(affect) unknowns = OrderedSet() params = OrderedSet() + for eq in affect - !haspre(eq) && warn && @warn "Equation $eq has no `Pre` operator. As such it will be interpreted as an algebraic equation to be satisfied after the callback. - If you intended to use the value of a variable x before the affect, use Pre(x)." - collect_vars!(unknowns, params, eq, iv; op = Pre) + !haspre(eq) && warn && + @warn "Equation $eq has no `Pre` operator. As such it will be interpreted as an algebraic equation to be satisfied after the callback. If you intended to use the value of a variable x before the affect, use Pre(x)." + collect_vars!(unknowns, params, eq, nothing; op = Pre) end + iv = isempty(unknowns) ? t_nounits : only(arguments(unknowns[1])) - # System parameters should become unknowns. - cb_params = OrderedSet() - sys_params = OrderedSet() + # System parameters should become unknowns in the ImplicitDiscreteSystem. + cb_params = Any[] + discretes = Any[] + p_as_unknowns = Any[] for p in params if iscall(p) && (operator(p) isa Pre) push!(cb_params, p) + elseif iscall(p) && length(arguments(p)) == 1 && + isequal(only(arguments(p)), iv) + push!(discretes, p) + push!(p_as_unknowns, tovar(p)) else - p = Sym{FnType{Tuple{symtype(iv)}, Real}}(nameof(p))(iv) - p = wrap(tovar(p)) - push!(sys_params, p) + push!(discretes, p) + p = iscall(p) ? wrap(Sym{FnType{Tuple{symtype(iv)}, Real}}(nameof(operation(p)))(iv)) : + wrap(Sym{FnType{Tuple{symtype(iv)}, Real}}(nameof(p))(iv)) + push!(p_as_unknowns, p) end end - - @mtkbuild affect = ImplicitDiscreteSystem(affect, iv, vcat(unknowns, sys_params), cb_params) + @mtkbuild affectsys = ImplicitDiscreteSystem( + affect, iv, collect(union(unknowns, p_as_unknowns)), cb_params) + params = map(x -> only(arguments(unwrap(x))), cb_params) + affmap = Dict(zip([p_as_unknowns, unknowns], [discretes, unknowns])) + + return AffectSystem(affectsys, collect(unknowns), params, discretes, affmap) end -make_affect(affect, iv) = error("Malformed affect $(affect). This should be a vector of equations or a tuple specifying a functional affect.") +function make_affect(affect) + error("Malformed affect $(affect). This should be a vector of equations or a tuple specifying a functional affect.") +end """ Generate continuous callbacks. -""" -function SymbolicContinuousCallbacks(events, algeeqs, iv) +""" +function SymbolicContinuousCallbacks(events, algeeqs::Vector{Equation} = Equation[]) callbacks = SymbolicContinuousCallback[] - (isnothing(events) || isempty(events)) && return callbacks + isnothing(events) && return callbacks events isa AbstractVector || (events = [events]) - for (cond, affs) in events + isempty(events) && return callbacks + + for event in events + cond, affs = event isa Pair ? (event[1], event[2]) : (event, nothing) if affs isa AbstractVector affs = vcat(affs, algeeqs) end - affect = make_affect(affs, iv) - push!(callbacks, SymbolicContinuousCallback(cond, affect, affect, nothing, nothing, SciMLBase.LeftRootFind)) + affect = make_affect(affs) + push!(callbacks, SymbolicContinuousCallback(cond, affect)) end callbacks end @@ -240,22 +284,22 @@ function Base.show(io::IO, cb::SymbolicContinuousCallback) print(iio, "Equations:") show(iio, equations(cb)) print(iio, "; ") - if affects(cb) != NULL_AFFECT + if affects(cb) != nothing print(iio, "Affect:") show(iio, affects(cb)) print(iio, ", ") end - if affect_negs(cb) != NULL_AFFECT + if affect_negs(cb) != nothing print(iio, "Negative-edge affect:") show(iio, affect_negs(cb)) print(iio, ", ") end - if initialize_affects(cb) != NULL_AFFECT + if initialize_affects(cb) != nothing print(iio, "Initialization affect:") show(iio, initialize_affects(cb)) print(iio, ", ") end - if finalize_affects(cb) != NULL_AFFECT + if finalize_affects(cb) != nothing print(iio, "Finalization affect:") show(iio, finalize_affects(cb)) end @@ -269,22 +313,22 @@ function Base.show(io::IO, mime::MIME"text/plain", cb::SymbolicContinuousCallbac println(iio, "Equations:") show(iio, mime, equations(cb)) print(iio, "\n") - if affects(cb) != NULL_AFFECT + if affects(cb) != nothing println(iio, "Affect:") show(iio, mime, affects(cb)) print(iio, "\n") end - if affect_negs(cb) != NULL_AFFECT - println(iio, "Negative-edge affect:") + if affect_negs(cb) != nothing + print(iio, "Negative-edge affect:\n") show(iio, mime, affect_negs(cb)) print(iio, "\n") end - if initialize_affects(cb) != NULL_AFFECT + if initialize_affects(cb) != nothing println(iio, "Initialization affect:") show(iio, mime, initialize_affects(cb)) print(iio, "\n") end - if finalize_affects(cb) != NULL_AFFECT + if finalize_affects(cb) != nothing println(iio, "Finalization affect:") show(iio, mime, finalize_affects(cb)) print(iio, "\n") @@ -322,7 +366,7 @@ The condition can be one of: - ts::Vector{Real} - events trigger at these preset times given by `ts` - eqs::Vector{Equation} - events trigger when the condition evaluates to true """ -struct SymbolicDiscreteCallback <: AbstractCallback +struct SymbolicDiscreteCallback <: AbstractCallback conditions::Any affect::Affect initialize::Union{Affect, Nothing} @@ -340,22 +384,25 @@ end """ Generate discrete callbacks. """ -function SymbolicDiscreteCallbacks(events, algeeqs, iv) +function SymbolicDiscreteCallbacks(events, algeeqs::Vector{Equation} = Equation[]) callbacks = SymbolicDiscreteCallback[] - (isnothing(events) || isempty(events)) && return callbacks + + isnothing(events) && return callbacks events isa AbstractVector || (events = [events]) + isempty(events) && return callbacks - for (cond, aff) in events + for event in events + cond, affs = event isa Pair ? (event[1], event[2]) : (event, nothing) if aff isa AbstractVector aff = vcat(aff, algeeqs) end - affect = make_affect(aff, iv) + affect = make_affect(aff) push!(callbacks, SymbolicDiscreteCallback(cond, affect, nothing, nothing)) end callbacks end -function is_timed_condition(condition::T) where T +function is_timed_condition(condition::T) where {T} if T <: Real true elseif T <: AbstractVector @@ -371,17 +418,17 @@ function Base.show(io::IO, db::SymbolicDiscreteCallback) println(io, "SymbolicDiscreteCallback:") println(iio, "Conditions:") print(iio, "; ") - if affects(db) != nothing + if affects(db) != nothing print(iio, "Affect:") show(iio, affects(db)) print(iio, ", ") end - if initialize_affects(db) != nothing + if initialize_affects(db) != nothing print(iio, "Initialization affect:") show(iio, initialize_affects(db)) print(iio, ", ") end - if finalize_affects(db) != nothing + if finalize_affects(db) != nothing print(iio, "Finalization affect:") show(iio, finalize_affects(db)) end @@ -424,24 +471,17 @@ function namespace_affect(affect::FunctionalAffect, s) context(affect)) end -function namespace_affects(af::Affect, s) - if af isa ImplicitDiscreteSystem - af - elseif af isa FunctionalAffect || af isa ImperativeAffect - namespace_affect(af, s) - else - nothing - end -end +namespace_affect(affect::AffectSystem, s) = AffectSystem(system(affect), renamespace.((s,), discretes(affect))) +namespace_affects(af::Union{Nothing, Affect}, s) = af isa Affect ? namespace_affect(af, s) : nothing function namespace_callback(cb::SymbolicContinuousCallback, s)::SymbolicContinuousCallback SymbolicContinuousCallback( namespace_equation.(equations(cb), (s,)), namespace_affects(affects(cb), s), - namespace_affects(affect_negs(cb), s), - namespace_affects(initialize_affects(cb), s), - namespace_affects(finalize_affects(cb), s), - cb.rootfind) + affect_neg = namespace_affects(affect_negs(cb), s), + initialize = namespace_affects(initialize_affects(cb), s), + finalize = namespace_affects(finalize_affects(cb), s), + rootfind = cb.rootfind) end function namespace_condition(condition, s) @@ -450,7 +490,7 @@ end function namespace_callback(cb::SymbolicDiscreteCallback, s)::SymbolicDiscreteCallback SymbolicDiscreteCallback( - namespace_condition(condition(cb), s), + namespace_condition(condition(cb), s), namespace_affects(affects(cb), s), namespace_affects(initialize_affects(cb), s), namespace_affects(finalize_affects(cb), s)) @@ -477,29 +517,39 @@ end ########################### conditions(cb::AbstractCallback) = cb.conditions -conditions(cbs::Vector{<:AbstractCallback}) = reduce(vcat, conditions(cb) for cb in cbs; init = []) +function conditions(cbs::Vector{<:AbstractCallback}) + reduce(vcat, conditions(cb) for cb in cbs; init = []) +end equations(cb::AbstractCallback) = conditions(cb) equations(cb::Vector{<:AbstractCallback}) = conditions(cb) affects(cb::AbstractCallback) = cb.affect -affects(cbs::Vector{<:AbstractCallback}) = reduce(vcat, affects(cb) for cb in cbs; init = []) +function affects(cbs::Vector{<:AbstractCallback}) + reduce(vcat, affects(cb) for cb in cbs; init = []) +end affect_negs(cb::SymbolicContinuousCallback) = cb.affect_neg -affect_negs(cbs::Vector{SymbolicContinuousCallback}) = reduce(vcat, affect_negs(cb) for cb in cbs; init = []) +function affect_negs(cbs::Vector{SymbolicContinuousCallback}) + reduce(vcat, affect_negs(cb) for cb in cbs; init = []) +end initialize_affects(cb::AbstractCallback) = cb.initialize -initialize_affects(cbs::Vector{<:AbstractCallback}) = reduce(initialize_affects, vcat, cbs; init = []) +function initialize_affects(cbs::Vector{<:AbstractCallback}) + reduce(initialize_affects, vcat, cbs; init = []) +end finalize_affects(cb::AbstractCallback) = cb.finalize -finalize_affects(cbs::Vector{<:AbstractCallback}) = reduce(finalize_affects, vcat, cbs; init = []) +function finalize_affects(cbs::Vector{<:AbstractCallback}) + reduce(finalize_affects, vcat, cbs; init = []) +end function Base.:(==)(e1::SymbolicDiscreteCallback, e2::SymbolicDiscreteCallback) - isequal(e1.condition, e2.condition) && isequal(e1.affects, e2.affects) && + isequal(e1.conditions, e2.conditions) && isequal(e1.affects, e2.affects) && isequal(e1.initialize, e2.initialize) && isequal(e1.finalize, e2.finalize) end function Base.:(==)(e1::SymbolicContinuousCallback, e2::SymbolicContinuousCallback) - isequal(e1.eqs, e2.eqs) && isequal(e1.affect, e2.affect) && + isequal(e1.conditions, e2.conditions) && isequal(e1.affect, e2.affect) && isequal(e1.initialize, e2.initialize) && isequal(e1.finalize, e2.finalize) && isequal(e1.affect_neg, e2.affect_neg) && isequal(e1.rootfind, e2.rootfind) end @@ -509,18 +559,10 @@ Base.isempty(cb::AbstractCallback) = isempty(cb.conditions) #################################### ####### Compilation functions ###### #################################### -function condition_header(sys::AbstractSystem, integrator = gensym(:MTKIntegrator)) - expr -> Func( - [expr.args[1], expr.args[2], - DestructuredArgs(expr.args[3:end], integrator, inds = [:p])], - [], - expr.body) -end - """ compile_condition(cb::AbstractCallback, sys, dvs, ps; expression, kwargs...) -Returns a function `condition(u,t,integrator)` returning the `condition(cb)`. +Returns a function `condition(u,t,integrator)`, condition(out,u,t,integrator)` returning the `condition(cb)`. Notes @@ -528,26 +570,40 @@ Notes If set to `Val{false}` a `RuntimeGeneratedFunction` will be returned. - `kwargs` are passed through to `Symbolics.build_function`. """ -function compile_condition(cb::AbstractCallback, sys, dvs, ps; - expression = Val{true}, eval_expression = false, eval_module = @__MODULE__, kwargs...) - u = map(value, dvs) - p = map.(value, reorder_parameters(sys, ps)) +function compile_condition(cbs::Union{AbstractCallback, Vector{<:AbstractCallback}}, sys, dvs, ps; + expression = Val{false}, eval_expression = false, eval_module = @__MODULE__, kwargs...) + u = map(x -> time_varying_as_func(value(x), sys), dvs) + p = map.(x -> time_varying_as_func(value(x), sys), reorder_parameters(sys, ps)) t = get_iv(sys) - condit = conditions(cb) + condit = conditions(cbs) cs = collect_constants(condit) if !isempty(cs) cmap = map(x -> x => getdefault(x), cs) condit = substitute(condit, cmap) end - expr = build_function_wrapper(sys, - condit, u, t, p...; expression = Val{true}, - p_start = 3, p_end = length(p) + 2, - wrap_code = condition_header(sys), - kwargs...) - if expression == Val{true} - return expr + + f_oop, f_iip = build_function_wrapper(sys, + condit, u, t, p...; expression = Val{true}, + p_start = 3, p_end = length(p) + 2, + kwargs...) + + if cbs isa AbstractVector + cond(out, u, t, integ) = f_iip(out, u, t, parameter_values(integ)) + elseif is_discrete(cbs) + cond(u, t, integ) = f_oop(u, t, parameter_values(integ)) + else + cond = function (u, t, integ) + if DiffEqBase.isinplace(integ.sol.prob) + tmp, = DiffEqBase.get_tmp_cache(integ) + f_iip(tmp, u, t, parameter_values(integ)) + tmp[1] + else + f_oop(u, t, parameter_values(integ)) + end + end end - return eval_or_rgf(expr; eval_expression, eval_module) + + cond end """ @@ -558,8 +614,9 @@ function compile_functional_affect(affect::FunctionalAffect, cb, sys, dvs, ps; k v_inds = map(sym -> dvs_ind[sym], unknowns(affect)) if has_index_cache(sys) && get_index_cache(sys) !== nothing - p_inds = [(pind = parameter_index(sys, sym)) === nothing ? sym : pind for sym in parameters(affect)] - save_idxs = get(ic. callback_to_clocks, cb, Int[]) + p_inds = [(pind = parameter_index(sys, sym)) === nothing ? sym : pind + for sym in parameters(affect)] + save_idxs = get(ic.callback_to_clocks, cb, Int[]) else ps_ind = Dict(reverse(en) for en in enumerate(ps)) p_inds = map(sym -> get(ps_ind, sym, sym), parameters(affect)) @@ -574,7 +631,6 @@ function compile_functional_affect(affect::FunctionalAffect, cb, sys, dvs, ps; k let u = u, p = p, user_affect = func(affect), ctx = context(affect), save_idxs = save_idxs - function (integ) user_affect(integ, u, p, ctx) for idx in save_idxs @@ -586,31 +642,44 @@ end is_discrete(cb::AbstractCallback) = cb isa SymbolicDiscreteCallback +function generate_continuous_callbacks(sys::AbstractSystem, dvs = unknowns(sys), ps = parameters(sys; initial_parameters = true); kwargs...) + cbs = continuous_events(sys) + isempty(cbs) && return nothing + generate_callback(cbs, sys; kwargs...) +end + +function generate_discrete_callbacks(sys::AbstractSystem, dvs = unknowns(sys), ps = parameters(sys; initial_parameters = true); kwargs...) + dbs = discrete_events(sys) + isempty(dbs) && return nothing + [generate_callback(db, sys; kwargs...) for db in dbs] +end + """ -Codegen a DifferentialEquations callback. A set of continuous callbacks becomes a VectorContinuousCallback. -Individual callbacks become DiscreteCallback, PresetTimeCallback, PeriodicCallback, or ContinuousCallback -depending on the case. +Codegen a DifferentialEquations callback. A (set of) continuous callback with multiple equations becomes a VectorContinuousCallback. +Continuous callbacks with only one equation will become a ContinuousCallback. +Individual discrete callbacks become DiscreteCallback, PresetTimeCallback, PeriodicCallback depending on the case. """ function generate_callback(cbs::Vector{SymbolicContinuousCallback}, sys; kwargs...) - length(cbs) == 1 && return generate_callback(only(cbs), sys) - eqs = map(cb -> flatten_equations(cb.eqs), cbs) - - _, f_iip = generate_custom_function( - sys, [eq.lhs - eq.rhs for eq in eqs], unknowns(sys), parameters(sys); - expression = Val{false}, kwargs...) - trigger = (out, u, t, integ) -> f_iip(out, u, parameter_values(integ), t) - + eqs = map(cb -> flatten_equations(equations(cb)), cbs) + num_eqs = length.(eqs) + (isempty(eqs) || sum(num_eqs) == 0) && return nothing + if sum(num_eqs) == 1 + cb_ind = findfirst(>(0), num_eqs) + return generate_callback(cbs[cb_ind], sys; kwargs...) + end + + trigger = compile_condition(cbs, sys, dvs, ps; kwargs...) affects = [] affect_negs = [] inits = [] finals = [] for cb in cbs - affect = compile_affect(cb.affect) + affect = compile_affect(cb.affect, cb, sys) push!(affects, affect) - push!(affect_negs, compile_affect(cb.affect_neg, default = affect)) - push!(inits, compile_affect(cb.initialize, default = SciMLBase.INITALIZE_DEFAULT)) - push!(finals, compile_affect(cb.finalize, default = SciMLBase.FINALIZE_DEFAULT)) + push!(affect_negs, compile_affect(cb.affect_neg, cb, sys, default = affect)) + push!(inits, compile_affect(cb.initialize, cb, sys, default = SciMLBase.INITALIZE_DEFAULT)) + push!(finals, compile_affect(cb.finalize, cb, sys, default = SciMLBase.FINALIZE_DEFAULT)) end # Since there may be different number of conditions and affects, @@ -632,7 +701,9 @@ function generate_callback(cbs::Vector{SymbolicContinuousCallback}, sys; kwargs. initialize = compile_vector_optional_affect(inits, SciMLBase.INITIALIZE_DEFAULT) finalize = compile_vector_optional_affect(finals, SciMLBase.FINALIZE_DEFAULT) - return VectorContinuousCallback(trigger, affect, length(cbs); affect_neg, initialize, finalize, rootfind = callback.rootfind, initializealg = SciMLBase.NoInit) + return VectorContinuousCallback( + trigger, affect, length(cbs); affect_neg, initialize, finalize, + rootfind = callback.rootfind, initializealg = SciMLBase.NoInit) end function generate_callback(cb, sys; kwargs...) @@ -641,21 +712,25 @@ function generate_callback(cb, sys; kwargs...) ps = parameters(sys; initial_parameters = true) trigger = is_timed ? conditions(cb) : compile_condition(cb, sys, dvs, ps; kwargs...) - affect = compile_affect(cb.affect) - affect_neg = hasfield(cb, :affect_neg) ? compile_affect(cb.affect_neg, default = affect) : nothing - initialize = compile_affect(cb.initialize, default = SciMLBase.INITIALIZE_DEFAULT) - finalize = compile_affect(cb.finalize, default = SciMLBase.FINALIZE_DEFAULT) + affect = compile_affect(cb.affect, cb, sys) + affect_neg = hasfield(typeof(cb), :affect_neg) ? + compile_affect(cb.affect_neg, cb, sys, default = affect) : nothing + initialize = compile_affect(cb.initialize, cb, sys, default = SciMLBase.INITIALIZE_DEFAULT) + finalize = compile_affect(cb.finalize, cb, sys, default = SciMLBase.FINALIZE_DEFAULT) if is_discrete(cb) if is_timed && condition(cb) isa AbstractVector - return PresetTimeCallback(trigger, affect; affect_neg, initialize, finalize, initializealg = SciMLBase.NoInit) + return PresetTimeCallback(trigger, affect; affect_neg, initialize, + finalize, initializealg = SciMLBase.NoInit) elseif is_timed return PeriodicCallback(affect, trigger; initialize, finalize) else - return DiscreteCallback(trigger, affect; affect_neg, initialize, finalize, initializealg = SciMLBase.NoInit) + return DiscreteCallback(trigger, affect; initialize, + finalize, initializealg = SciMLBase.NoInit) end else - return ContinuousCallback(trigger, affect; affect_neg, initialize, finalize, rootfind = cb.rootfind, initializealg = SciMLBase.NoInit) + return ContinuousCallback(trigger, affect, affect_neg; initialize, finalize, + rootfind = cb.rootfind, initializealg = SciMLBase.NoInit) end end @@ -675,7 +750,8 @@ Notes well-formed. - `kwargs` are passed through to `Symbolics.build_function`. """ -function compile_affect(aff::Affect, cb::AbstractCallback, sys::AbstractSystem; default = nothing) +function compile_affect( + aff::Union{Nothing, Affect}, cb::AbstractCallback, sys::AbstractSystem; default = nothing) save_idxs = if !(has_index_cache(sys) && (ic = get_index_cache(sys)) !== nothing) Int[] else @@ -684,21 +760,22 @@ function compile_affect(aff::Affect, cb::AbstractCallback, sys::AbstractSystem; isnothing(aff) && return default - ps = parameters(aff; initial_parameters = true) + ps = parameters(aff) dvs = unknowns(aff) - if aff isa ImplicitDiscreteSystem - function affect!(integrator) - pmap = [] - for pre_p in ps + if aff isa AffectSystem + aff_map = affu_to_sysu(aff) + function affect!(integrator) + pmap = [] + for pre_p in parameters(system(affect)) p = only(arguments(unwrap(pre_p))) push!(pmap, pre_p => integrator[p]) end - guesses = [u => integrator[u] for u in dvs] - prob = ImplicitDiscreteProblem(aff, [], (0, 1), pmap; guesses) + guesses = [u => integrator[aff_map[u]] for u in unknowns(system(affect))] + prob = ImplicitDiscreteProblem(system(affect), [], (0, 1), pmap; guesses) sol = init(prob, SimpleIDSolve()) - for u in dvs - integrator[u] = sol[u] + for u in unknowns(system(affect)) + integrator[aff_map[u]] = sol[u] end for idx in save_idxs @@ -706,7 +783,7 @@ function compile_affect(aff::Affect, cb::AbstractCallback, sys::AbstractSystem; end end elseif aff isa FunctionalAffect || aff isa ImperativeAffect - compile_functional_affect(aff, callback, sys, dvs, ps; kwargs...) + compile_functional_affect(aff, cb, sys, dvs, ps; kwargs...) end end @@ -717,9 +794,9 @@ function compile_vector_optional_affect(funs, default) all(isnothing, funs) && return default return let funs = funs function (cb, u, t, integ) - for func in funs - isnothing(func) ? continue : func(integ) - end + for func in funs + isnothing(func) ? continue : func(integ) + end end end end @@ -733,19 +810,8 @@ merge_cb(x, y) = CallbackSet(x, y) Generate the CallbackSet for a ODESystem or SDESystem. """ function process_events(sys; callback = nothing, kwargs...) - if has_continuous_events(sys) && !isempty(continuous_events(sys)) - cbs = continuous_events(sys) - contin_cbs = generate_callback(cbs, sys; kwargs...) - else - contin_cbs = nothing - end - if has_discrete_events(sys) && !isempty(discrete_events(sys)) - dbs = discrete_events(sys) - discrete_cbs = [generate_callback(db, sys; kwargs...) for db in dbs] - else - discrete_cbs = nothing - end - + contin_cbs = generate_continuous_callbacks(sys; kwargs...) + discrete_cbs = generate_discrete_callbacks(sys; kwargs...) cb = merge_cb(contin_cbs, callback) (discrete_cbs === nothing) ? cb : CallbackSet(contin_cbs, discrete_cbs...) end diff --git a/src/systems/diffeqs/odesystem.jl b/src/systems/diffeqs/odesystem.jl index 5de296c398..a5aff93e7d 100644 --- a/src/systems/diffeqs/odesystem.jl +++ b/src/systems/diffeqs/odesystem.jl @@ -337,8 +337,8 @@ function ODESystem(deqs::AbstractVector{<:Equation}, iv, dvs, ps; end algeeqs = filter(is_alg_equation, deqs) - cont_callbacks = SymbolicContinuousCallbacks(continuous_events, algeeqs, iv) - disc_callbacks = SymbolicDiscreteCallbacks(discrete_events, algeeqs, iv) + cont_callbacks = SymbolicContinuousCallbacks(continuous_events, algeeqs) + disc_callbacks = SymbolicDiscreteCallbacks(discrete_events, algeeqs) if is_dde === nothing is_dde = _check_if_dde(deqs, iv′, systems) diff --git a/src/systems/imperative_affect.jl b/src/systems/imperative_affect.jl index 0b578f55c5..991a16a23a 100644 --- a/src/systems/imperative_affect.jl +++ b/src/systems/imperative_affect.jl @@ -155,7 +155,6 @@ function check_assignable(sys, sym) end end - function compile_functional_affect(affect::ImperativeAffect, cb, sys, dvs, ps; kwargs...) #= Implementation sketch: @@ -280,4 +279,3 @@ function vars!(vars, aff::ImperativeAffect; op = Differential) end return vars end - diff --git a/src/systems/index_cache.jl b/src/systems/index_cache.jl index d0b687c212..8f8bebad31 100644 --- a/src/systems/index_cache.jl +++ b/src/systems/index_cache.jl @@ -121,6 +121,8 @@ function IndexCache(sys::AbstractSystem) is_parameter(sys, affect.lhs) && push!(discs, affect.lhs) elseif affect isa FunctionalAffect || affect isa ImperativeAffect union!(discs, unwrap.(discretes(affect))) + elseif isnothing(affect) + continue else error("Unhandled affect type $(typeof(affect))") end diff --git a/test/symbolic_events.jl b/test/symbolic_events.jl index 9099d32d14..2b9da431a7 100644 --- a/test/symbolic_events.jl +++ b/test/symbolic_events.jl @@ -1,10 +1,11 @@ using ModelingToolkit, OrdinaryDiffEq, StochasticDiffEq, JumpProcesses, Test using SciMLStructures: canonicalize, Discrete using ModelingToolkit: SymbolicContinuousCallback, - SymbolicContinuousCallbacks, NULL_AFFECT, + SymbolicContinuousCallbacks, get_callback, t_nounits as t, - D_nounits as D + D_nounits as D, + affects, affect_negs, system, observed, AffectSystem using StableRNGs import SciMLBase using SymbolicIndexingInterface @@ -17,215 +18,110 @@ eqs = [D(x) ~ 1] affect = [x ~ 0] affect_neg = [x ~ 1] -## Test SymbolicContinuousCallback @testset "SymbolicContinuousCallback constructors" begin e = SymbolicContinuousCallback(eqs[]) @test e isa SymbolicContinuousCallback - @test isequal(e.eqs, eqs) - @test e.affect == NULL_AFFECT - @test e.affect_neg == NULL_AFFECT + @test isequal(equations(e), eqs) + @test e.affect == nothing + @test e.affect_neg == nothing @test e.rootfind == SciMLBase.LeftRootFind e = SymbolicContinuousCallback(eqs) @test e isa SymbolicContinuousCallback - @test isequal(e.eqs, eqs) - @test e.affect == NULL_AFFECT - @test e.affect_neg == NULL_AFFECT + @test isequal(equations(e), eqs) + @test e.affect == nothing + @test e.affect_neg == nothing @test e.rootfind == SciMLBase.LeftRootFind - e = SymbolicContinuousCallback(eqs, NULL_AFFECT) + e = SymbolicContinuousCallback(eqs, nothing) @test e isa SymbolicContinuousCallback - @test isequal(e.eqs, eqs) - @test e.affect == NULL_AFFECT - @test e.affect_neg == NULL_AFFECT + @test isequal(equations(e), eqs) + @test e.affect == nothing + @test e.affect_neg == nothing @test e.rootfind == SciMLBase.LeftRootFind - e = SymbolicContinuousCallback(eqs[], NULL_AFFECT) + e = SymbolicContinuousCallback(eqs[], nothing) @test e isa SymbolicContinuousCallback - @test isequal(e.eqs, eqs) - @test e.affect == NULL_AFFECT - @test e.affect_neg == NULL_AFFECT + @test isequal(equations(e), eqs) + @test e.affect == nothing + @test e.affect_neg == nothing @test e.rootfind == SciMLBase.LeftRootFind - e = SymbolicContinuousCallback(eqs => NULL_AFFECT) + e = SymbolicContinuousCallback(eqs => nothing) @test e isa SymbolicContinuousCallback - @test isequal(e.eqs, eqs) - @test e.affect == NULL_AFFECT - @test e.affect_neg == NULL_AFFECT + @test isequal(equations(e), eqs) + @test e.affect == nothing + @test e.affect_neg == nothing @test e.rootfind == SciMLBase.LeftRootFind - e = SymbolicContinuousCallback(eqs[] => NULL_AFFECT) + e = SymbolicContinuousCallback(eqs[] => nothing) @test e isa SymbolicContinuousCallback - @test isequal(e.eqs, eqs) - @test e.affect == NULL_AFFECT - @test e.affect_neg == NULL_AFFECT + @test isequal(equations(e), eqs) + @test e.affect == nothing + @test e.affect_neg == nothing @test e.rootfind == SciMLBase.LeftRootFind ## With affect - - e = SymbolicContinuousCallback(eqs[], affect) - @test e isa SymbolicContinuousCallback - @test isequal(e.eqs, eqs) - @test e.affect == affect - @test e.affect_neg == affect - @test e.rootfind == SciMLBase.LeftRootFind - - e = SymbolicContinuousCallback(eqs, affect) - @test e isa SymbolicContinuousCallback - @test isequal(e.eqs, eqs) - @test e.affect == affect - @test e.affect_neg == affect - @test e.rootfind == SciMLBase.LeftRootFind - - e = SymbolicContinuousCallback(eqs, affect) - @test e isa SymbolicContinuousCallback - @test isequal(e.eqs, eqs) - @test e.affect == affect - @test e.affect_neg == affect - @test e.rootfind == SciMLBase.LeftRootFind - e = SymbolicContinuousCallback(eqs[], affect) @test e isa SymbolicContinuousCallback - @test isequal(e.eqs, eqs) - @test e.affect == affect - @test e.affect_neg == affect - @test e.rootfind == SciMLBase.LeftRootFind - - e = SymbolicContinuousCallback(eqs => affect) - @test e isa SymbolicContinuousCallback - @test isequal(e.eqs, eqs) - @test e.affect == affect - @test e.affect_neg == affect - @test e.rootfind == SciMLBase.LeftRootFind - - e = SymbolicContinuousCallback(eqs[] => affect) - @test e isa SymbolicContinuousCallback - @test isequal(e.eqs, eqs) - @test e.affect == affect - @test e.affect_neg == affect + @test isequal(equations(e), eqs) + @test observed(system(affects(e))) == affect + @test observed(system(affect_negs(e))) == affect @test e.rootfind == SciMLBase.LeftRootFind # with only positive edge affect - - e = SymbolicContinuousCallback(eqs[], affect, affect_neg = nothing) - @test e isa SymbolicContinuousCallback - @test isequal(e.eqs, eqs) - @test e.affect == affect - @test isnothing(e.affect_neg) - @test e.rootfind == SciMLBase.LeftRootFind - - e = SymbolicContinuousCallback(eqs, affect, affect_neg = nothing) - @test e isa SymbolicContinuousCallback - @test isequal(e.eqs, eqs) - @test e.affect == affect - @test isnothing(e.affect_neg) - @test e.rootfind == SciMLBase.LeftRootFind - - e = SymbolicContinuousCallback(eqs, affect, affect_neg = nothing) - @test e isa SymbolicContinuousCallback - @test isequal(e.eqs, eqs) - @test e.affect == affect - @test isnothing(e.affect_neg) - @test e.rootfind == SciMLBase.LeftRootFind - e = SymbolicContinuousCallback(eqs[], affect, affect_neg = nothing) @test e isa SymbolicContinuousCallback - @test isequal(e.eqs, eqs) - @test e.affect == affect + @test isequal(equations(e), eqs) + @test observed(system(affects(e))) == affect @test isnothing(e.affect_neg) @test e.rootfind == SciMLBase.LeftRootFind # with explicit edge affects - - e = SymbolicContinuousCallback(eqs[], affect, affect_neg = affect_neg) - @test e isa SymbolicContinuousCallback - @test isequal(e.eqs, eqs) - @test e.affect == affect - @test e.affect_neg == affect_neg - @test e.rootfind == SciMLBase.LeftRootFind - - e = SymbolicContinuousCallback(eqs, affect, affect_neg = affect_neg) - @test e isa SymbolicContinuousCallback - @test isequal(e.eqs, eqs) - @test e.affect == affect - @test e.affect_neg == affect_neg - @test e.rootfind == SciMLBase.LeftRootFind - - e = SymbolicContinuousCallback(eqs, affect, affect_neg = affect_neg) - @test e isa SymbolicContinuousCallback - @test isequal(e.eqs, eqs) - @test e.affect == affect - @test e.affect_neg == affect_neg - @test e.rootfind == SciMLBase.LeftRootFind - e = SymbolicContinuousCallback(eqs[], affect, affect_neg = affect_neg) @test e isa SymbolicContinuousCallback - @test isequal(e.eqs, eqs) - @test e.affect == affect - @test e.affect_neg == affect_neg + @test isequal(equations(e), eqs) + @test observed(system(affects(e))) == affect + @test observed(system(affect_negs(e))) == affect_neg @test e.rootfind == SciMLBase.LeftRootFind # with different root finding ops - e = SymbolicContinuousCallback( eqs[], affect, affect_neg = affect_neg, rootfind = SciMLBase.LeftRootFind) @test e isa SymbolicContinuousCallback - @test isequal(e.eqs, eqs) - @test e.affect == affect - @test e.affect_neg == affect_neg + @test isequal(equations(e), eqs) @test e.rootfind == SciMLBase.LeftRootFind - e = SymbolicContinuousCallback( - eqs[], affect, affect_neg = affect_neg, rootfind = SciMLBase.RightRootFind) - @test e isa SymbolicContinuousCallback - @test isequal(e.eqs, eqs) - @test e.affect == affect - @test e.affect_neg == affect_neg - @test e.rootfind == SciMLBase.RightRootFind - - e = SymbolicContinuousCallback( - eqs[], affect, affect_neg = affect_neg, rootfind = SciMLBase.NoRootFind) - @test e isa SymbolicContinuousCallback - @test isequal(e.eqs, eqs) - @test e.affect == affect - @test e.affect_neg == affect_neg - @test e.rootfind == SciMLBase.NoRootFind # test plural constructor - e = SymbolicContinuousCallbacks(eqs[]) @test e isa Vector{SymbolicContinuousCallback} - @test isequal(e[].eqs, eqs) - @test e[].affect == NULL_AFFECT + @test isequal(equations(e[]), eqs) + @test e[].affect == nothing e = SymbolicContinuousCallbacks(eqs) @test e isa Vector{SymbolicContinuousCallback} - @test isequal(e[].eqs, eqs) - @test e[].affect == NULL_AFFECT + @test isequal(equations(e[]), eqs) + @test e[].affect == nothing e = SymbolicContinuousCallbacks(eqs[] => affect) @test e isa Vector{SymbolicContinuousCallback} - @test isequal(e[].eqs, eqs) - @test e[].affect == affect + @test isequal(equations(e[]), eqs) + @test e[].affect isa AffectSystem e = SymbolicContinuousCallbacks(eqs => affect) @test e isa Vector{SymbolicContinuousCallback} - @test isequal(e[].eqs, eqs) - @test e[].affect == affect + @test isequal(equations(e[]), eqs) + @test e[].affect isa AffectSystem e = SymbolicContinuousCallbacks([eqs[] => affect]) @test e isa Vector{SymbolicContinuousCallback} - @test isequal(e[].eqs, eqs) - @test e[].affect == affect + @test isequal(equations(e[]), eqs) + @test e[].affect isa AffectSystem e = SymbolicContinuousCallbacks([eqs => affect]) @test e isa Vector{SymbolicContinuousCallback} - @test isequal(e[].eqs, eqs) - @test e[].affect == affect - - e = SymbolicContinuousCallbacks(SymbolicContinuousCallbacks([eqs => affect])) - @test e isa Vector{SymbolicContinuousCallback} - @test isequal(e[].eqs, eqs) - @test e[].affect == affect + @test isequal(equations(e[]), eqs) + @test e[].affect isa AffectSystem end @testset "ImperativeAffect constructors" begin @@ -341,159 +237,162 @@ end @test m.ctx === 3 end -## - -@named sys = ODESystem(eqs, t, continuous_events = [x ~ 1]) -@test getfield(sys, :continuous_events)[] == - SymbolicContinuousCallback(Equation[x ~ 1], NULL_AFFECT) -@test isequal(equations(getfield(sys, :continuous_events))[], x ~ 1) -fsys = flatten(sys) -@test isequal(equations(getfield(fsys, :continuous_events))[], x ~ 1) - -@named sys2 = ODESystem([D(x) ~ 1], t, continuous_events = [x ~ 2], systems = [sys]) -@test getfield(sys2, :continuous_events)[] == - SymbolicContinuousCallback(Equation[x ~ 2], NULL_AFFECT) -@test all(ModelingToolkit.continuous_events(sys2) .== [ - SymbolicContinuousCallback(Equation[x ~ 2], NULL_AFFECT), - SymbolicContinuousCallback(Equation[sys.x ~ 1], NULL_AFFECT) -]) - -@test isequal(equations(getfield(sys2, :continuous_events))[1], x ~ 2) -@test length(ModelingToolkit.continuous_events(sys2)) == 2 -@test isequal(ModelingToolkit.continuous_events(sys2)[1].eqs[], x ~ 2) -@test isequal(ModelingToolkit.continuous_events(sys2)[2].eqs[], sys.x ~ 1) - -sys = complete(sys) -sys_nosplit = complete(sys; split = false) -sys2 = complete(sys2) -# Functions should be generated for root-finding equations -prob = ODEProblem(sys, Pair[], (0.0, 2.0)) -p0 = 0 -t0 = 0 -@test get_callback(prob) isa ModelingToolkit.DiffEqCallbacks.ContinuousCallback -cb = ModelingToolkit.generate_rootfinding_callback(sys) -cond = cb.condition -out = [0.0] -cond.rf_ip(out, [0], p0, t0) -@test out[] ≈ -1 # signature is u,p,t -cond.rf_ip(out, [1], p0, t0) -@test out[] ≈ 0 # signature is u,p,t -cond.rf_ip(out, [2], p0, t0) -@test out[] ≈ 1 # signature is u,p,t - -prob = ODEProblem(sys, Pair[], (0.0, 2.0)) -prob_nosplit = ODEProblem(sys_nosplit, Pair[], (0.0, 2.0)) -sol = solve(prob, Tsit5()) -sol_nosplit = solve(prob_nosplit, Tsit5()) -@test minimum(t -> abs(t - 1), sol.t) < 1e-10 # test that the solver stepped at the root -@test minimum(t -> abs(t - 1), sol_nosplit.t) < 1e-10 # test that the solver stepped at the root - -# Test that a user provided callback is respected -test_callback = DiscreteCallback(x -> x, x -> x) -prob = ODEProblem(sys, Pair[], (0.0, 2.0), callback = test_callback) -prob_nosplit = ODEProblem(sys_nosplit, Pair[], (0.0, 2.0), callback = test_callback) -cbs = get_callback(prob) -cbs_nosplit = get_callback(prob_nosplit) -@test cbs isa CallbackSet -@test cbs.discrete_callbacks[1] == test_callback -@test cbs_nosplit isa CallbackSet -@test cbs_nosplit.discrete_callbacks[1] == test_callback - -prob = ODEProblem(sys2, Pair[], (0.0, 3.0)) -cb = get_callback(prob) -@test cb isa ModelingToolkit.DiffEqCallbacks.VectorContinuousCallback - -cond = cb.condition -out = [0.0, 0.0] -# the root to find is 2 -cond.rf_ip(out, [0, 0], p0, t0) -@test out[1] ≈ -2 # signature is u,p,t -cond.rf_ip(out, [1, 0], p0, t0) -@test out[1] ≈ -1 # signature is u,p,t -cond.rf_ip(out, [2, 0], p0, t0) # this should return 0 -@test out[1] ≈ 0 # signature is u,p,t - -# the root to find is 1 -out = [0.0, 0.0] -cond.rf_ip(out, [0, 0], p0, t0) -@test out[2] ≈ -1 # signature is u,p,t -cond.rf_ip(out, [0, 1], p0, t0) # this should return 0 -@test out[2] ≈ 0 # signature is u,p,t -cond.rf_ip(out, [0, 2], p0, t0) -@test out[2] ≈ 1 # signature is u,p,t - -sol = solve(prob, Tsit5(); abstol = 1e-14, reltol = 1e-14) -@test minimum(t -> abs(t - 1), sol.t) < 1e-10 # test that the solver stepped at the first root -@test minimum(t -> abs(t - 2), sol.t) < 1e-10 # test that the solver stepped at the second root - -@named sys = ODESystem(eqs, t, continuous_events = [x ~ 1, x ~ 2]) # two root eqs using the same unknown -sys = complete(sys) -prob = ODEProblem(sys, Pair[], (0.0, 3.0)) -@test get_callback(prob) isa ModelingToolkit.DiffEqCallbacks.VectorContinuousCallback -sol = solve(prob, Tsit5(); abstol = 1e-14, reltol = 1e-14) -@test minimum(t -> abs(t - 1), sol.t) < 1e-10 # test that the solver stepped at the first root -@test minimum(t -> abs(t - 2), sol.t) < 1e-10 # test that the solver stepped at the second root - -## Test bouncing ball with equation affect -@variables x(t)=1 v(t)=0 - -root_eqs = [x ~ 0] -affect = [v ~ -v] - -@named ball = ODESystem([D(x) ~ v - D(v) ~ -9.8], t, continuous_events = root_eqs => affect) - -@test getfield(ball, :continuous_events)[] == - SymbolicContinuousCallback(Equation[x ~ 0], Equation[v ~ -v]) -ball = structural_simplify(ball) - -@test length(ModelingToolkit.continuous_events(ball)) == 1 - -tspan = (0.0, 5.0) -prob = ODEProblem(ball, Pair[], tspan) -sol = solve(prob, Tsit5()) -@test 0 <= minimum(sol[x]) <= 1e-10 # the ball never went through the floor but got very close -# plot(sol) - -## Test bouncing ball in 2D with walls -@variables x(t)=1 y(t)=0 vx(t)=0 vy(t)=1 - -continuous_events = [[x ~ 0] => [vx ~ -vx] - [y ~ -1.5, y ~ 1.5] => [vy ~ -vy]] - -@named ball = ODESystem( - [D(x) ~ vx - D(y) ~ vy - D(vx) ~ -9.8 - D(vy) ~ -0.01vy], t; continuous_events) - -_ball = ball -ball = structural_simplify(_ball) -ball_nosplit = structural_simplify(_ball; split = false) - -tspan = (0.0, 5.0) -prob = ODEProblem(ball, Pair[], tspan) -prob_nosplit = ODEProblem(ball_nosplit, Pair[], tspan) - -cb = get_callback(prob) -@test cb isa ModelingToolkit.DiffEqCallbacks.VectorContinuousCallback -@test getfield(ball, :continuous_events)[1] == - SymbolicContinuousCallback(Equation[x ~ 0], Equation[vx ~ -vx]) -@test getfield(ball, :continuous_events)[2] == - SymbolicContinuousCallback(Equation[y ~ -1.5, y ~ 1.5], Equation[vy ~ -vy]) -cond = cb.condition -out = [0.0, 0.0, 0.0] -cond.rf_ip(out, [0, 0, 0, 0], p0, t0) -@test out ≈ [0, 1.5, -1.5] +@testset "Basic ODESystem Tests" begin + @named sys = ODESystem(eqs, t, continuous_events = [x ~ 1]) + @test getfield(sys, :continuous_events)[] == + SymbolicContinuousCallback(Equation[x ~ 1], nothing) + @test isequal(equations(getfield(sys, :continuous_events))[], x ~ 1) + fsys = flatten(sys) + @test isequal(equations(getfield(fsys, :continuous_events))[], x ~ 1) + + @named sys2 = ODESystem([D(x) ~ 1], t, continuous_events = [x ~ 2], systems = [sys]) + @test getfield(sys2, :continuous_events)[] == + SymbolicContinuousCallback(Equation[x ~ 2], nothing) + @test all(ModelingToolkit.continuous_events(sys2) .== [ + SymbolicContinuousCallback(Equation[x ~ 2], nothing), + SymbolicContinuousCallback(Equation[sys.x ~ 1], nothing) + ]) + + @test isequal(equations(getfield(sys2, :continuous_events))[1], x ~ 2) + @test length(ModelingToolkit.continuous_events(sys2)) == 2 + @test isequal(equations(ModelingToolkit.continuous_events(sys2)[1])[], x ~ 2) + @test isequal(equations(ModelingToolkit.continuous_events(sys2)[2])[], sys.x ~ 1) + + sys = complete(sys) + sys_nosplit = complete(sys; split = false) + sys2 = complete(sys2) + + # Test proper rootfinding + prob = ODEProblem(sys, Pair[], (0.0, 2.0)) + p0 = 0 + t0 = 0 + @test get_callback(prob) isa ModelingToolkit.DiffEqCallbacks.ContinuousCallback + cb = ModelingToolkit.generate_continuous_callbacks(sys) + cond = cb.condition + out = [0.0] + cond(out, [0], p0, t0) + @test out[] ≈ -1 # signature is u,p,t + cond.rf_ip(out, [1], p0, t0) + @test out[] ≈ 0 # signature is u,p,t + cond.rf_ip(out, [2], p0, t0) + @test out[] ≈ 1 # signature is u,p,t + + prob = ODEProblem(sys, Pair[], (0.0, 2.0)) + prob_nosplit = ODEProblem(sys_nosplit, Pair[], (0.0, 2.0)) + sol = solve(prob, Tsit5()) + sol_nosplit = solve(prob_nosplit, Tsit5()) + @test minimum(t -> abs(t - 1), sol.t) < 1e-10 # test that the solver stepped at the root + @test minimum(t -> abs(t - 1), sol_nosplit.t) < 1e-10 # test that the solver stepped at the root + + # Test user-provided callback is respected + test_callback = DiscreteCallback(x -> x, x -> x) + prob = ODEProblem(sys, Pair[], (0.0, 2.0), callback = test_callback) + prob_nosplit = ODEProblem(sys_nosplit, Pair[], (0.0, 2.0), callback = test_callback) + cbs = get_callback(prob) + cbs_nosplit = get_callback(prob_nosplit) + @test cbs isa CallbackSet + @test cbs.discrete_callbacks[1] == test_callback + @test cbs_nosplit isa CallbackSet + @test cbs_nosplit.discrete_callbacks[1] == test_callback + + prob = ODEProblem(sys2, Pair[], (0.0, 3.0)) + cb = get_callback(prob) + @test cb isa ModelingToolkit.DiffEqCallbacks.VectorContinuousCallback + + cond = cb.condition + out = [0.0, 0.0] + # the root to find is 2 + cond.rf_ip(out, [0, 0], p0, t0) + @test out[1] ≈ -2 # signature is u,p,t + cond.rf_ip(out, [1, 0], p0, t0) + @test out[1] ≈ -1 # signature is u,p,t + cond.rf_ip(out, [2, 0], p0, t0) # this should return 0 + @test out[1] ≈ 0 # signature is u,p,t + + # the root to find is 1 + out = [0.0, 0.0] + cond.rf_ip(out, [0, 0], p0, t0) + @test out[2] ≈ -1 # signature is u,p,t + cond.rf_ip(out, [0, 1], p0, t0) # this should return 0 + @test out[2] ≈ 0 # signature is u,p,t + cond.rf_ip(out, [0, 2], p0, t0) + @test out[2] ≈ 1 # signature is u,p,t -sol = solve(prob, Tsit5()) -sol_nosplit = solve(prob_nosplit, Tsit5()) -@test 0 <= minimum(sol[x]) <= 1e-10 # the ball never went through the floor but got very close -@test minimum(sol[y]) ≈ -1.5 # check wall conditions -@test maximum(sol[y]) ≈ 1.5 # check wall conditions -@test 0 <= minimum(sol_nosplit[x]) <= 1e-10 # the ball never went through the floor but got very close -@test minimum(sol_nosplit[y]) ≈ -1.5 # check wall conditions -@test maximum(sol_nosplit[y]) ≈ 1.5 # check wall conditions + sol = solve(prob, Tsit5()) + @test minimum(t -> abs(t - 1), sol.t) < 1e-10 # test that the solver stepped at the first root + @test minimum(t -> abs(t - 2), sol.t) < 1e-10 # test that the solver stepped at the second root + + @named sys = ODESystem(eqs, t, continuous_events = [x ~ 1, x ~ 2]) # two root eqs using the same unknown + sys = complete(sys) + prob = ODEProblem(sys, Pair[], (0.0, 3.0)) + @test get_callback(prob) isa ModelingToolkit.DiffEqCallbacks.VectorContinuousCallback + sol = solve(prob, Tsit5()) + @test minimum(t -> abs(t - 1), sol.t) < 1e-10 # test that the solver stepped at the first root + @test minimum(t -> abs(t - 2), sol.t) < 1e-10 # test that the solver stepped at the second root +end + +@testset "Bouncing Ball" begin + ###### 1D Bounce + @variables x(t)=1 v(t)=0 + + root_eqs = [x ~ 0] + affect = [v ~ -v] + + @named ball = ODESystem( + [D(x) ~ v + D(v) ~ -9.8], t, continuous_events = root_eqs => affect) + + @test getfield(ball, :continuous_events)[] == + SymbolicContinuousCallback(Equation[x ~ 0], Equation[v ~ -v]) + ball = structural_simplify(ball) + + @test length(ModelingToolkit.continuous_events(ball)) == 1 + + tspan = (0.0, 5.0) + prob = ODEProblem(ball, Pair[], tspan) + sol = solve(prob, Tsit5()) + @test 0 <= minimum(sol[x]) <= 1e-10 # the ball never went through the floor but got very close + + ###### 2D bouncing ball + @variables x(t)=1 y(t)=0 vx(t)=0 vy(t)=1 + + continuous_events = [[x ~ 0] => [vx ~ -vx] + [y ~ -1.5, y ~ 1.5] => [vy ~ -vy]] + + @named ball = ODESystem( + [D(x) ~ vx + D(y) ~ vy + D(vx) ~ -9.8 + D(vy) ~ -0.01vy], t; continuous_events) + + _ball = ball + ball = structural_simplify(_ball) + ball_nosplit = structural_simplify(_ball; split = false) + + tspan = (0.0, 5.0) + prob = ODEProblem(ball, Pair[], tspan) + prob_nosplit = ODEProblem(ball_nosplit, Pair[], tspan) + + cb = get_callback(prob) + @test cb isa ModelingToolkit.DiffEqCallbacks.VectorContinuousCallback + @test getfield(ball, :continuous_events)[1] == + SymbolicContinuousCallback(Equation[x ~ 0], Equation[vx ~ -vx]) + @test getfield(ball, :continuous_events)[2] == + SymbolicContinuousCallback(Equation[y ~ -1.5, y ~ 1.5], Equation[vy ~ -vy]) + cond = cb.condition + out = [0.0, 0.0, 0.0] + cond.rf_ip(out, [0, 0, 0, 0], p0, t0) + @test out ≈ [0, 1.5, -1.5] + + sol = solve(prob, Tsit5()) + sol_nosplit = solve(prob_nosplit, Tsit5()) + @test 0 <= minimum(sol[x]) <= 1e-10 # the ball never went through the floor but got very close + @test minimum(sol[y]) ≈ -1.5 # check wall conditions + @test maximum(sol[y]) ≈ 1.5 # check wall conditions + @test 0 <= minimum(sol_nosplit[x]) <= 1e-10 # the ball never went through the floor but got very close + @test minimum(sol_nosplit[y]) ≈ -1.5 # check wall conditions + @test maximum(sol_nosplit[y]) ≈ 1.5 # check wall conditions +end # tv = sort([LinRange(0, 5, 200); sol.t]) # plot(sol(tv)[y], sol(tv)[x], line_z=tv) @@ -502,27 +401,29 @@ sol_nosplit = solve(prob_nosplit, Tsit5()) ## Test multi-variable affect # in this test, there are two variables affected by a single event. -continuous_events = [ - [x ~ 0] => [vx ~ -vx, vy ~ -vy] -] +@testset "Multi-variable affect" begin + continuous_events = [ + [x ~ 0] => [vx ~ -vx, vy ~ -vy] + ] -@named ball = ODESystem([D(x) ~ vx - D(y) ~ vy - D(vx) ~ -1 - D(vy) ~ 0], t; continuous_events) + @named ball = ODESystem([D(x) ~ vx + D(y) ~ vy + D(vx) ~ -1 + D(vy) ~ 0], t; continuous_events) -ball_nosplit = structural_simplify(ball) -ball = structural_simplify(ball) + ball_nosplit = structural_simplify(ball) + ball = structural_simplify(ball) -tspan = (0.0, 5.0) -prob = ODEProblem(ball, Pair[], tspan) -prob_nosplit = ODEProblem(ball_nosplit, Pair[], tspan) -sol = solve(prob, Tsit5()) -sol_nosplit = solve(prob_nosplit, Tsit5()) -@test 0 <= minimum(sol[x]) <= 1e-10 # the ball never went through the floor but got very close -@test -minimum(sol[y]) ≈ maximum(sol[y]) ≈ sqrt(2) # the ball will never go further than √2 in either direction (gravity was changed to 1 to get this particular number) -@test 0 <= minimum(sol_nosplit[x]) <= 1e-10 # the ball never went through the floor but got very close -@test -minimum(sol_nosplit[y]) ≈ maximum(sol_nosplit[y]) ≈ sqrt(2) # the ball will never go further than √2 in either direction (gravity was changed to 1 to get this particular number) + tspan = (0.0, 5.0) + prob = ODEProblem(ball, Pair[], tspan) + prob_nosplit = ODEProblem(ball_nosplit, Pair[], tspan) + sol = solve(prob, Tsit5()) + sol_nosplit = solve(prob_nosplit, Tsit5()) + @test 0 <= minimum(sol[x]) <= 1e-10 # the ball never went through the floor but got very close + @test -minimum(sol[y]) ≈ maximum(sol[y]) ≈ sqrt(2) # the ball will never go further than √2 in either direction (gravity was changed to 1 to get this particular number) + @test 0 <= minimum(sol_nosplit[x]) <= 1e-10 # the ball never went through the floor but got very close + @test -minimum(sol_nosplit[y]) ≈ maximum(sol_nosplit[y]) ≈ sqrt(2) # the ball will never go further than √2 in either direction (gravity was changed to 1 to get this particular number) +end # tv = sort([LinRange(0, 5, 200); sol.t]) # plot(sol(tv)[y], sol(tv)[x], line_z=tv) @@ -544,50 +445,53 @@ sol = solve(prob, Tsit5()) @test sol([0.25 - eps()])[vmeasured][] == sol([0.23])[vmeasured][] # test the hold property ## https://github.com/SciML/ModelingToolkit.jl/issues/1528 -Dₜ = D +@testset "Handle Empty Events" begin + Dₜ = D -@parameters u(t) [input = true] # Indicate that this is a controlled input -@parameters y(t) [output = true] # Indicate that this is a measured output + @parameters u(t) [input = true] # Indicate that this is a controlled input + @parameters y(t) [output = true] # Indicate that this is a measured output -function Mass(; name, m = 1.0, p = 0, v = 0) - ps = @parameters m = m - sts = @variables pos(t)=p vel(t)=v - eqs = Dₜ(pos) ~ vel - ODESystem(eqs, t, [pos, vel], ps; name) -end -function Spring(; name, k = 1e4) - ps = @parameters k = k - @variables x(t) = 0 # Spring deflection - ODESystem(Equation[], t, [x], ps; name) -end -function Damper(; name, c = 10) - ps = @parameters c = c - @variables vel(t) = 0 - ODESystem(Equation[], t, [vel], ps; name) -end -function SpringDamper(; name, k = false, c = false) - spring = Spring(; name = :spring, k) - damper = Damper(; name = :damper, c) - compose(ODESystem(Equation[], t; name), - spring, damper) -end -connect_sd(sd, m1, m2) = [sd.spring.x ~ m1.pos - m2.pos, sd.damper.vel ~ m1.vel - m2.vel] -sd_force(sd) = -sd.spring.k * sd.spring.x - sd.damper.c * sd.damper.vel -@named mass1 = Mass(; m = 1) -@named mass2 = Mass(; m = 1) -@named sd = SpringDamper(; k = 1000, c = 10) -function Model(u, d = 0) - eqs = [connect_sd(sd, mass1, mass2) - Dₜ(mass1.vel) ~ (sd_force(sd) + u) / mass1.m - Dₜ(mass2.vel) ~ (-sd_force(sd) + d) / mass2.m] - @named _model = ODESystem(eqs, t; observed = [y ~ mass2.pos]) - @named model = compose(_model, mass1, mass2, sd) + function Mass(; name, m = 1.0, p = 0, v = 0) + ps = @parameters m = m + sts = @variables pos(t)=p vel(t)=v + eqs = Dₜ(pos) ~ vel + ODESystem(eqs, t, [pos, vel], ps; name) + end + function Spring(; name, k = 1e4) + ps = @parameters k = k + @variables x(t) = 0 # Spring deflection + ODESystem(Equation[], t, [x], ps; name) + end + function Damper(; name, c = 10) + ps = @parameters c = c + @variables vel(t) = 0 + ODESystem(Equation[], t, [vel], ps; name) + end + function SpringDamper(; name, k = false, c = false) + spring = Spring(; name = :spring, k) + damper = Damper(; name = :damper, c) + compose(ODESystem(Equation[], t; name), + spring, damper) + end + connect_sd(sd, m1, m2) = [ + sd.spring.x ~ m1.pos - m2.pos, sd.damper.vel ~ m1.vel - m2.vel] + sd_force(sd) = -sd.spring.k * sd.spring.x - sd.damper.c * sd.damper.vel + @named mass1 = Mass(; m = 1) + @named mass2 = Mass(; m = 1) + @named sd = SpringDamper(; k = 1000, c = 10) + function Model(u, d = 0) + eqs = [connect_sd(sd, mass1, mass2) + Dₜ(mass1.vel) ~ (sd_force(sd) + u) / mass1.m + Dₜ(mass2.vel) ~ (-sd_force(sd) + d) / mass2.m] + @named _model = ODESystem(eqs, t; observed = [y ~ mass2.pos]) + @named model = compose(_model, mass1, mass2, sd) + end + model = Model(sin(30t)) + sys = structural_simplify(model) + @test isempty(ModelingToolkit.continuous_events(sys)) end -model = Model(sin(30t)) -sys = structural_simplify(model) -@test isempty(ModelingToolkit.continuous_events(sys)) -let +@testset "ODESystem Discrete Callbacks" begin function testsol(osys, u0, p, tspan; tstops = Float64[], paramtotest = nothing, kwargs...) oprob = ODEProblem(complete(osys), u0, tspan, p; kwargs...) @@ -662,7 +566,7 @@ let @test isapprox(sol(10.0)[1], 0.1; atol = 1e-10, rtol = 1e-10) end -let +@testset "SDESystem Discrete Callbacks" begin function testsol(ssys, u0, p, tspan; tstops = Float64[], paramtotest = nothing, kwargs...) sprob = SDEProblem(complete(ssys), u0, tspan, p; kwargs...) @@ -743,7 +647,8 @@ let @test isapprox(sol(10.0)[1], 0.1; atol = 1e-10, rtol = 1e-10) end -let rng = rng +@testset "JumpSystem Discrete Callbacks" begin + rng = rng function testsol(jsys, u0, p, tspan; tstops = Float64[], paramtotest = nothing, N = 40000, kwargs...) jsys = complete(jsys) @@ -810,7 +715,7 @@ let rng = rng testsol(jsys6, u0, p, tspan; tstops = [1.0, 2.0], rng, paramtotest = k) end -let +@testset "Oscillator" begin function oscillator_ce(k = 1.0; name) sts = @variables x(t)=1.0 v(t)=0.0 F(t) ps = @parameters k=k Θ=0.5 @@ -1083,6 +988,7 @@ end @test sol[b] == [2.0, 5.0, 5.0] @test sol[c] == [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0] end + @testset "Heater" begin @variables temp(t) params = @parameters furnace_on_threshold=0.5 furnace_off_threshold=0.7 furnace_power=1.0 leakage=0.1 furnace_on::Bool=false @@ -1378,7 +1284,7 @@ end @test_nowarn solve(prob, Tsit5(), tstops = [1.0]) end -@testset "Array parameter updates in ImperativeEffect" begin +@testset "Array parameter updates in ImperativeAffect" begin function weird1(max_time; name) params = @parameters begin θ(t) = 0.0 From 0e9215fbb89c1240299128d19ec0ccb7704bc0fb Mon Sep 17 00:00:00 2001 From: vyudu Date: Wed, 12 Mar 2025 03:59:29 -0400 Subject: [PATCH 033/122] some tests working --- src/systems/callbacks.jl | 132 ++++++++++++------ .../discrete_system/discrete_system.jl | 10 ++ .../implicit_discrete_system.jl | 10 ++ src/systems/index_cache.jl | 4 +- src/systems/problem_utils.jl | 4 +- test/symbolic_events.jl | 86 ++++++------ 6 files changed, 147 insertions(+), 99 deletions(-) diff --git a/src/systems/callbacks.jl b/src/systems/callbacks.jl index b25d64d59c..76e5a0d47f 100644 --- a/src/systems/callbacks.jl +++ b/src/systems/callbacks.jl @@ -67,21 +67,31 @@ struct AffectSystem unknowns::Vector parameters::Vector discretes::Vector - """Maps the unknowns in the ImplicitDiscreteSystem to the corresponding parameter or unknown in the parent system.""" - affu_to_sysu::Dict + """Maps the symbols of unknowns/observed in the ImplicitDiscreteSystem to its corresponding unknown/parameter in the parent system.""" + aff_to_sys::Dict end system(a::AffectSystem) = a.system discretes(a::AffectSystem) = a.discretes unknowns(a::AffectSystem) = a.unknowns parameters(a::AffectSystem) = a.parameters -affu_to_sysu(a::AffectSystem) = a.affu_to_sysu +aff_to_sys(a::AffectSystem) = a.aff_to_sys +previous_vals(a::AffectSystem) = parameters(system(a)) +updated_vals(a::AffectSystem) = unknowns(system(a)) function Base.show(iio::IO, aff::AffectSystem) eqs = vcat(equations(system(aff)), observed(system(aff))) show(iio, eqs) end +function Base.:(==)(a1::AffectSystem, a2::AffectSystem) + isequal(system(a1), system(a2)) && + isequal(discretes(a1), discretes(a2)) && + isequal(unknowns(a1), unknowns(a2)) && + isequal(parameters(a1), parameters(a2)) && + isequal(aff_to_sys(a1), aff_to_sys(a2)) +end + """ Pre(x) @@ -112,14 +122,14 @@ function (p::Pre)(x) iscall(x) && operation(x) isa Pre && return x result = if symbolic_type(x) == ArraySymbolic() # create an array for `Pre(array)` - Symbolics.array_term(p, toparam(x)) + Symbolics.array_term(p, x) elseif iscall(x) && operation(x) == getindex # instead of `Pre(x[1])` create `Pre(x)[1]` # which allows parameter indexing to handle this case automatically. arr = arguments(x)[1] - term(getindex, p(toparam(arr)), arguments(x)[2:end]...) + term(getindex, p(arr), arguments(x)[2:end]...) else - term(p, toparam(x)) + term(p, x) end # the result should be a parameter result = toparam(result) @@ -231,7 +241,7 @@ function make_affect(affect::Vector{Equation}; warn = true) discretes = Any[] p_as_unknowns = Any[] for p in params - if iscall(p) && (operator(p) isa Pre) + if iscall(p) && (operation(p) isa Pre) push!(cb_params, p) elseif iscall(p) && length(arguments(p)) == 1 && isequal(only(arguments(p)), iv) @@ -239,17 +249,28 @@ function make_affect(affect::Vector{Equation}; warn = true) push!(p_as_unknowns, tovar(p)) else push!(discretes, p) - p = iscall(p) ? wrap(Sym{FnType{Tuple{symtype(iv)}, Real}}(nameof(operation(p)))(iv)) : - wrap(Sym{FnType{Tuple{symtype(iv)}, Real}}(nameof(p))(iv)) + name = iscall(p) ? nameof(operation(p)) : nameof(p) + p = wrap(Sym{FnType{Tuple{symtype(iv)}, Real}}(name)(iv)) + p = setmetadata(p, Symbolics.VariableSource, (:variables, name)) push!(p_as_unknowns, p) end end + aff_map = Dict(zip(p_as_unknowns, discretes)) + rev_map = Dict([v => k for (k, v) in aff_map]) + affect = Symbolics.substitute(affect, rev_map) @mtkbuild affectsys = ImplicitDiscreteSystem( affect, iv, collect(union(unknowns, p_as_unknowns)), cb_params) - params = map(x -> only(arguments(unwrap(x))), cb_params) - affmap = Dict(zip([p_as_unknowns, unknowns], [discretes, unknowns])) + params = filter(isparameter, map(x -> only(arguments(unwrap(x))), cb_params)) + @show params + + for u in unknowns + aff_map[u] = u + end + + @show unknowns + @show params - return AffectSystem(affectsys, collect(unknowns), params, discretes, affmap) + return AffectSystem(affectsys, collect(unknowns), params, discretes, aff_map) end function make_affect(affect) @@ -393,17 +414,19 @@ function SymbolicDiscreteCallbacks(events, algeeqs::Vector{Equation} = Equation[ for event in events cond, affs = event isa Pair ? (event[1], event[2]) : (event, nothing) - if aff isa AbstractVector - aff = vcat(aff, algeeqs) + if affs isa AbstractVector + affs = vcat(affs, algeeqs) end - affect = make_affect(aff) - push!(callbacks, SymbolicDiscreteCallback(cond, affect, nothing, nothing)) + affect = make_affect(affs) + push!(callbacks, SymbolicDiscreteCallback(cond, affect)) end callbacks end function is_timed_condition(condition::T) where {T} - if T <: Real + if T === Num + false + elseif T <: Real true elseif T <: AbstractVector eltype(condition) <: Real @@ -582,23 +605,31 @@ function compile_condition(cbs::Union{AbstractCallback, Vector{<:AbstractCallbac condit = substitute(condit, cmap) end - f_oop, f_iip = build_function_wrapper(sys, - condit, u, t, p...; expression = Val{true}, - p_start = 3, p_end = length(p) + 2, + if !is_discrete(cbs) + condit = [cond.lhs - cond.rhs for cond in condit] + end + + fs = build_function_wrapper(sys, + condit, u, p..., t; expression, kwargs...) - if cbs isa AbstractVector - cond(out, u, t, integ) = f_iip(out, u, t, parameter_values(integ)) + if expression == Val{true} + fs = eval_or_rgf.(fs; eval_expression, eval_module) + end + is_discrete(cbs) ? (f_oop = fs) : (f_oop, f_iip = fs) + + cond = if cbs isa AbstractVector + (out, u, t, integ) -> f_iip(out, u, parameter_values(integ), t) elseif is_discrete(cbs) - cond(u, t, integ) = f_oop(u, t, parameter_values(integ)) + (u, t, integ) -> f_oop(u, parameter_values(integ), t) else - cond = function (u, t, integ) + function (u, t, integ) if DiffEqBase.isinplace(integ.sol.prob) tmp, = DiffEqBase.get_tmp_cache(integ) - f_iip(tmp, u, t, parameter_values(integ)) + f_iip(tmp, u, parameter_values(integ), t) tmp[1] else - f_oop(u, t, parameter_values(integ)) + f_oop(u, parameter_values(integ), t) end end end @@ -641,6 +672,7 @@ function compile_functional_affect(affect::FunctionalAffect, cb, sys, dvs, ps; k end is_discrete(cb::AbstractCallback) = cb isa SymbolicDiscreteCallback +is_discrete(cb::Vector{<:AbstractCallback}) = eltype(cb) isa SymbolicDiscreteCallback function generate_continuous_callbacks(sys::AbstractSystem, dvs = unknowns(sys), ps = parameters(sys; initial_parameters = true); kwargs...) cbs = continuous_events(sys) @@ -668,27 +700,27 @@ function generate_callback(cbs::Vector{SymbolicContinuousCallback}, sys; kwargs. return generate_callback(cbs[cb_ind], sys; kwargs...) end - trigger = compile_condition(cbs, sys, dvs, ps; kwargs...) + trigger = compile_condition(cbs, sys, unknowns(sys), parameters(sys; initial_parameters = true); kwargs...) affects = [] affect_negs = [] inits = [] finals = [] for cb in cbs - affect = compile_affect(cb.affect, cb, sys) + affect = compile_affect(cb.affect, cb, sys, default = (args...) -> ()) push!(affects, affect) push!(affect_negs, compile_affect(cb.affect_neg, cb, sys, default = affect)) - push!(inits, compile_affect(cb.initialize, cb, sys, default = SciMLBase.INITALIZE_DEFAULT)) - push!(finals, compile_affect(cb.finalize, cb, sys, default = SciMLBase.FINALIZE_DEFAULT)) + push!(inits, compile_affect(cb.initialize, cb, sys, default = nothing)) + push!(finals, compile_affect(cb.finalize, cb, sys, default = nothing)) end # Since there may be different number of conditions and affects, # we build a map that translates the condition eq. number to the affect number - num_eqs = length.(eqs) eq2affect = reduce(vcat, [fill(i, num_eqs[i]) for i in eachindex(affects)]) + eqs = reduce(vcat, eqs) @assert length(eq2affect) == length(eqs) - @assert maximum(eq2affect) == length(affect_functions) + @assert maximum(eq2affect) == length(affects) affect = function (integ, idx) affects[eq2affect[idx]](integ) @@ -702,8 +734,8 @@ function generate_callback(cbs::Vector{SymbolicContinuousCallback}, sys; kwargs. finalize = compile_vector_optional_affect(finals, SciMLBase.FINALIZE_DEFAULT) return VectorContinuousCallback( - trigger, affect, length(cbs); affect_neg, initialize, finalize, - rootfind = callback.rootfind, initializealg = SciMLBase.NoInit) + trigger, affect, affect_neg, length(eqs); initialize, finalize, + rootfind = cbs[1].rootfind, initializealg = SciMLBase.NoInit) end function generate_callback(cb, sys; kwargs...) @@ -712,14 +744,14 @@ function generate_callback(cb, sys; kwargs...) ps = parameters(sys; initial_parameters = true) trigger = is_timed ? conditions(cb) : compile_condition(cb, sys, dvs, ps; kwargs...) - affect = compile_affect(cb.affect, cb, sys) + affect = compile_affect(cb.affect, cb, sys, default = (args...) -> ()) affect_neg = hasfield(typeof(cb), :affect_neg) ? compile_affect(cb.affect_neg, cb, sys, default = affect) : nothing initialize = compile_affect(cb.initialize, cb, sys, default = SciMLBase.INITIALIZE_DEFAULT) finalize = compile_affect(cb.finalize, cb, sys, default = SciMLBase.FINALIZE_DEFAULT) if is_discrete(cb) - if is_timed && condition(cb) isa AbstractVector + if is_timed && conditions(cb) isa AbstractVector return PresetTimeCallback(trigger, affect; affect_neg, initialize, finalize, initializealg = SciMLBase.NoInit) elseif is_timed @@ -762,22 +794,30 @@ function compile_affect( ps = parameters(aff) dvs = unknowns(aff) + @show ps if aff isa AffectSystem - aff_map = affu_to_sysu(aff) + aff_map = aff_to_sys(aff) + sys_map = Dict([v => k for (k, v) in aff_map]) + build_initializeprob = has_alg_eqs(sys) + function affect!(integrator) - pmap = [] - for pre_p in parameters(system(affect)) + pmap = Pair[] + for pre_p in previous_vals(aff) p = only(arguments(unwrap(pre_p))) - push!(pmap, pre_p => integrator[p]) - end - guesses = [u => integrator[aff_map[u]] for u in unknowns(system(affect))] - prob = ImplicitDiscreteProblem(system(affect), [], (0, 1), pmap; guesses) - sol = init(prob, SimpleIDSolve()) - for u in unknowns(system(affect)) - integrator[aff_map[u]] = sol[u] + pval = isparameter(p) ? integrator.ps[p] : integrator[p] + push!(pmap, pre_p => pval) end + guesses = Pair[u => integrator[aff_map[u]] for u in updated_vals(aff)] + affprob = ImplicitDiscreteProblem(system(aff), Pair[], (0, 1), pmap; guesses, build_initializeprob) + affsol = init(affprob, SimpleIDSolve()) + for u in unknowns(aff) + integrator[u] = affsol[u] + end + for p in discretes(aff) + integrator.ps[p] = affsol[sys_map[p]] + end for idx in save_idxs SciMLBase.save_discretes!(integ, idx) end diff --git a/src/systems/discrete_system/discrete_system.jl b/src/systems/discrete_system/discrete_system.jl index 075aa27e4d..773a6ccf6b 100644 --- a/src/systems/discrete_system/discrete_system.jl +++ b/src/systems/discrete_system/discrete_system.jl @@ -428,4 +428,14 @@ function DiscreteFunctionExpr(sys::DiscreteSystem, args...; kwargs...) DiscreteFunctionExpr{true}(sys, args...; kwargs...) end +function Base.:(==)(sys1::DiscreteSystem, sys2::DiscreteSystem) + sys1 === sys2 && return true + isequal(nameof(sys1), nameof(sys2)) && + isequal(get_iv(sys1), get_iv(sys2)) && + _eq_unordered(get_eqs(sys1), get_eqs(sys2)) && + _eq_unordered(get_unknowns(sys1), get_unknowns(sys2)) && + _eq_unordered(get_ps(sys1), get_ps(sys2)) && + all(s1 == s2 for (s1, s2) in zip(get_systems(sys1), get_systems(sys2))) +end + supports_initialization(::DiscreteSystem) = false diff --git a/src/systems/discrete_system/implicit_discrete_system.jl b/src/systems/discrete_system/implicit_discrete_system.jl index 3956c089d4..b977ba992e 100644 --- a/src/systems/discrete_system/implicit_discrete_system.jl +++ b/src/systems/discrete_system/implicit_discrete_system.jl @@ -441,3 +441,13 @@ end function ImplicitDiscreteFunctionExpr(sys::ImplicitDiscreteSystem, args...; kwargs...) ImplicitDiscreteFunctionExpr{true}(sys, args...; kwargs...) end + +function Base.:(==)(sys1::ImplicitDiscreteSystem, sys2::ImplicitDiscreteSystem) + sys1 === sys2 && return true + isequal(nameof(sys1), nameof(sys2)) && + isequal(get_iv(sys1), get_iv(sys2)) && + _eq_unordered(get_eqs(sys1), get_eqs(sys2)) && + _eq_unordered(get_unknowns(sys1), get_unknowns(sys2)) && + _eq_unordered(get_ps(sys1), get_ps(sys2)) && + all(s1 == s2 for (s1, s2) in zip(get_systems(sys1), get_systems(sys2))) +end diff --git a/src/systems/index_cache.jl b/src/systems/index_cache.jl index 8f8bebad31..e4087e1368 100644 --- a/src/systems/index_cache.jl +++ b/src/systems/index_cache.jl @@ -117,9 +117,7 @@ function IndexCache(sys::AbstractSystem) affs = [affs] end for affect in affs - if affect isa Equation - is_parameter(sys, affect.lhs) && push!(discs, affect.lhs) - elseif affect isa FunctionalAffect || affect isa ImperativeAffect + if affect isa AffectSystem || affect isa FunctionalAffect || affect isa ImperativeAffect union!(discs, unwrap.(discretes(affect))) elseif isnothing(affect) continue diff --git a/src/systems/problem_utils.jl b/src/systems/problem_utils.jl index 47bf3c678d..a7867380e7 100644 --- a/src/systems/problem_utils.jl +++ b/src/systems/problem_utils.jl @@ -380,9 +380,7 @@ function better_varmap_to_vars(varmap::AbstractDict, vars::Vector; vals = promote_to_concrete(vals; tofloat = tofloat, use_union = false) end - if isempty(vals) - return nothing - elseif container_type <: Tuple + if container_type <: Tuple return (vals...,) else return SymbolicUtils.Code.create_array(container_type, eltype(vals), Val{1}(), diff --git a/test/symbolic_events.jl b/test/symbolic_events.jl index 2b9da431a7..8b9e901880 100644 --- a/test/symbolic_events.jl +++ b/test/symbolic_events.jl @@ -237,7 +237,7 @@ end @test m.ctx === 3 end -@testset "Basic ODESystem Tests" begin +@testset "Condition Compilation" begin @named sys = ODESystem(eqs, t, continuous_events = [x ~ 1]) @test getfield(sys, :continuous_events)[] == SymbolicContinuousCallback(Equation[x ~ 1], nothing) @@ -270,11 +270,11 @@ end cb = ModelingToolkit.generate_continuous_callbacks(sys) cond = cb.condition out = [0.0] - cond(out, [0], p0, t0) + cond.f_iip.contents(out, [0], p0, t0) @test out[] ≈ -1 # signature is u,p,t - cond.rf_ip(out, [1], p0, t0) + cond.f_iip.contents(out, [1], p0, t0) @test out[] ≈ 0 # signature is u,p,t - cond.rf_ip(out, [2], p0, t0) + cond.f_iip.contents(out, [2], p0, t0) @test out[] ≈ 1 # signature is u,p,t prob = ODEProblem(sys, Pair[], (0.0, 2.0)) @@ -302,20 +302,20 @@ end cond = cb.condition out = [0.0, 0.0] # the root to find is 2 - cond.rf_ip(out, [0, 0], p0, t0) + cond.f_iip.contents(out, [0, 0], p0, t0) @test out[1] ≈ -2 # signature is u,p,t - cond.rf_ip(out, [1, 0], p0, t0) + cond.f_iip.contents(out, [1, 0], p0, t0) @test out[1] ≈ -1 # signature is u,p,t - cond.rf_ip(out, [2, 0], p0, t0) # this should return 0 + cond.f_iip.contents(out, [2, 0], p0, t0) # this should return 0 @test out[1] ≈ 0 # signature is u,p,t # the root to find is 1 out = [0.0, 0.0] - cond.rf_ip(out, [0, 0], p0, t0) + cond.f_iip.contents(out, [0, 0], p0, t0) @test out[2] ≈ -1 # signature is u,p,t - cond.rf_ip(out, [0, 1], p0, t0) # this should return 0 + cond.f_iip.contents(out, [0, 1], p0, t0) # this should return 0 @test out[2] ≈ 0 # signature is u,p,t - cond.rf_ip(out, [0, 2], p0, t0) + cond.f_iip.contents(out, [0, 2], p0, t0) @test out[2] ≈ 1 # signature is u,p,t sol = solve(prob, Tsit5()) @@ -336,14 +336,14 @@ end @variables x(t)=1 v(t)=0 root_eqs = [x ~ 0] - affect = [v ~ -v] + affect = [v ~ -Pre(v)] @named ball = ODESystem( [D(x) ~ v D(v) ~ -9.8], t, continuous_events = root_eqs => affect) - @test getfield(ball, :continuous_events)[] == - SymbolicContinuousCallback(Equation[x ~ 0], Equation[v ~ -v]) + @test only(continuous_events(ball)) == + SymbolicContinuousCallback(Equation[x ~ 0], Equation[v ~ -Pre(v)]) ball = structural_simplify(ball) @test length(ModelingToolkit.continuous_events(ball)) == 1 @@ -356,14 +356,14 @@ end ###### 2D bouncing ball @variables x(t)=1 y(t)=0 vx(t)=0 vy(t)=1 - continuous_events = [[x ~ 0] => [vx ~ -vx] - [y ~ -1.5, y ~ 1.5] => [vy ~ -vy]] + events = [[x ~ 0] => [vx ~ -Pre(vx)] + [y ~ -1.5, y ~ 1.5] => [vy ~ -Pre(vy)]] @named ball = ODESystem( [D(x) ~ vx D(y) ~ vy D(vx) ~ -9.8 - D(vy) ~ -0.01vy], t; continuous_events) + D(vy) ~ -0.01vy], t; continuous_events = events) _ball = ball ball = structural_simplify(_ball) @@ -381,7 +381,9 @@ end SymbolicContinuousCallback(Equation[y ~ -1.5, y ~ 1.5], Equation[vy ~ -vy]) cond = cb.condition out = [0.0, 0.0, 0.0] - cond.rf_ip(out, [0, 0, 0, 0], p0, t0) + p0 = 0. + t0 = 0. + cond.f_iip.contents(out, [0, 0, 0, 0], p0, t0) @test out ≈ [0, 1.5, -1.5] sol = solve(prob, Tsit5()) @@ -394,22 +396,15 @@ end @test maximum(sol_nosplit[y]) ≈ 1.5 # check wall conditions end -# tv = sort([LinRange(0, 5, 200); sol.t]) -# plot(sol(tv)[y], sol(tv)[x], line_z=tv) -# vline!([-1.5, 1.5], l=(:black, 5), primary=false) -# hline!([0], l=(:black, 5), primary=false) - ## Test multi-variable affect # in this test, there are two variables affected by a single event. @testset "Multi-variable affect" begin - continuous_events = [ - [x ~ 0] => [vx ~ -vx, vy ~ -vy] - ] + events = [[x ~ 0] => [vx ~ -Pre(vx), vy ~ -Pre(vy)]] @named ball = ODESystem([D(x) ~ vx D(y) ~ vy D(vx) ~ -1 - D(vy) ~ 0], t; continuous_events) + D(vy) ~ 0], t; continuous_events = events) ball_nosplit = structural_simplify(ball) ball = structural_simplify(ball) @@ -425,24 +420,21 @@ end @test -minimum(sol_nosplit[y]) ≈ maximum(sol_nosplit[y]) ≈ sqrt(2) # the ball will never go further than √2 in either direction (gravity was changed to 1 to get this particular number) end -# tv = sort([LinRange(0, 5, 200); sol.t]) -# plot(sol(tv)[y], sol(tv)[x], line_z=tv) -# vline!([-1.5, 1.5], l=(:black, 5), primary=false) -# hline!([0], l=(:black, 5), primary=false) - # issue https://github.com/SciML/ModelingToolkit.jl/issues/1386 # tests that it works for ODAESystem -@variables vs(t) v(t) vmeasured(t) -eq = [vs ~ sin(2pi * t) - D(v) ~ vs - v - D(vmeasured) ~ 0.0] -ev = [sin(20pi * t) ~ 0.0] => [vmeasured ~ v] -@named sys = ODESystem(eq, t, continuous_events = ev) -sys = structural_simplify(sys) -prob = ODEProblem(sys, zeros(2), (0.0, 5.1)) -sol = solve(prob, Tsit5()) -@test all(minimum((0:0.05:5) .- sol.t', dims = 2) .< 0.0001) # test that the solver stepped every 0.05s as dictated by event -@test sol([0.25 - eps()])[vmeasured][] == sol([0.23])[vmeasured][] # test the hold property +@testset "ODAESystem" begin + @variables vs(t) v(t) vmeasured(t) + eq = [vs ~ sin(2pi * t) + D(v) ~ vs - v + D(vmeasured) ~ 0.0] + ev = [sin(20pi * t) ~ 0.0] => [vmeasured ~ Pre(v)] + @named sys = ODESystem(eq, t, continuous_events = ev) + sys = structural_simplify(sys) + prob = ODEProblem(sys, zeros(2), (0.0, 5.1)) + sol = solve(prob, Tsit5()) + @test all(minimum((0:0.1:5) .- sol.t', dims = 2) .< 0.0001) # test that the solver stepped every 0.1s as dictated by event + @test sol([0.25])[vmeasured][] == sol([0.23])[vmeasured][] # test the hold property +end ## https://github.com/SciML/ModelingToolkit.jl/issues/1528 @testset "Handle Empty Events" begin @@ -506,7 +498,7 @@ end @variables A(t) B(t) cond1 = (t == t1) - affect1 = [A ~ A + 1] + affect1 = [A ~ Pre(A) + 1] cb1 = cond1 => affect1 cond2 = (t == t2) affect2 = [k ~ 1.0] @@ -581,7 +573,7 @@ end @variables A(t) B(t) cond1 = (t == t1) - affect1 = [A ~ A + 1] + affect1 = [A ~ Pre(A) + 1] cb1 = cond1 => affect1 cond2 = (t == t2) affect2 = [k ~ 1.0] @@ -597,7 +589,7 @@ end testsol(ssys, u0, p, tspan; tstops = [1.0, 2.0], paramtotest = k) cond1a = (t == t1) - affect1a = [A ~ A + 1, B ~ A] + affect1a = [A ~ Pre(A) + 1, B ~ Pre(A)] cb1a = cond1a => affect1a @named ssys1 = SDESystem(eqs, [0.0], t, [A, B], [k, t1, t2], discrete_events = [cb1a, cb2]) @@ -665,7 +657,7 @@ end @variables A(t) B(t) cond1 = (t == t1) - affect1 = [A ~ A + 1] + affect1 = [A ~ Pre(A) + 1] cb1 = cond1 => affect1 cond2 = (t == t2) affect2 = [k ~ 1.0] @@ -679,7 +671,7 @@ end testsol(jsys, u0, p, tspan; tstops = [1.0, 2.0], rng, paramtotest = k) cond1a = (t == t1) - affect1a = [A ~ A + 1, B ~ A] + affect1a = [A ~ Pre(A) + 1, B ~ Pre(A)] cb1a = cond1a => affect1a @named jsys1 = JumpSystem(eqs, t, [A, B], [k, t1, t2], discrete_events = [cb1a, cb2]) u0′ = [A => 1, B => 0] From 66afd663a0db9642cfe01da7e1126fc12dfe64f2 Mon Sep 17 00:00:00 2001 From: vyudu Date: Wed, 12 Mar 2025 11:37:33 -0400 Subject: [PATCH 034/122] fix: modify constructor for SDESystem and JUmpSystem --- src/systems/diffeqs/odesystem.jl | 4 ---- src/systems/diffeqs/sdesystem.jl | 6 ++++-- src/systems/jumps/jumpsystem.jl | 4 ++-- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/systems/diffeqs/odesystem.jl b/src/systems/diffeqs/odesystem.jl index a5aff93e7d..30fd2d4724 100644 --- a/src/systems/diffeqs/odesystem.jl +++ b/src/systems/diffeqs/odesystem.jl @@ -336,10 +336,6 @@ function ODESystem(deqs::AbstractVector{<:Equation}, iv, dvs, ps; throw(ArgumentError("System names must be unique.")) end - algeeqs = filter(is_alg_equation, deqs) - cont_callbacks = SymbolicContinuousCallbacks(continuous_events, algeeqs) - disc_callbacks = SymbolicDiscreteCallbacks(discrete_events, algeeqs) - if is_dde === nothing is_dde = _check_if_dde(deqs, iv′, systems) end diff --git a/src/systems/diffeqs/sdesystem.jl b/src/systems/diffeqs/sdesystem.jl index c5299c28be..9fb692d541 100644 --- a/src/systems/diffeqs/sdesystem.jl +++ b/src/systems/diffeqs/sdesystem.jl @@ -269,8 +269,10 @@ function SDESystem(deqs::AbstractVector{<:Equation}, neqs::AbstractArray, iv, dv ctrl_jac = RefValue{Any}(EMPTY_JAC) Wfact = RefValue(EMPTY_JAC) Wfact_t = RefValue(EMPTY_JAC) - cont_callbacks = SymbolicContinuousCallbacks(continuous_events) - disc_callbacks = SymbolicDiscreteCallbacks(discrete_events) + + algeeqs = filter(is_alg_equation, deqs) + cont_callbacks = SymbolicContinuousCallbacks(continuous_events, algeeqs) + disc_callbacks = SymbolicDiscreteCallbacks(discrete_events, algeeqs) if is_dde === nothing is_dde = _check_if_dde(deqs, iv′, systems) end diff --git a/src/systems/jumps/jumpsystem.jl b/src/systems/jumps/jumpsystem.jl index 06f5e1b623..07a41dd04f 100644 --- a/src/systems/jumps/jumpsystem.jl +++ b/src/systems/jumps/jumpsystem.jl @@ -230,8 +230,8 @@ function JumpSystem(eqs, iv, unknowns, ps; end end - cont_callbacks = SymbolicContinuousCallbacks(continuous_events) - disc_callbacks = SymbolicDiscreteCallbacks(discrete_events) + cont_callbacks = SymbolicContinuousCallbacks(continuous_events, Equation[]) + disc_callbacks = SymbolicDiscreteCallbacks(discrete_events, Equation[]) JumpSystem{typeof(ap)}(Threads.atomic_add!(SYSTEM_COUNT, UInt(1)), ap, iv′, us′, ps′, var_to_name, observed, name, description, systems, From 627fbe050e3e829285ce932e1221217a8d766246 Mon Sep 17 00:00:00 2001 From: vyudu Date: Wed, 12 Mar 2025 12:13:23 -0400 Subject: [PATCH 035/122] test: make more tests pass --- src/systems/callbacks.jl | 12 +++--- src/systems/diffeqs/odesystem.jl | 4 ++ test/symbolic_events.jl | 63 +++++++++++++++----------------- 3 files changed, 40 insertions(+), 39 deletions(-) diff --git a/src/systems/callbacks.jl b/src/systems/callbacks.jl index 76e5a0d47f..a197d978cc 100644 --- a/src/systems/callbacks.jl +++ b/src/systems/callbacks.jl @@ -219,8 +219,8 @@ SymbolicContinuousCallback(p::Pair) = SymbolicContinuousCallback(p[1], p[2]) SymbolicContinuousCallback(cb::SymbolicContinuousCallback, args...) = cb make_affect(affect::Nothing) = nothing -make_affect(affect::Tuple) = FunctionalAffect(affects...) -make_affect(affect::NamedTuple) = FunctionalAffect(; affects...) +make_affect(affect::Tuple) = FunctionalAffect(affect...) +make_affect(affect::NamedTuple) = FunctionalAffect(; affect...) make_affect(affect::FunctionalAffect) = affect make_affect(affect::AffectSystem) = affect @@ -616,7 +616,7 @@ function compile_condition(cbs::Union{AbstractCallback, Vector{<:AbstractCallbac if expression == Val{true} fs = eval_or_rgf.(fs; eval_expression, eval_module) end - is_discrete(cbs) ? (f_oop = fs) : (f_oop, f_iip = fs) + f_oop, f_iip = is_discrete(cbs) ? (fs, nothing) : fs # no iip function for discrete condition. cond = if cbs isa AbstractVector (out, u, t, integ) -> f_iip(out, u, parameter_values(integ), t) @@ -644,7 +644,7 @@ function compile_functional_affect(affect::FunctionalAffect, cb, sys, dvs, ps; k dvs_ind = Dict(reverse(en) for en in enumerate(dvs)) v_inds = map(sym -> dvs_ind[sym], unknowns(affect)) - if has_index_cache(sys) && get_index_cache(sys) !== nothing + 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)] save_idxs = get(ic.callback_to_clocks, cb, Int[]) @@ -752,7 +752,7 @@ function generate_callback(cb, sys; kwargs...) if is_discrete(cb) if is_timed && conditions(cb) isa AbstractVector - return PresetTimeCallback(trigger, affect; affect_neg, initialize, + return PresetTimeCallback(trigger, affect; initialize, finalize, initializealg = SciMLBase.NoInit) elseif is_timed return PeriodicCallback(affect, trigger; initialize, finalize) @@ -783,7 +783,7 @@ Notes - `kwargs` are passed through to `Symbolics.build_function`. """ function compile_affect( - aff::Union{Nothing, Affect}, cb::AbstractCallback, sys::AbstractSystem; default = nothing) + aff::Union{Nothing, Affect}, cb::AbstractCallback, sys::AbstractSystem; default = nothing, kwargs...) save_idxs = if !(has_index_cache(sys) && (ic = get_index_cache(sys)) !== nothing) Int[] else diff --git a/src/systems/diffeqs/odesystem.jl b/src/systems/diffeqs/odesystem.jl index 30fd2d4724..a5aff93e7d 100644 --- a/src/systems/diffeqs/odesystem.jl +++ b/src/systems/diffeqs/odesystem.jl @@ -336,6 +336,10 @@ function ODESystem(deqs::AbstractVector{<:Equation}, iv, dvs, ps; throw(ArgumentError("System names must be unique.")) end + algeeqs = filter(is_alg_equation, deqs) + cont_callbacks = SymbolicContinuousCallbacks(continuous_events, algeeqs) + disc_callbacks = SymbolicDiscreteCallbacks(discrete_events, algeeqs) + if is_dde === nothing is_dde = _check_if_dde(deqs, iv′, systems) end diff --git a/test/symbolic_events.jl b/test/symbolic_events.jl index 8b9e901880..8839d11557 100644 --- a/test/symbolic_events.jl +++ b/test/symbolic_events.jl @@ -270,11 +270,11 @@ end cb = ModelingToolkit.generate_continuous_callbacks(sys) cond = cb.condition out = [0.0] - cond.f_iip.contents(out, [0], p0, t0) + cond.f_iip(out, [0], p0, t0) @test out[] ≈ -1 # signature is u,p,t - cond.f_iip.contents(out, [1], p0, t0) + cond.f_iip(out, [1], p0, t0) @test out[] ≈ 0 # signature is u,p,t - cond.f_iip.contents(out, [2], p0, t0) + cond.f_iip(out, [2], p0, t0) @test out[] ≈ 1 # signature is u,p,t prob = ODEProblem(sys, Pair[], (0.0, 2.0)) @@ -302,20 +302,20 @@ end cond = cb.condition out = [0.0, 0.0] # the root to find is 2 - cond.f_iip.contents(out, [0, 0], p0, t0) + cond.f_iip(out, [0, 0], p0, t0) @test out[1] ≈ -2 # signature is u,p,t - cond.f_iip.contents(out, [1, 0], p0, t0) + cond.f_iip(out, [1, 0], p0, t0) @test out[1] ≈ -1 # signature is u,p,t - cond.f_iip.contents(out, [2, 0], p0, t0) # this should return 0 + cond.f_iip(out, [2, 0], p0, t0) # this should return 0 @test out[1] ≈ 0 # signature is u,p,t # the root to find is 1 out = [0.0, 0.0] - cond.f_iip.contents(out, [0, 0], p0, t0) + cond.f_iip(out, [0, 0], p0, t0) @test out[2] ≈ -1 # signature is u,p,t - cond.f_iip.contents(out, [0, 1], p0, t0) # this should return 0 + cond.f_iip(out, [0, 1], p0, t0) # this should return 0 @test out[2] ≈ 0 # signature is u,p,t - cond.f_iip.contents(out, [0, 2], p0, t0) + cond.f_iip(out, [0, 2], p0, t0) @test out[2] ≈ 1 # signature is u,p,t sol = solve(prob, Tsit5()) @@ -376,14 +376,14 @@ end cb = get_callback(prob) @test cb isa ModelingToolkit.DiffEqCallbacks.VectorContinuousCallback @test getfield(ball, :continuous_events)[1] == - SymbolicContinuousCallback(Equation[x ~ 0], Equation[vx ~ -vx]) + SymbolicContinuousCallback(Equation[x ~ 0], Equation[vx ~ -Pre(vx)]) @test getfield(ball, :continuous_events)[2] == - SymbolicContinuousCallback(Equation[y ~ -1.5, y ~ 1.5], Equation[vy ~ -vy]) + SymbolicContinuousCallback(Equation[y ~ -1.5, y ~ 1.5], Equation[vy ~ -Pre(vy)]) cond = cb.condition out = [0.0, 0.0, 0.0] p0 = 0. t0 = 0. - cond.f_iip.contents(out, [0, 0, 0, 0], p0, t0) + cond.f_iip(out, [0, 0, 0, 0], p0, t0) @test out ≈ [0, 1.5, -1.5] sol = solve(prob, Tsit5()) @@ -394,11 +394,9 @@ end @test 0 <= minimum(sol_nosplit[x]) <= 1e-10 # the ball never went through the floor but got very close @test minimum(sol_nosplit[y]) ≈ -1.5 # check wall conditions @test maximum(sol_nosplit[y]) ≈ 1.5 # check wall conditions -end -## Test multi-variable affect -# in this test, there are two variables affected by a single event. -@testset "Multi-variable affect" begin + ## Test multi-variable affect + # in this test, there are two variables affected by a single event. events = [[x ~ 0] => [vx ~ -Pre(vx), vy ~ -Pre(vy)]] @named ball = ODESystem([D(x) ~ vx @@ -422,19 +420,19 @@ end # issue https://github.com/SciML/ModelingToolkit.jl/issues/1386 # tests that it works for ODAESystem -@testset "ODAESystem" begin - @variables vs(t) v(t) vmeasured(t) - eq = [vs ~ sin(2pi * t) - D(v) ~ vs - v - D(vmeasured) ~ 0.0] - ev = [sin(20pi * t) ~ 0.0] => [vmeasured ~ Pre(v)] - @named sys = ODESystem(eq, t, continuous_events = ev) - sys = structural_simplify(sys) - prob = ODEProblem(sys, zeros(2), (0.0, 5.1)) - sol = solve(prob, Tsit5()) - @test all(minimum((0:0.1:5) .- sol.t', dims = 2) .< 0.0001) # test that the solver stepped every 0.1s as dictated by event - @test sol([0.25])[vmeasured][] == sol([0.23])[vmeasured][] # test the hold property -end +#@testset "ODAESystem" begin +# @variables vs(t) v(t) vmeasured(t) +# eq = [vs ~ sin(2pi * t) +# D(v) ~ vs - v +# D(vmeasured) ~ 0.0] +# ev = [sin(20pi * t) ~ 0.0] => [vmeasured ~ Pre(v)] +# @named sys = ODESystem(eq, t, continuous_events = ev) +# sys = structural_simplify(sys) +# prob = ODEProblem(sys, zeros(2), (0.0, 5.1)) +# sol = solve(prob, Tsit5()) +# @test all(minimum((0:0.1:5) .- sol.t', dims = 2) .< 0.0001) # test that the solver stepped every 0.1s as dictated by event +# @test sol([0.25])[vmeasured][] == sol([0.23])[vmeasured][] # test the hold property +#end ## https://github.com/SciML/ModelingToolkit.jl/issues/1528 @testset "Handle Empty Events" begin @@ -513,7 +511,7 @@ end testsol(osys, u0, p, tspan; tstops = [1.0, 2.0], paramtotest = k) cond1a = (t == t1) - affect1a = [A ~ A + 1, B ~ A] + affect1a = [A ~ Pre(A) + 1, B ~ A] cb1a = cond1a => affect1a @named osys1 = ODESystem(eqs, t, [A, B], [k, t1, t2], discrete_events = [cb1a, cb2]) u0′ = [A => 1.0, B => 0.0] @@ -589,7 +587,7 @@ end testsol(ssys, u0, p, tspan; tstops = [1.0, 2.0], paramtotest = k) cond1a = (t == t1) - affect1a = [A ~ Pre(A) + 1, B ~ Pre(A)] + affect1a = [A ~ Pre(A) + 1, B ~ A] cb1a = cond1a => affect1a @named ssys1 = SDESystem(eqs, [0.0], t, [A, B], [k, t1, t2], discrete_events = [cb1a, cb2]) @@ -640,7 +638,6 @@ end end @testset "JumpSystem Discrete Callbacks" begin - rng = rng function testsol(jsys, u0, p, tspan; tstops = Float64[], paramtotest = nothing, N = 40000, kwargs...) jsys = complete(jsys) @@ -671,7 +668,7 @@ end testsol(jsys, u0, p, tspan; tstops = [1.0, 2.0], rng, paramtotest = k) cond1a = (t == t1) - affect1a = [A ~ Pre(A) + 1, B ~ Pre(A)] + affect1a = [A ~ Pre(A) + 1, B ~ A] cb1a = cond1a => affect1a @named jsys1 = JumpSystem(eqs, t, [A, B], [k, t1, t2], discrete_events = [cb1a, cb2]) u0′ = [A => 1, B => 0] From f3fe987449910071149dbf272fbcf00a490620d3 Mon Sep 17 00:00:00 2001 From: vyudu Date: Wed, 12 Mar 2025 15:29:00 -0400 Subject: [PATCH 036/122] test: fix namespacing --- src/systems/callbacks.jl | 36 ++++++++++++++++++------------------ test/symbolic_events.jl | 7 ++++--- 2 files changed, 22 insertions(+), 21 deletions(-) diff --git a/src/systems/callbacks.jl b/src/systems/callbacks.jl index a197d978cc..813e5412c7 100644 --- a/src/systems/callbacks.jl +++ b/src/systems/callbacks.jl @@ -258,18 +258,12 @@ function make_affect(affect::Vector{Equation}; warn = true) aff_map = Dict(zip(p_as_unknowns, discretes)) rev_map = Dict([v => k for (k, v) in aff_map]) affect = Symbolics.substitute(affect, rev_map) - @mtkbuild affectsys = ImplicitDiscreteSystem( - affect, iv, collect(union(unknowns, p_as_unknowns)), cb_params) + @mtkbuild affectsys = ImplicitDiscreteSystem(affect, iv, collect(union(unknowns, p_as_unknowns)), cb_params) params = filter(isparameter, map(x -> only(arguments(unwrap(x))), cb_params)) - @show params - for u in unknowns aff_map[u] = u end - @show unknowns - @show params - return AffectSystem(affectsys, collect(unknowns), params, discretes, aff_map) end @@ -494,16 +488,22 @@ function namespace_affect(affect::FunctionalAffect, s) context(affect)) end -namespace_affect(affect::AffectSystem, s) = AffectSystem(system(affect), renamespace.((s,), discretes(affect))) -namespace_affects(af::Union{Nothing, Affect}, s) = af isa Affect ? namespace_affect(af, s) : nothing +function namespace_affect(affect::AffectSystem, s) + AffectSystem(renamespace(s, system(affect)), + renamespace.((s,), unknowns(affect)), + renamespace.((s,), parameters(affect)), + renamespace.((s,), discretes(affect)), + Dict([k => renamespace(s, v) for (k, v) in aff_to_sys(affect)])) +end +namespace_affect(af::Nothing, s) = nothing function namespace_callback(cb::SymbolicContinuousCallback, s)::SymbolicContinuousCallback SymbolicContinuousCallback( namespace_equation.(equations(cb), (s,)), - namespace_affects(affects(cb), s), - affect_neg = namespace_affects(affect_negs(cb), s), - initialize = namespace_affects(initialize_affects(cb), s), - finalize = namespace_affects(finalize_affects(cb), s), + namespace_affect(affects(cb), s), + affect_neg = namespace_affect(affect_negs(cb), s), + initialize = namespace_affect(initialize_affects(cb), s), + finalize = namespace_affect(finalize_affects(cb), s), rootfind = cb.rootfind) end @@ -794,9 +794,9 @@ function compile_affect( ps = parameters(aff) dvs = unknowns(aff) - @show ps if aff isa AffectSystem + affsys = system(aff) aff_map = aff_to_sys(aff) sys_map = Dict([v => k for (k, v) in aff_map]) build_initializeprob = has_alg_eqs(sys) @@ -809,11 +809,11 @@ function compile_affect( push!(pmap, pre_p => pval) end guesses = Pair[u => integrator[aff_map[u]] for u in updated_vals(aff)] - affprob = ImplicitDiscreteProblem(system(aff), Pair[], (0, 1), pmap; guesses, build_initializeprob) + affprob = ImplicitDiscreteProblem(affsys, Pair[], (0, 1), pmap; guesses, build_initializeprob) affsol = init(affprob, SimpleIDSolve()) for u in unknowns(aff) - integrator[u] = affsol[u] + integrator[u] = affsol[sys_map[u]] end for p in discretes(aff) integrator.ps[p] = affsol[sys_map[p]] @@ -899,9 +899,9 @@ function continuous_events(sys::AbstractSystem) systems = get_systems(sys) cbs = [obs; reduce(vcat, - (map(o -> namespace_callback(o, s), continuous_events(s)) - for s in systems), + (map(o -> namespace_callback(o, s), continuous_events(s)) for s in systems), init = SymbolicContinuousCallback[])] + @show cbs filter(!isempty, cbs) end diff --git a/test/symbolic_events.jl b/test/symbolic_events.jl index 8839d11557..31b3a819e1 100644 --- a/test/symbolic_events.jl +++ b/test/symbolic_events.jl @@ -644,6 +644,7 @@ end dprob = DiscreteProblem(jsys, u0, tspan, p) jprob = JumpProblem(jsys, dprob, Direct(); kwargs...) sol = solve(jprob, SSAStepper(); tstops = tstops) + @show sol @test (sol(1.000000000001)[1] - sol(0.99999999999)[1]) == 1 paramtotest === nothing || (@test sol.ps[paramtotest] == 1.0) @test sol(40.0)[1] == 0 @@ -654,7 +655,7 @@ end @variables A(t) B(t) cond1 = (t == t1) - affect1 = [A ~ Pre(A) + 1] + affect1 = [A ~ A + 1] cb1 = cond1 => affect1 cond2 = (t == t2) affect2 = [k ~ 1.0] @@ -704,7 +705,7 @@ end testsol(jsys6, u0, p, tspan; tstops = [1.0, 2.0], rng, paramtotest = k) end -@testset "Oscillator" begin +@testset "Namespacing" begin function oscillator_ce(k = 1.0; name) sts = @variables x(t)=1.0 v(t)=0.0 F(t) ps = @parameters k=k Θ=0.5 @@ -1152,7 +1153,7 @@ end f = ModelingToolkit.FunctionalAffect( f = (i, u, p, c) -> seen = true, sts = [], pars = [], discretes = []) cb1 = ModelingToolkit.SymbolicContinuousCallback( - [x ~ 0], Equation[], initialize = [x ~ 1.5], finalize = f) + [x ~ 0], nothing, initialize = [x ~ 1.5], finalize = f) @mtkbuild sys = ODESystem(D(x) ~ -1, t, [x], []; continuous_events = [cb1]) prob = ODEProblem(sys, [x => 1.0], (0.0, 2), []) sol = solve(prob, Tsit5(); dtmax = 0.01) From e6abcd43c5a5d8cf42f9f76071979f19997c5f2a Mon Sep 17 00:00:00 2001 From: vyudu Date: Wed, 12 Mar 2025 17:11:33 -0400 Subject: [PATCH 037/122] fix: fix JumpSystem and don't use is_diff_equation --- src/systems/callbacks.jl | 22 ++++++++++++---------- src/systems/diffeqs/odesystem.jl | 2 +- src/systems/diffeqs/sdesystem.jl | 2 +- 3 files changed, 14 insertions(+), 12 deletions(-) diff --git a/src/systems/callbacks.jl b/src/systems/callbacks.jl index 813e5412c7..814d43d679 100644 --- a/src/systems/callbacks.jl +++ b/src/systems/callbacks.jl @@ -355,8 +355,8 @@ function vars!(vars, cb::SymbolicContinuousCallback; op = Differential) vars!(vars, eq; op) end for aff in (affects(cb), affect_negs(cb), initialize_affects(cb), finalize_affects(cb)) - if aff isa Vector{Equation} - for eq in aff + if aff isa AffectSystem + for eq in vcat(observed(system(aff)), equations(system(aff))) vars!(vars, eq; op) end elseif aff !== nothing @@ -453,18 +453,18 @@ function Base.show(io::IO, db::SymbolicDiscreteCallback) end function vars!(vars, cb::SymbolicDiscreteCallback; op = Differential) - if symbolic_type(cb.condition) == NotSymbolic - if cb.condition isa AbstractArray - for eq in cb.condition + if symbolic_type(conditions(cb)) == NotSymbolic + if conditions(cb) isa AbstractArray + for eq in conditions(cb) vars!(vars, eq; op) end end else - vars!(vars, cb.condition; op) + vars!(vars, conditions(cb); op) end - for aff in (cb.affects, cb.initialize, cb.finalize) - if aff isa Vector{Equation} - for eq in aff + for aff in (affects(cb), initialize_affects(cb), finalize_affects(cb)) + if aff isa AffectSystem + for eq in vcat(observed(system(aff)), equations(system(aff))) vars!(vars, eq; op) end elseif aff !== nothing @@ -709,7 +709,7 @@ function generate_callback(cbs::Vector{SymbolicContinuousCallback}, sys; kwargs. affect = compile_affect(cb.affect, cb, sys, default = (args...) -> ()) push!(affects, affect) - push!(affect_negs, compile_affect(cb.affect_neg, cb, sys, default = affect)) + push!(affect_negs, compile_affect(cb.affect_neg, cb, sys, default = affect) push!(inits, compile_affect(cb.initialize, cb, sys, default = nothing)) push!(finals, compile_affect(cb.finalize, cb, sys, default = nothing)) end @@ -821,6 +821,8 @@ function compile_affect( for idx in save_idxs SciMLBase.save_discretes!(integ, idx) end + + sys isa JumpSystem && reset_aggregated_jumps!(integrator) end elseif aff isa FunctionalAffect || aff isa ImperativeAffect compile_functional_affect(aff, cb, sys, dvs, ps; kwargs...) diff --git a/src/systems/diffeqs/odesystem.jl b/src/systems/diffeqs/odesystem.jl index a5aff93e7d..79c0a99ff7 100644 --- a/src/systems/diffeqs/odesystem.jl +++ b/src/systems/diffeqs/odesystem.jl @@ -336,7 +336,7 @@ function ODESystem(deqs::AbstractVector{<:Equation}, iv, dvs, ps; throw(ArgumentError("System names must be unique.")) end - algeeqs = filter(is_alg_equation, deqs) + alg_eqs = filter(eq -> eq.lhs isa Union{Symbolic, Number} && !isdiffeq(eq), deqs) cont_callbacks = SymbolicContinuousCallbacks(continuous_events, algeeqs) disc_callbacks = SymbolicDiscreteCallbacks(discrete_events, algeeqs) diff --git a/src/systems/diffeqs/sdesystem.jl b/src/systems/diffeqs/sdesystem.jl index 9fb692d541..05db96a68f 100644 --- a/src/systems/diffeqs/sdesystem.jl +++ b/src/systems/diffeqs/sdesystem.jl @@ -270,7 +270,7 @@ function SDESystem(deqs::AbstractVector{<:Equation}, neqs::AbstractArray, iv, dv Wfact = RefValue(EMPTY_JAC) Wfact_t = RefValue(EMPTY_JAC) - algeeqs = filter(is_alg_equation, deqs) + alg_eqs = filter(eq -> eq.lhs isa Union{Symbolic, Number} && !isdiffeq(eq), deqs) cont_callbacks = SymbolicContinuousCallbacks(continuous_events, algeeqs) disc_callbacks = SymbolicDiscreteCallbacks(discrete_events, algeeqs) if is_dde === nothing From 1e15e748339eff0c1bb5fc73b2e187e37d4440a1 Mon Sep 17 00:00:00 2001 From: vyudu Date: Wed, 12 Mar 2025 17:18:19 -0400 Subject: [PATCH 038/122] typo: add ) --- src/systems/callbacks.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/systems/callbacks.jl b/src/systems/callbacks.jl index 814d43d679..2f2846e362 100644 --- a/src/systems/callbacks.jl +++ b/src/systems/callbacks.jl @@ -709,7 +709,7 @@ function generate_callback(cbs::Vector{SymbolicContinuousCallback}, sys; kwargs. affect = compile_affect(cb.affect, cb, sys, default = (args...) -> ()) push!(affects, affect) - push!(affect_negs, compile_affect(cb.affect_neg, cb, sys, default = affect) + push!(affect_negs, compile_affect(cb.affect_neg, cb, sys, default = affect)) push!(inits, compile_affect(cb.initialize, cb, sys, default = nothing)) push!(finals, compile_affect(cb.finalize, cb, sys, default = nothing)) end From c462dfcaee7b010e7c219f54c57e445b8281d7d8 Mon Sep 17 00:00:00 2001 From: vyudu Date: Wed, 12 Mar 2025 17:22:00 -0400 Subject: [PATCH 039/122] typo: algeeqs --- src/systems/diffeqs/odesystem.jl | 4 ++-- src/systems/diffeqs/sdesystem.jl | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/systems/diffeqs/odesystem.jl b/src/systems/diffeqs/odesystem.jl index 79c0a99ff7..305c82f8f6 100644 --- a/src/systems/diffeqs/odesystem.jl +++ b/src/systems/diffeqs/odesystem.jl @@ -337,8 +337,8 @@ function ODESystem(deqs::AbstractVector{<:Equation}, iv, dvs, ps; end alg_eqs = filter(eq -> eq.lhs isa Union{Symbolic, Number} && !isdiffeq(eq), deqs) - cont_callbacks = SymbolicContinuousCallbacks(continuous_events, algeeqs) - disc_callbacks = SymbolicDiscreteCallbacks(discrete_events, algeeqs) + cont_callbacks = SymbolicContinuousCallbacks(continuous_events, alg_eqs) + disc_callbacks = SymbolicDiscreteCallbacks(discrete_events, alg_eqs) if is_dde === nothing is_dde = _check_if_dde(deqs, iv′, systems) diff --git a/src/systems/diffeqs/sdesystem.jl b/src/systems/diffeqs/sdesystem.jl index 05db96a68f..bfc1496f3d 100644 --- a/src/systems/diffeqs/sdesystem.jl +++ b/src/systems/diffeqs/sdesystem.jl @@ -271,8 +271,8 @@ function SDESystem(deqs::AbstractVector{<:Equation}, neqs::AbstractArray, iv, dv Wfact_t = RefValue(EMPTY_JAC) alg_eqs = filter(eq -> eq.lhs isa Union{Symbolic, Number} && !isdiffeq(eq), deqs) - cont_callbacks = SymbolicContinuousCallbacks(continuous_events, algeeqs) - disc_callbacks = SymbolicDiscreteCallbacks(discrete_events, algeeqs) + cont_callbacks = SymbolicContinuousCallbacks(continuous_events, alg_eqs) + disc_callbacks = SymbolicDiscreteCallbacks(discrete_events, alg_eqs) if is_dde === nothing is_dde = _check_if_dde(deqs, iv′, systems) end From cdf3a6d808720ef30cf8b074bd328937031479d3 Mon Sep 17 00:00:00 2001 From: vyudu Date: Thu, 13 Mar 2025 20:53:56 -0400 Subject: [PATCH 040/122] fix --- src/systems/callbacks.jl | 1 - 1 file changed, 1 deletion(-) diff --git a/src/systems/callbacks.jl b/src/systems/callbacks.jl index 2f2846e362..ad36dab9a2 100644 --- a/src/systems/callbacks.jl +++ b/src/systems/callbacks.jl @@ -903,7 +903,6 @@ function continuous_events(sys::AbstractSystem) reduce(vcat, (map(o -> namespace_callback(o, s), continuous_events(s)) for s in systems), init = SymbolicContinuousCallback[])] - @show cbs filter(!isempty, cbs) end From 72421e7c068689f562ccae970a620acd6fab89e6 Mon Sep 17 00:00:00 2001 From: vyudu Date: Mon, 17 Mar 2025 10:06:00 -0400 Subject: [PATCH 041/122] more test fixes --- Project.toml | 1 + src/systems/callbacks.jl | 74 ++++++++++++++++++-------------- src/systems/diffeqs/odesystem.jl | 4 +- src/systems/diffeqs/sdesystem.jl | 4 +- test/symbolic_events.jl | 26 +++++------ 5 files changed, 59 insertions(+), 50 deletions(-) diff --git a/Project.toml b/Project.toml index 1d7899d521..49035e30c6 100644 --- a/Project.toml +++ b/Project.toml @@ -51,6 +51,7 @@ SciMLBase = "0bca4576-84f4-4d90-8ffe-ffa030f20462" SciMLStructures = "53ae85a6-f571-4167-b2af-e1d143709226" Serialization = "9e88b42a-f829-5b0c-bbe9-9e923198166b" Setfield = "efcf1570-3423-57d1-acb7-fd33fddbac46" +SimpleImplicitDiscreteSolve = "3263718b-31ed-49cf-8a0f-35a466e8af96" SimpleNonlinearSolve = "727e6d20-b764-4bd8-a329-72de5adea6c7" SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" SpecialFunctions = "276daf66-3868-5448-9aa4-cd146d93841b" diff --git a/src/systems/callbacks.jl b/src/systems/callbacks.jl index ad36dab9a2..3f3defea88 100644 --- a/src/systems/callbacks.jl +++ b/src/systems/callbacks.jl @@ -203,68 +203,79 @@ struct SymbolicContinuousCallback <: AbstractCallback function SymbolicContinuousCallback( conditions::Union{Equation, Vector{Equation}}, - affect = nothing; + affect = nothing, iv = nothing; affect_neg = affect, initialize = nothing, finalize = nothing, - rootfind = SciMLBase.LeftRootFind) + rootfind = SciMLBase.LeftRootFind, + algeeqs = Equation[]) + affect isa AbstractVector && isnothing(iv) && @warn "No independent variable specified. If t appears in an affect equation explicitly, like x ~ t + 1, then this must be specified. Otherwise this can be disregarded." conditions = (conditions isa AbstractVector) ? conditions : [conditions] - new(conditions, make_affect(affect), make_affect(affect_neg), - initialize, finalize, rootfind) + new(conditions, make_affect(affect, iv; algeeqs), make_affect(affect_neg, iv; algeeqs), + make_affect(initialize, iv; algeeqs), make_affect(finalize, iv; algeeqs), rootfind) end # Default affect to nothing end SymbolicContinuousCallback(p::Pair) = SymbolicContinuousCallback(p[1], p[2]) SymbolicContinuousCallback(cb::SymbolicContinuousCallback, args...) = cb -make_affect(affect::Nothing) = nothing -make_affect(affect::Tuple) = FunctionalAffect(affect...) -make_affect(affect::NamedTuple) = FunctionalAffect(; affect...) -make_affect(affect::FunctionalAffect) = affect -make_affect(affect::AffectSystem) = affect +make_affect(affect::Nothing, iv; kwargs...) = nothing +make_affect(affect::Tuple, iv; kwargs...) = FunctionalAffect(affect...) +make_affect(affect::NamedTuple, iv; kwargs...) = FunctionalAffect(; affect...) +make_affect(affect::FunctionalAffect, iv; kwargs...) = affect +make_affect(affect::AffectSystem, iv; kwargs...) = affect + +function make_affect(affect::Vector{Equation}, iv; algeeqs = Equation[]) + isempty(affect) && return nothing + isempty(algeeqs) && @warn "No algebraic equations were found. If the system has no algebraic equations, this can be disregarded. Otherwise consider passing in `algeeqs` to the SymbolicContinuousCallbacks constructor." -function make_affect(affect::Vector{Equation}; warn = true) affect = scalarize(affect) - unknowns = OrderedSet() + dvs = OrderedSet() params = OrderedSet() - for eq in affect - !haspre(eq) && warn && - @warn "Equation $eq has no `Pre` operator. As such it will be interpreted as an algebraic equation to be satisfied after the callback. If you intended to use the value of a variable x before the affect, use Pre(x)." - collect_vars!(unknowns, params, eq, nothing; op = Pre) + !haspre(eq) && + @warn "Affect equation $eq has no `Pre` operator. As such it will be interpreted as an algebraic equation to be satisfied after the callback. If you intended to use the value of a variable x before the affect, use Pre(x)." + collect_vars!(dvs, params, eq, iv; op = Pre) + end + for eq in algeeqs + collect_vars!(dvs, params, eq, iv) + end + if isnothing(iv) + iv = isempty(dvs) ? iv : only(arguments(dvs[1])) end - iv = isempty(unknowns) ? t_nounits : only(arguments(unknowns[1])) # System parameters should become unknowns in the ImplicitDiscreteSystem. cb_params = Any[] discretes = Any[] - p_as_unknowns = Any[] + p_as_dvs = Any[] for p in params if iscall(p) && (operation(p) isa Pre) push!(cb_params, p) elseif iscall(p) && length(arguments(p)) == 1 && isequal(only(arguments(p)), iv) push!(discretes, p) - push!(p_as_unknowns, tovar(p)) + push!(p_as_dvs, tovar(p)) else push!(discretes, p) name = iscall(p) ? nameof(operation(p)) : nameof(p) p = wrap(Sym{FnType{Tuple{symtype(iv)}, Real}}(name)(iv)) p = setmetadata(p, Symbolics.VariableSource, (:variables, name)) - push!(p_as_unknowns, p) + push!(p_as_dvs, p) end end - aff_map = Dict(zip(p_as_unknowns, discretes)) + aff_map = Dict(zip(p_as_dvs, discretes)) rev_map = Dict([v => k for (k, v) in aff_map]) affect = Symbolics.substitute(affect, rev_map) - @mtkbuild affectsys = ImplicitDiscreteSystem(affect, iv, collect(union(unknowns, p_as_unknowns)), cb_params) + @mtkbuild affectsys = ImplicitDiscreteSystem(vcat(affect, algeeqs), iv, collect(union(dvs, p_as_dvs)), cb_params) + # get accessed parameters p from Pre(p) in the callback parameters params = filter(isparameter, map(x -> only(arguments(unwrap(x))), cb_params)) - for u in unknowns + # add unknowns to the map + for u in unknowns(affectsys) aff_map[u] = u end - return AffectSystem(affectsys, collect(unknowns), params, discretes, aff_map) + return AffectSystem(affectsys, unknowns(affectsys), params, discretes, aff_map) end function make_affect(affect) @@ -274,7 +285,7 @@ end """ Generate continuous callbacks. """ -function SymbolicContinuousCallbacks(events, algeeqs::Vector{Equation} = Equation[]) +function SymbolicContinuousCallbacks(events, algeeqs::Vector{Equation} = Equation[], iv = nothing) callbacks = SymbolicContinuousCallback[] isnothing(events) && return callbacks @@ -283,10 +294,7 @@ function SymbolicContinuousCallbacks(events, algeeqs::Vector{Equation} = Equatio for event in events cond, affs = event isa Pair ? (event[1], event[2]) : (event, nothing) - if affs isa AbstractVector - affs = vcat(affs, algeeqs) - end - affect = make_affect(affs) + affect = make_affect(affs, iv; algeeqs) push!(callbacks, SymbolicContinuousCallback(cond, affect)) end callbacks @@ -391,6 +399,8 @@ struct SymbolicDiscreteCallback <: AbstractCallback condition, affect = nothing; initialize = nothing, finalize = nothing) c = is_timed_condition(condition) ? condition : value(scalarize(condition)) + + isnothing(iv) && @warn "No independent variable specified. If t appears in an affect equation explicitly, like x ~ t + 1, then this must be specified. Otherwise this can be disregarded." new(c, make_affect(affect), make_affect(initialize), make_affect(finalize)) end # Default affect to nothing @@ -399,7 +409,7 @@ end """ Generate discrete callbacks. """ -function SymbolicDiscreteCallbacks(events, algeeqs::Vector{Equation} = Equation[]) +function SymbolicDiscreteCallbacks(events, algeeqs::Vector{Equation} = Equation[], iv = nothing) callbacks = SymbolicDiscreteCallback[] isnothing(events) && return callbacks @@ -408,10 +418,7 @@ function SymbolicDiscreteCallbacks(events, algeeqs::Vector{Equation} = Equation[ for event in events cond, affs = event isa Pair ? (event[1], event[2]) : (event, nothing) - if affs isa AbstractVector - affs = vcat(affs, algeeqs) - end - affect = make_affect(affs) + affect = make_affect(affs, iv; algeeqs) push!(callbacks, SymbolicDiscreteCallback(cond, affect)) end callbacks @@ -813,6 +820,7 @@ function compile_affect( affsol = init(affprob, SimpleIDSolve()) for u in unknowns(aff) + @show u integrator[u] = affsol[sys_map[u]] end for p in discretes(aff) diff --git a/src/systems/diffeqs/odesystem.jl b/src/systems/diffeqs/odesystem.jl index 305c82f8f6..1f3d272328 100644 --- a/src/systems/diffeqs/odesystem.jl +++ b/src/systems/diffeqs/odesystem.jl @@ -337,8 +337,8 @@ function ODESystem(deqs::AbstractVector{<:Equation}, iv, dvs, ps; end alg_eqs = filter(eq -> eq.lhs isa Union{Symbolic, Number} && !isdiffeq(eq), deqs) - cont_callbacks = SymbolicContinuousCallbacks(continuous_events, alg_eqs) - disc_callbacks = SymbolicDiscreteCallbacks(discrete_events, alg_eqs) + cont_callbacks = SymbolicContinuousCallbacks(continuous_events, alg_eqs, iv) + disc_callbacks = SymbolicDiscreteCallbacks(discrete_events, alg_eqs, iv) if is_dde === nothing is_dde = _check_if_dde(deqs, iv′, systems) diff --git a/src/systems/diffeqs/sdesystem.jl b/src/systems/diffeqs/sdesystem.jl index bfc1496f3d..2698ac70f9 100644 --- a/src/systems/diffeqs/sdesystem.jl +++ b/src/systems/diffeqs/sdesystem.jl @@ -271,8 +271,8 @@ function SDESystem(deqs::AbstractVector{<:Equation}, neqs::AbstractArray, iv, dv Wfact_t = RefValue(EMPTY_JAC) alg_eqs = filter(eq -> eq.lhs isa Union{Symbolic, Number} && !isdiffeq(eq), deqs) - cont_callbacks = SymbolicContinuousCallbacks(continuous_events, alg_eqs) - disc_callbacks = SymbolicDiscreteCallbacks(discrete_events, alg_eqs) + cont_callbacks = SymbolicContinuousCallbacks(continuous_events, alg_eqs, iv) + disc_callbacks = SymbolicDiscreteCallbacks(discrete_events, alg_eqs, iv) if is_dde === nothing is_dde = _check_if_dde(deqs, iv′, systems) end diff --git a/test/symbolic_events.jl b/test/symbolic_events.jl index 31b3a819e1..50cc345707 100644 --- a/test/symbolic_events.jl +++ b/test/symbolic_events.jl @@ -420,19 +420,19 @@ end # issue https://github.com/SciML/ModelingToolkit.jl/issues/1386 # tests that it works for ODAESystem -#@testset "ODAESystem" begin -# @variables vs(t) v(t) vmeasured(t) -# eq = [vs ~ sin(2pi * t) -# D(v) ~ vs - v -# D(vmeasured) ~ 0.0] -# ev = [sin(20pi * t) ~ 0.0] => [vmeasured ~ Pre(v)] -# @named sys = ODESystem(eq, t, continuous_events = ev) -# sys = structural_simplify(sys) -# prob = ODEProblem(sys, zeros(2), (0.0, 5.1)) -# sol = solve(prob, Tsit5()) -# @test all(minimum((0:0.1:5) .- sol.t', dims = 2) .< 0.0001) # test that the solver stepped every 0.1s as dictated by event -# @test sol([0.25])[vmeasured][] == sol([0.23])[vmeasured][] # test the hold property -#end +@testset "ODAESystem" begin + @variables vs(t) v(t) vmeasured(t) + eq = [vs ~ sin(2pi * t) + D(v) ~ vs - v + D(vmeasured) ~ 0.0] + ev = [sin(20pi * t) ~ 0.0] => [vmeasured ~ Pre(v)] + @named sys = ODESystem(eq, t, continuous_events = ev) + sys = structural_simplify(sys) + prob = ODEProblem(sys, zeros(2), (0.0, 5.1)) + sol = solve(prob, Tsit5()) + @test all(minimum((0:0.1:5) .- sol.t', dims = 2) .< 0.0001) # test that the solver stepped every 0.1s as dictated by event + @test sol([0.25])[vmeasured][] == sol([0.23])[vmeasured][] # test the hold property +end ## https://github.com/SciML/ModelingToolkit.jl/issues/1528 @testset "Handle Empty Events" begin From a57df9d58a0bb8d5bd2da299418339f98c9bd61b Mon Sep 17 00:00:00 2001 From: vyudu Date: Tue, 18 Mar 2025 12:36:20 -0400 Subject: [PATCH 042/122] refactor: make iv, algeeqs kwargs --- src/systems/callbacks.jl | 70 +-- src/systems/diffeqs/odesystem.jl | 6 +- src/systems/diffeqs/sdesystem.jl | 6 +- src/systems/jumps/jumpsystem.jl | 4 +- test/symbolic_events.jl | 741 +++++++++++++++++++++++++++++-- 5 files changed, 757 insertions(+), 70 deletions(-) diff --git a/src/systems/callbacks.jl b/src/systems/callbacks.jl index 3f3defea88..0207f552a1 100644 --- a/src/systems/callbacks.jl +++ b/src/systems/callbacks.jl @@ -77,7 +77,6 @@ unknowns(a::AffectSystem) = a.unknowns parameters(a::AffectSystem) = a.parameters aff_to_sys(a::AffectSystem) = a.aff_to_sys previous_vals(a::AffectSystem) = parameters(system(a)) -updated_vals(a::AffectSystem) = unknowns(system(a)) function Base.show(iio::IO, aff::AffectSystem) eqs = vcat(equations(system(aff)), observed(system(aff))) @@ -148,7 +147,8 @@ haspre(O) = recursive_hasoperator(Pre, O) const Affect = Union{AffectSystem, FunctionalAffect, ImperativeAffect} """ - SymbolicContinuousCallback(eqs::Vector{Equation}, affect, affect_neg, rootfind) + SymbolicContinuousCallback(eqs::Vector{Equation}, affect = nothing, iv = nothing; + affect_neg = affect, initialize = nothing, finalize = nothing, rootfind = SciMLBase.LeftRootFind, algeeqs = Equation[]) A [`ContinuousCallback`](@ref SciMLBase.ContinuousCallback) specified symbolically. Takes a vector of equations `eq` as well as the positive-edge `affect` and negative-edge `affect_neg` that apply when *any* of `eq` are satisfied. @@ -203,32 +203,31 @@ struct SymbolicContinuousCallback <: AbstractCallback function SymbolicContinuousCallback( conditions::Union{Equation, Vector{Equation}}, - affect = nothing, iv = nothing; + affect = nothing; affect_neg = affect, initialize = nothing, finalize = nothing, rootfind = SciMLBase.LeftRootFind, + iv = nothing, algeeqs = Equation[]) - affect isa AbstractVector && isnothing(iv) && @warn "No independent variable specified. If t appears in an affect equation explicitly, like x ~ t + 1, then this must be specified. Otherwise this can be disregarded." conditions = (conditions isa AbstractVector) ? conditions : [conditions] - new(conditions, make_affect(affect, iv; algeeqs), make_affect(affect_neg, iv; algeeqs), - make_affect(initialize, iv; algeeqs), make_affect(finalize, iv; algeeqs), rootfind) + new(conditions, make_affect(affect; iv, algeeqs), make_affect(affect_neg; iv, algeeqs), + make_affect(initialize; iv, algeeqs), make_affect(finalize; iv, algeeqs), rootfind) end # Default affect to nothing end -SymbolicContinuousCallback(p::Pair) = SymbolicContinuousCallback(p[1], p[2]) -SymbolicContinuousCallback(cb::SymbolicContinuousCallback, args...) = cb +SymbolicContinuousCallback(p::Pair, args...; kwargs...) = SymbolicContinuousCallback(p[1], p[2]) +SymbolicContinuousCallback(cb::SymbolicContinuousCallback, args...; kwargs...) = cb -make_affect(affect::Nothing, iv; kwargs...) = nothing -make_affect(affect::Tuple, iv; kwargs...) = FunctionalAffect(affect...) -make_affect(affect::NamedTuple, iv; kwargs...) = FunctionalAffect(; affect...) -make_affect(affect::FunctionalAffect, iv; kwargs...) = affect -make_affect(affect::AffectSystem, iv; kwargs...) = affect +make_affect(affect::Nothing; kwargs...) = nothing +make_affect(affect::Tuple; kwargs...) = FunctionalAffect(affect...) +make_affect(affect::NamedTuple; kwargs...) = FunctionalAffect(; affect...) +make_affect(affect::Affect; kwargs...) = affect -function make_affect(affect::Vector{Equation}, iv; algeeqs = Equation[]) +function make_affect(affect::Vector{Equation}; iv = nothing, algeeqs = Equation[]) isempty(affect) && return nothing - isempty(algeeqs) && @warn "No algebraic equations were found. If the system has no algebraic equations, this can be disregarded. Otherwise consider passing in `algeeqs` to the SymbolicContinuousCallbacks constructor." + isempty(algeeqs) && @warn "No algebraic equations were found. If the system has no algebraic equations, this can be disregarded. Otherwise pass in `algeeqs` to the SymbolicContinuousCallback constructor." affect = scalarize(affect) dvs = OrderedSet() @@ -243,6 +242,7 @@ function make_affect(affect::Vector{Equation}, iv; algeeqs = Equation[]) end if isnothing(iv) iv = isempty(dvs) ? iv : only(arguments(dvs[1])) + isnothing(iv) && @warn "No independent variable specified and could not be inferred. If the iv appears in an affect equation explicitly, like x ~ t + 1, then it must be specified as an argument to the SymbolicContinuousCallback or SymbolicDiscreteCallback constructor. Otherwise this warning can be disregarded." end # System parameters should become unknowns in the ImplicitDiscreteSystem. @@ -271,21 +271,21 @@ function make_affect(affect::Vector{Equation}, iv; algeeqs = Equation[]) # get accessed parameters p from Pre(p) in the callback parameters params = filter(isparameter, map(x -> only(arguments(unwrap(x))), cb_params)) # add unknowns to the map - for u in unknowns(affectsys) + for u in dvs aff_map[u] = u end - return AffectSystem(affectsys, unknowns(affectsys), params, discretes, aff_map) + return AffectSystem(affectsys, collect(dvs), params, discretes, aff_map) end -function make_affect(affect) +function make_affect(affect; kwargs...) error("Malformed affect $(affect). This should be a vector of equations or a tuple specifying a functional affect.") end """ Generate continuous callbacks. """ -function SymbolicContinuousCallbacks(events, algeeqs::Vector{Equation} = Equation[], iv = nothing) +function SymbolicContinuousCallbacks(events; algeeqs::Vector{Equation} = Equation[], iv = nothing) callbacks = SymbolicContinuousCallback[] isnothing(events) && return callbacks @@ -294,8 +294,7 @@ function SymbolicContinuousCallbacks(events, algeeqs::Vector{Equation} = Equatio for event in events cond, affs = event isa Pair ? (event[1], event[2]) : (event, nothing) - affect = make_affect(affs, iv; algeeqs) - push!(callbacks, SymbolicContinuousCallback(cond, affect)) + push!(callbacks, SymbolicContinuousCallback(cond, affs; iv, algeeqs)) end callbacks end @@ -380,7 +379,8 @@ end # TODO: Iterative callbacks """ - SymbolicDiscreteCallback(conditions::Vector{Equation}, affect) + SymbolicDiscreteCallback(conditions::Vector{Equation}, affect = nothing, iv = nothing; + initialize = nothing, finalize = nothing, algeeqs = Equation[]) A callback that triggers at the first timestep that the conditions are satisfied. @@ -388,6 +388,10 @@ The condition can be one of: - Δt::Real - periodic events with period Δt - ts::Vector{Real} - events trigger at these preset times given by `ts` - eqs::Vector{Equation} - events trigger when the condition evaluates to true + +Arguments: +- iv: The independent variable of the system. This must be specified if the independent variable appaers in one of the equations explicitly, as in x ~ t + 1. +- algeeqs: Algebraic equations of the system that must be satisfied after the callback occurs. """ struct SymbolicDiscreteCallback <: AbstractCallback conditions::Any @@ -397,19 +401,18 @@ struct SymbolicDiscreteCallback <: AbstractCallback function SymbolicDiscreteCallback( condition, affect = nothing; - initialize = nothing, finalize = nothing) + initialize = nothing, finalize = nothing, iv = nothing, algeeqs = Equation[]) c = is_timed_condition(condition) ? condition : value(scalarize(condition)) - isnothing(iv) && @warn "No independent variable specified. If t appears in an affect equation explicitly, like x ~ t + 1, then this must be specified. Otherwise this can be disregarded." - new(c, make_affect(affect), make_affect(initialize), - make_affect(finalize)) + new(c, make_affect(affect; iv, algeeqs), make_affect(initialize; iv, algeeqs), + make_affect(finalize; iv, algeeqs)) end # Default affect to nothing end """ Generate discrete callbacks. """ -function SymbolicDiscreteCallbacks(events, algeeqs::Vector{Equation} = Equation[], iv = nothing) +function SymbolicDiscreteCallbacks(events; algeeqs::Vector{Equation} = Equation[], iv = nothing) callbacks = SymbolicDiscreteCallback[] isnothing(events) && return callbacks @@ -418,8 +421,7 @@ function SymbolicDiscreteCallbacks(events, algeeqs::Vector{Equation} = Equation[ for event in events cond, affs = event isa Pair ? (event[1], event[2]) : (event, nothing) - affect = make_affect(affs, iv; algeeqs) - push!(callbacks, SymbolicDiscreteCallback(cond, affect)) + push!(callbacks, SymbolicDiscreteCallback(cond, affs; iv, algeeqs)) end callbacks end @@ -801,12 +803,13 @@ function compile_affect( ps = parameters(aff) dvs = unknowns(aff) + dvs_to_modify = setdiff(dvs, getfield.(observed(sys), :lhs)) if aff isa AffectSystem affsys = system(aff) aff_map = aff_to_sys(aff) sys_map = Dict([v => k for (k, v) in aff_map]) - build_initializeprob = has_alg_eqs(sys) + reinit = has_alg_eqs(sys) function affect!(integrator) pmap = Pair[] @@ -815,12 +818,11 @@ function compile_affect( pval = isparameter(p) ? integrator.ps[p] : integrator[p] push!(pmap, pre_p => pval) end - guesses = Pair[u => integrator[aff_map[u]] for u in updated_vals(aff)] - affprob = ImplicitDiscreteProblem(affsys, Pair[], (0, 1), pmap; guesses, build_initializeprob) + guesses = Pair[u => integrator[aff_map[u]] for u in unknowns(affsys)] + affprob = ImplicitDiscreteProblem(affsys, Pair[], (0, 1), pmap; guesses, build_initializeprob = reinit) affsol = init(affprob, SimpleIDSolve()) - for u in unknowns(aff) - @show u + for u in dvs_to_modify integrator[u] = affsol[sys_map[u]] end for p in discretes(aff) diff --git a/src/systems/diffeqs/odesystem.jl b/src/systems/diffeqs/odesystem.jl index 1f3d272328..a4a9cb45fe 100644 --- a/src/systems/diffeqs/odesystem.jl +++ b/src/systems/diffeqs/odesystem.jl @@ -336,9 +336,9 @@ function ODESystem(deqs::AbstractVector{<:Equation}, iv, dvs, ps; throw(ArgumentError("System names must be unique.")) end - alg_eqs = filter(eq -> eq.lhs isa Union{Symbolic, Number} && !isdiffeq(eq), deqs) - cont_callbacks = SymbolicContinuousCallbacks(continuous_events, alg_eqs, iv) - disc_callbacks = SymbolicDiscreteCallbacks(discrete_events, alg_eqs, iv) + algeeqs = filter(eq -> eq.lhs isa Union{Symbolic, Number} && !isdiffeq(eq), deqs) + cont_callbacks = SymbolicContinuousCallbacks(continuous_events; algeeqs, iv) + disc_callbacks = SymbolicDiscreteCallbacks(discrete_events; algeeqs, iv) if is_dde === nothing is_dde = _check_if_dde(deqs, iv′, systems) diff --git a/src/systems/diffeqs/sdesystem.jl b/src/systems/diffeqs/sdesystem.jl index 2698ac70f9..36a2fc2de8 100644 --- a/src/systems/diffeqs/sdesystem.jl +++ b/src/systems/diffeqs/sdesystem.jl @@ -270,9 +270,9 @@ function SDESystem(deqs::AbstractVector{<:Equation}, neqs::AbstractArray, iv, dv Wfact = RefValue(EMPTY_JAC) Wfact_t = RefValue(EMPTY_JAC) - alg_eqs = filter(eq -> eq.lhs isa Union{Symbolic, Number} && !isdiffeq(eq), deqs) - cont_callbacks = SymbolicContinuousCallbacks(continuous_events, alg_eqs, iv) - disc_callbacks = SymbolicDiscreteCallbacks(discrete_events, alg_eqs, iv) + algeeqs = filter(eq -> eq.lhs isa Union{Symbolic, Number} && !isdiffeq(eq), deqs) + cont_callbacks = SymbolicContinuousCallbacks(continuous_events; algeeqs, iv) + disc_callbacks = SymbolicDiscreteCallbacks(discrete_events; algeeqs, iv) if is_dde === nothing is_dde = _check_if_dde(deqs, iv′, systems) end diff --git a/src/systems/jumps/jumpsystem.jl b/src/systems/jumps/jumpsystem.jl index 07a41dd04f..91168fda39 100644 --- a/src/systems/jumps/jumpsystem.jl +++ b/src/systems/jumps/jumpsystem.jl @@ -230,8 +230,8 @@ function JumpSystem(eqs, iv, unknowns, ps; end end - cont_callbacks = SymbolicContinuousCallbacks(continuous_events, Equation[]) - disc_callbacks = SymbolicDiscreteCallbacks(discrete_events, Equation[]) + cont_callbacks = SymbolicContinuousCallbacks(continuous_events; iv) + disc_callbacks = SymbolicDiscreteCallbacks(discrete_events; iv) JumpSystem{typeof(ap)}(Threads.atomic_add!(SYSTEM_COUNT, UInt(1)), ap, iv′, us′, ps′, var_to_name, observed, name, description, systems, diff --git a/test/symbolic_events.jl b/test/symbolic_events.jl index 50cc345707..d7e219ccd1 100644 --- a/test/symbolic_events.jl +++ b/test/symbolic_events.jl @@ -18,34 +18,715 @@ eqs = [D(x) ~ 1] affect = [x ~ 0] affect_neg = [x ~ 1] -@testset "SymbolicContinuousCallback constructors" begin - e = SymbolicContinuousCallback(eqs[]) - @test e isa SymbolicContinuousCallback - @test isequal(equations(e), eqs) - @test e.affect == nothing - @test e.affect_neg == nothing - @test e.rootfind == SciMLBase.LeftRootFind - - e = SymbolicContinuousCallback(eqs) - @test e isa SymbolicContinuousCallback - @test isequal(equations(e), eqs) - @test e.affect == nothing - @test e.affect_neg == nothing - @test e.rootfind == SciMLBase.LeftRootFind - - e = SymbolicContinuousCallback(eqs, nothing) - @test e isa SymbolicContinuousCallback - @test isequal(equations(e), eqs) - @test e.affect == nothing - @test e.affect_neg == nothing - @test e.rootfind == SciMLBase.LeftRootFind - - e = SymbolicContinuousCallback(eqs[], nothing) - @test e isa SymbolicContinuousCallback - @test isequal(equations(e), eqs) - @test e.affect == nothing - @test e.affect_neg == nothing - @test e.rootfind == SciMLBase.LeftRootFind +#@testset "SymbolicContinuousCallback constructors" begin +# e = SymbolicContinuousCallback(eqs[]) +# @test e isa SymbolicContinuousCallback +# @test isequal(equations(e), eqs) +# @test e.affect == nothing +# @test e.affect_neg == nothing +# @test e.rootfind == SciMLBase.LeftRootFind +# +# e = SymbolicContinuousCallback(eqs) +# @test e isa SymbolicContinuousCallback +# @test isequal(equations(e), eqs) +# @test e.affect == nothing +# @test e.affect_neg == nothing +# @test e.rootfind == SciMLBase.LeftRootFind +# +# e = SymbolicContinuousCallback(eqs, nothing) +# @test e isa SymbolicContinuousCallback +# @test isequal(equations(e), eqs) +# @test e.affect == nothing +# @test e.affect_neg == nothing +# @test e.rootfind == SciMLBase.LeftRootFind +# +# e = SymbolicContinuousCallback(eqs[], nothing) +# @test e isa SymbolicContinuousCallback +# @test isequal(equations(e), eqs) +# @test e.affect == nothing +# @test e.affect_neg == nothing +# @test e.rootfind == SciMLBase.LeftRootFind +# +# e = SymbolicContinuousCallback(eqs => nothing) +# @test e isa SymbolicContinuousCallback +# @test isequal(equations(e), eqs) +# @test e.affect == nothing +# @test e.affect_neg == nothing +# @test e.rootfind == SciMLBase.LeftRootFind +# +# e = SymbolicContinuousCallback(eqs[] => nothing) +# @test e isa SymbolicContinuousCallback +# @test isequal(equations(e), eqs) +# @test e.affect == nothing +# @test e.affect_neg == nothing +# @test e.rootfind == SciMLBase.LeftRootFind +# +# ## With affect +# e = SymbolicContinuousCallback(eqs[], affect) +# @test e isa SymbolicContinuousCallback +# @test isequal(equations(e), eqs) +# @test observed(system(affects(e))) == affect +# @test observed(system(affect_negs(e))) == affect +# @test e.rootfind == SciMLBase.LeftRootFind +# +# # with only positive edge affect +# e = SymbolicContinuousCallback(eqs[], affect, affect_neg = nothing) +# @test e isa SymbolicContinuousCallback +# @test isequal(equations(e), eqs) +# @test observed(system(affects(e))) == affect +# @test isnothing(e.affect_neg) +# @test e.rootfind == SciMLBase.LeftRootFind +# +# # with explicit edge affects +# e = SymbolicContinuousCallback(eqs[], affect, affect_neg = affect_neg) +# @test e isa SymbolicContinuousCallback +# @test isequal(equations(e), eqs) +# @test observed(system(affects(e))) == affect +# @test observed(system(affect_negs(e))) == affect_neg +# @test e.rootfind == SciMLBase.LeftRootFind +# +# # with different root finding ops +# e = SymbolicContinuousCallback( +# eqs[], affect, affect_neg = affect_neg, rootfind = SciMLBase.LeftRootFind) +# @test e isa SymbolicContinuousCallback +# @test isequal(equations(e), eqs) +# @test e.rootfind == SciMLBase.LeftRootFind +# +# # test plural constructor +# e = SymbolicContinuousCallbacks(eqs[]) +# @test e isa Vector{SymbolicContinuousCallback} +# @test isequal(equations(e[]), eqs) +# @test e[].affect == nothing +# +# e = SymbolicContinuousCallbacks(eqs) +# @test e isa Vector{SymbolicContinuousCallback} +# @test isequal(equations(e[]), eqs) +# @test e[].affect == nothing +# +# e = SymbolicContinuousCallbacks(eqs[] => affect) +# @test e isa Vector{SymbolicContinuousCallback} +# @test isequal(equations(e[]), eqs) +# @test e[].affect isa AffectSystem +# +# e = SymbolicContinuousCallbacks(eqs => affect) +# @test e isa Vector{SymbolicContinuousCallback} +# @test isequal(equations(e[]), eqs) +# @test e[].affect isa AffectSystem +# +# e = SymbolicContinuousCallbacks([eqs[] => affect]) +# @test e isa Vector{SymbolicContinuousCallback} +# @test isequal(equations(e[]), eqs) +# @test e[].affect isa AffectSystem +# +# e = SymbolicContinuousCallbacks([eqs => affect]) +# @test e isa Vector{SymbolicContinuousCallback} +# @test isequal(equations(e[]), eqs) +# @test e[].affect isa AffectSystem +#end +# +#@testset "ImperativeAffect constructors" begin +# fmfa(o, x, i, c) = nothing +# m = ModelingToolkit.ImperativeAffect(fmfa) +# @test m isa ModelingToolkit.ImperativeAffect +# @test m.f == fmfa +# @test m.obs == [] +# @test m.obs_syms == [] +# @test m.modified == [] +# @test m.mod_syms == [] +# @test m.ctx === nothing +# +# m = ModelingToolkit.ImperativeAffect(fmfa, (;)) +# @test m isa ModelingToolkit.ImperativeAffect +# @test m.f == fmfa +# @test m.obs == [] +# @test m.obs_syms == [] +# @test m.modified == [] +# @test m.mod_syms == [] +# @test m.ctx === nothing +# +# m = ModelingToolkit.ImperativeAffect(fmfa, (; x)) +# @test m isa ModelingToolkit.ImperativeAffect +# @test m.f == fmfa +# @test isequal(m.obs, []) +# @test m.obs_syms == [] +# @test isequal(m.modified, [x]) +# @test m.mod_syms == [:x] +# @test m.ctx === nothing +# +# m = ModelingToolkit.ImperativeAffect(fmfa, (; y = x)) +# @test m isa ModelingToolkit.ImperativeAffect +# @test m.f == fmfa +# @test isequal(m.obs, []) +# @test m.obs_syms == [] +# @test isequal(m.modified, [x]) +# @test m.mod_syms == [:y] +# @test m.ctx === nothing +# +# m = ModelingToolkit.ImperativeAffect(fmfa; observed = (; y = x)) +# @test m isa ModelingToolkit.ImperativeAffect +# @test m.f == fmfa +# @test isequal(m.obs, [x]) +# @test m.obs_syms == [:y] +# @test m.modified == [] +# @test m.mod_syms == [] +# @test m.ctx === nothing +# +# m = ModelingToolkit.ImperativeAffect(fmfa; modified = (; x)) +# @test m isa ModelingToolkit.ImperativeAffect +# @test m.f == fmfa +# @test isequal(m.obs, []) +# @test m.obs_syms == [] +# @test isequal(m.modified, [x]) +# @test m.mod_syms == [:x] +# @test m.ctx === nothing +# +# m = ModelingToolkit.ImperativeAffect(fmfa; modified = (; y = x)) +# @test m isa ModelingToolkit.ImperativeAffect +# @test m.f == fmfa +# @test isequal(m.obs, []) +# @test m.obs_syms == [] +# @test isequal(m.modified, [x]) +# @test m.mod_syms == [:y] +# @test m.ctx === nothing +# +# m = ModelingToolkit.ImperativeAffect(fmfa, (; x), (; x)) +# @test m isa ModelingToolkit.ImperativeAffect +# @test m.f == fmfa +# @test isequal(m.obs, [x]) +# @test m.obs_syms == [:x] +# @test isequal(m.modified, [x]) +# @test m.mod_syms == [:x] +# @test m.ctx === nothing +# +# m = ModelingToolkit.ImperativeAffect(fmfa, (; y = x), (; y = x)) +# @test m isa ModelingToolkit.ImperativeAffect +# @test m.f == fmfa +# @test isequal(m.obs, [x]) +# @test m.obs_syms == [:y] +# @test isequal(m.modified, [x]) +# @test m.mod_syms == [:y] +# @test m.ctx === nothing +# +# m = ModelingToolkit.ImperativeAffect( +# fmfa; modified = (; y = x), observed = (; y = x)) +# @test m isa ModelingToolkit.ImperativeAffect +# @test m.f == fmfa +# @test isequal(m.obs, [x]) +# @test m.obs_syms == [:y] +# @test isequal(m.modified, [x]) +# @test m.mod_syms == [:y] +# @test m.ctx === nothing +# +# m = ModelingToolkit.ImperativeAffect( +# fmfa; modified = (; y = x), observed = (; y = x), ctx = 3) +# @test m isa ModelingToolkit.ImperativeAffect +# @test m.f == fmfa +# @test isequal(m.obs, [x]) +# @test m.obs_syms == [:y] +# @test isequal(m.modified, [x]) +# @test m.mod_syms == [:y] +# @test m.ctx === 3 +# +# m = ModelingToolkit.ImperativeAffect(fmfa, (; x), (; x), 3) +# @test m isa ModelingToolkit.ImperativeAffect +# @test m.f == fmfa +# @test isequal(m.obs, [x]) +# @test m.obs_syms == [:x] +# @test isequal(m.modified, [x]) +# @test m.mod_syms == [:x] +# @test m.ctx === 3 +#end + +#@testset "Condition Compilation" begin +# @named sys = ODESystem(eqs, t, continuous_events = [x ~ 1]) +# @test getfield(sys, :continuous_events)[] == +# SymbolicContinuousCallback(Equation[x ~ 1], nothing) +# @test isequal(equations(getfield(sys, :continuous_events))[], x ~ 1) +# fsys = flatten(sys) +# @test isequal(equations(getfield(fsys, :continuous_events))[], x ~ 1) +# +# @named sys2 = ODESystem([D(x) ~ 1], t, continuous_events = [x ~ 2], systems = [sys]) +# @test getfield(sys2, :continuous_events)[] == +# SymbolicContinuousCallback(Equation[x ~ 2], nothing) +# @test all(ModelingToolkit.continuous_events(sys2) .== [ +# SymbolicContinuousCallback(Equation[x ~ 2], nothing), +# SymbolicContinuousCallback(Equation[sys.x ~ 1], nothing) +# ]) +# +# @test isequal(equations(getfield(sys2, :continuous_events))[1], x ~ 2) +# @test length(ModelingToolkit.continuous_events(sys2)) == 2 +# @test isequal(equations(ModelingToolkit.continuous_events(sys2)[1])[], x ~ 2) +# @test isequal(equations(ModelingToolkit.continuous_events(sys2)[2])[], sys.x ~ 1) +# +# sys = complete(sys) +# sys_nosplit = complete(sys; split = false) +# sys2 = complete(sys2) +# +# # Test proper rootfinding +# prob = ODEProblem(sys, Pair[], (0.0, 2.0)) +# p0 = 0 +# t0 = 0 +# @test get_callback(prob) isa ModelingToolkit.DiffEqCallbacks.ContinuousCallback +# cb = ModelingToolkit.generate_continuous_callbacks(sys) +# cond = cb.condition +# out = [0.0] +# cond.f_iip(out, [0], p0, t0) +# @test out[] ≈ -1 # signature is u,p,t +# cond.f_iip(out, [1], p0, t0) +# @test out[] ≈ 0 # signature is u,p,t +# cond.f_iip(out, [2], p0, t0) +# @test out[] ≈ 1 # signature is u,p,t +# +# prob = ODEProblem(sys, Pair[], (0.0, 2.0)) +# prob_nosplit = ODEProblem(sys_nosplit, Pair[], (0.0, 2.0)) +# sol = solve(prob, Tsit5()) +# sol_nosplit = solve(prob_nosplit, Tsit5()) +# @test minimum(t -> abs(t - 1), sol.t) < 1e-10 # test that the solver stepped at the root +# @test minimum(t -> abs(t - 1), sol_nosplit.t) < 1e-10 # test that the solver stepped at the root +# +# # Test user-provided callback is respected +# test_callback = DiscreteCallback(x -> x, x -> x) +# prob = ODEProblem(sys, Pair[], (0.0, 2.0), callback = test_callback) +# prob_nosplit = ODEProblem(sys_nosplit, Pair[], (0.0, 2.0), callback = test_callback) +# cbs = get_callback(prob) +# cbs_nosplit = get_callback(prob_nosplit) +# @test cbs isa CallbackSet +# @test cbs.discrete_callbacks[1] == test_callback +# @test cbs_nosplit isa CallbackSet +# @test cbs_nosplit.discrete_callbacks[1] == test_callback +# +# prob = ODEProblem(sys2, Pair[], (0.0, 3.0)) +# cb = get_callback(prob) +# @test cb isa ModelingToolkit.DiffEqCallbacks.VectorContinuousCallback +# +# cond = cb.condition +# out = [0.0, 0.0] +# # the root to find is 2 +# cond.f_iip(out, [0, 0], p0, t0) +# @test out[1] ≈ -2 # signature is u,p,t +# cond.f_iip(out, [1, 0], p0, t0) +# @test out[1] ≈ -1 # signature is u,p,t +# cond.f_iip(out, [2, 0], p0, t0) # this should return 0 +# @test out[1] ≈ 0 # signature is u,p,t +# +# # the root to find is 1 +# out = [0.0, 0.0] +# cond.f_iip(out, [0, 0], p0, t0) +# @test out[2] ≈ -1 # signature is u,p,t +# cond.f_iip(out, [0, 1], p0, t0) # this should return 0 +# @test out[2] ≈ 0 # signature is u,p,t +# cond.f_iip(out, [0, 2], p0, t0) +# @test out[2] ≈ 1 # signature is u,p,t +# +# sol = solve(prob, Tsit5()) +# @test minimum(t -> abs(t - 1), sol.t) < 1e-10 # test that the solver stepped at the first root +# @test minimum(t -> abs(t - 2), sol.t) < 1e-10 # test that the solver stepped at the second root +# +# @named sys = ODESystem(eqs, t, continuous_events = [x ~ 1, x ~ 2]) # two root eqs using the same unknown +# sys = complete(sys) +# prob = ODEProblem(sys, Pair[], (0.0, 3.0)) +# @test get_callback(prob) isa ModelingToolkit.DiffEqCallbacks.VectorContinuousCallback +# sol = solve(prob, Tsit5()) +# @test minimum(t -> abs(t - 1), sol.t) < 1e-10 # test that the solver stepped at the first root +# @test minimum(t -> abs(t - 2), sol.t) < 1e-10 # test that the solver stepped at the second root +#end + +#@testset "Bouncing Ball" begin +# ###### 1D Bounce +# @variables x(t)=1 v(t)=0 +# +# root_eqs = [x ~ 0] +# affect = [v ~ -Pre(v)] +# +# @named ball = ODESystem( +# [D(x) ~ v +# D(v) ~ -9.8], t, continuous_events = root_eqs => affect) +# +# @test only(continuous_events(ball)) == +# SymbolicContinuousCallback(Equation[x ~ 0], Equation[v ~ -Pre(v)]) +# ball = structural_simplify(ball) +# +# @test length(ModelingToolkit.continuous_events(ball)) == 1 +# +# tspan = (0.0, 5.0) +# prob = ODEProblem(ball, Pair[], tspan) +# sol = solve(prob, Tsit5()) +# @test 0 <= minimum(sol[x]) <= 1e-10 # the ball never went through the floor but got very close +# +# ###### 2D bouncing ball +# @variables x(t)=1 y(t)=0 vx(t)=0 vy(t)=1 +# +# events = [[x ~ 0] => [vx ~ -Pre(vx)] +# [y ~ -1.5, y ~ 1.5] => [vy ~ -Pre(vy)]] +# +# @named ball = ODESystem( +# [D(x) ~ vx +# D(y) ~ vy +# D(vx) ~ -9.8 +# D(vy) ~ -0.01vy], t; continuous_events = events) +# +# _ball = ball +# ball = structural_simplify(_ball) +# ball_nosplit = structural_simplify(_ball; split = false) +# +# tspan = (0.0, 5.0) +# prob = ODEProblem(ball, Pair[], tspan) +# prob_nosplit = ODEProblem(ball_nosplit, Pair[], tspan) +# +# cb = get_callback(prob) +# @test cb isa ModelingToolkit.DiffEqCallbacks.VectorContinuousCallback +# @test getfield(ball, :continuous_events)[1] == +# SymbolicContinuousCallback(Equation[x ~ 0], Equation[vx ~ -Pre(vx)]) +# @test getfield(ball, :continuous_events)[2] == +# SymbolicContinuousCallback(Equation[y ~ -1.5, y ~ 1.5], Equation[vy ~ -Pre(vy)]) +# cond = cb.condition +# out = [0.0, 0.0, 0.0] +# p0 = 0. +# t0 = 0. +# cond.f_iip(out, [0, 0, 0, 0], p0, t0) +# @test out ≈ [0, 1.5, -1.5] +# +# sol = solve(prob, Tsit5()) +# sol_nosplit = solve(prob_nosplit, Tsit5()) +# @test 0 <= minimum(sol[x]) <= 1e-10 # the ball never went through the floor but got very close +# @test minimum(sol[y]) ≈ -1.5 # check wall conditions +# @test maximum(sol[y]) ≈ 1.5 # check wall conditions +# @test 0 <= minimum(sol_nosplit[x]) <= 1e-10 # the ball never went through the floor but got very close +# @test minimum(sol_nosplit[y]) ≈ -1.5 # check wall conditions +# @test maximum(sol_nosplit[y]) ≈ 1.5 # check wall conditions +# +# ## Test multi-variable affect +# # in this test, there are two variables affected by a single event. +# events = [[x ~ 0] => [vx ~ -Pre(vx), vy ~ -Pre(vy)]] +# +# @named ball = ODESystem([D(x) ~ vx +# D(y) ~ vy +# D(vx) ~ -1 +# D(vy) ~ 0], t; continuous_events = events) +# +# ball_nosplit = structural_simplify(ball) +# ball = structural_simplify(ball) +# +# tspan = (0.0, 5.0) +# prob = ODEProblem(ball, Pair[], tspan) +# prob_nosplit = ODEProblem(ball_nosplit, Pair[], tspan) +# sol = solve(prob, Tsit5()) +# sol_nosplit = solve(prob_nosplit, Tsit5()) +# @test 0 <= minimum(sol[x]) <= 1e-10 # the ball never went through the floor but got very close +# @test -minimum(sol[y]) ≈ maximum(sol[y]) ≈ sqrt(2) # the ball will never go further than √2 in either direction (gravity was changed to 1 to get this particular number) +# @test 0 <= minimum(sol_nosplit[x]) <= 1e-10 # the ball never went through the floor but got very close +# @test -minimum(sol_nosplit[y]) ≈ maximum(sol_nosplit[y]) ≈ sqrt(2) # the ball will never go further than √2 in either direction (gravity was changed to 1 to get this particular number) +#end +# +## issue https://github.com/SciML/ModelingToolkit.jl/issues/1386 +## tests that it works for ODAESystem +#@testset "ODAESystem" begin +# @variables vs(t) v(t) vmeasured(t) +# eq = [vs ~ sin(2pi * t) +# D(v) ~ vs - v +# D(vmeasured) ~ 0.0] +# ev = [sin(20pi * t) ~ 0.0] => [vmeasured ~ Pre(v)] +# @named sys = ODESystem(eq, t, continuous_events = ev) +# sys = structural_simplify(sys) +# prob = ODEProblem(sys, zeros(2), (0.0, 5.1)) +# sol = solve(prob, Tsit5()) +# @test all(minimum((0:0.1:5) .- sol.t', dims = 2) .< 0.0001) # test that the solver stepped every 0.1s as dictated by event +# @test sol([0.25])[vmeasured][] == sol([0.23])[vmeasured][] # test the hold property +#end +# +### https://github.com/SciML/ModelingToolkit.jl/issues/1528 +#@testset "Handle Empty Events" begin +# Dₜ = D +# +# @parameters u(t) [input = true] # Indicate that this is a controlled input +# @parameters y(t) [output = true] # Indicate that this is a measured output +# +# function Mass(; name, m = 1.0, p = 0, v = 0) +# ps = @parameters m = m +# sts = @variables pos(t)=p vel(t)=v +# eqs = Dₜ(pos) ~ vel +# ODESystem(eqs, t, [pos, vel], ps; name) +# end +# function Spring(; name, k = 1e4) +# ps = @parameters k = k +# @variables x(t) = 0 # Spring deflection +# ODESystem(Equation[], t, [x], ps; name) +# end +# function Damper(; name, c = 10) +# ps = @parameters c = c +# @variables vel(t) = 0 +# ODESystem(Equation[], t, [vel], ps; name) +# end +# function SpringDamper(; name, k = false, c = false) +# spring = Spring(; name = :spring, k) +# damper = Damper(; name = :damper, c) +# compose(ODESystem(Equation[], t; name), +# spring, damper) +# end +# connect_sd(sd, m1, m2) = [ +# sd.spring.x ~ m1.pos - m2.pos, sd.damper.vel ~ m1.vel - m2.vel] +# sd_force(sd) = -sd.spring.k * sd.spring.x - sd.damper.c * sd.damper.vel +# @named mass1 = Mass(; m = 1) +# @named mass2 = Mass(; m = 1) +# @named sd = SpringDamper(; k = 1000, c = 10) +# function Model(u, d = 0) +# eqs = [connect_sd(sd, mass1, mass2) +# Dₜ(mass1.vel) ~ (sd_force(sd) + u) / mass1.m +# Dₜ(mass2.vel) ~ (-sd_force(sd) + d) / mass2.m] +# @named _model = ODESystem(eqs, t; observed = [y ~ mass2.pos]) +# @named model = compose(_model, mass1, mass2, sd) +# end +# model = Model(sin(30t)) +# sys = structural_simplify(model) +# @test isempty(ModelingToolkit.continuous_events(sys)) +#end +# +#@testset "ODESystem Discrete Callbacks" begin +# function testsol(osys, u0, p, tspan; tstops = Float64[], paramtotest = nothing, +# kwargs...) +# oprob = ODEProblem(complete(osys), u0, tspan, p; kwargs...) +# sol = solve(oprob, Tsit5(); tstops = tstops, abstol = 1e-10, reltol = 1e-10) +# @test isapprox(sol(1.0000000001)[1] - sol(0.999999999)[1], 1.0; rtol = 1e-6) +# paramtotest === nothing || (@test sol.ps[paramtotest] == 1.0) +# @test isapprox(sol(4.0)[1], 2 * exp(-2.0)) +# sol +# end +# +# @parameters k t1 t2 +# @variables A(t) B(t) +# +# cond1 = (t == t1) +# affect1 = [A ~ Pre(A) + 1] +# cb1 = cond1 => affect1 +# cond2 = (t == t2) +# affect2 = [k ~ 1.0] +# cb2 = cond2 => affect2 +# +# ∂ₜ = D +# eqs = [∂ₜ(A) ~ -k * A] +# @named osys = ODESystem(eqs, t, [A], [k, t1, t2], discrete_events = [cb1, cb2]) +# u0 = [A => 1.0] +# p = [k => 0.0, t1 => 1.0, t2 => 2.0] +# tspan = (0.0, 4.0) +# testsol(osys, u0, p, tspan; tstops = [1.0, 2.0], paramtotest = k) +# +# cond1a = (t == t1) +# affect1a = [A ~ Pre(A) + 1, B ~ A] +# cb1a = cond1a => affect1a +# @named osys1 = ODESystem(eqs, t, [A, B], [k, t1, t2], discrete_events = [cb1a, cb2]) +# u0′ = [A => 1.0, B => 0.0] +# sol = testsol( +# osys1, u0′, p, tspan; tstops = [1.0, 2.0], check_length = false, paramtotest = k) +# @test sol(1.0000001, idxs = B) == 2.0 +# +# # same as above - but with set-time event syntax +# cb1‵ = [1.0] => affect1 # needs to be a Vector for the event to happen only once +# cb2‵ = [2.0] => affect2 +# @named osys‵ = ODESystem(eqs, t, [A], [k], discrete_events = [cb1‵, cb2‵]) +# testsol(osys‵, u0, p, tspan; paramtotest = k) +# +# # mixing discrete affects +# @named osys3 = ODESystem(eqs, t, [A], [k, t1, t2], discrete_events = [cb1, cb2‵]) +# testsol(osys3, 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 +# end +# cb2‵‵ = [2.0] => (affect!, [], [k], [k], nothing) +# @named osys4 = ODESystem(eqs, t, [A], [k, t1], discrete_events = [cb1, cb2‵‵]) +# oprob4 = ODEProblem(complete(osys4), u0, tspan, p) +# testsol(osys4, u0, p, tspan; tstops = [1.0], paramtotest = k) +# +# # mixing with symbolic condition in the func affect +# cb2‵‵‵ = (t == t2) => (affect!, [], [k], [k], nothing) +# @named osys5 = ODESystem(eqs, t, [A], [k, t1, t2], discrete_events = [cb1, cb2‵‵‵]) +# testsol(osys5, u0, p, tspan; tstops = [1.0, 2.0]) +# @named osys6 = ODESystem(eqs, t, [A], [k, t1, t2], discrete_events = [cb2‵‵‵, cb1]) +# testsol(osys6, u0, p, tspan; tstops = [1.0, 2.0], paramtotest = k) +# +# # mix a continuous event too +# cond3 = A ~ 0.1 +# affect3 = [k ~ 0.0] +# cb3 = cond3 => affect3 +# @named osys7 = ODESystem(eqs, t, [A], [k, t1, t2], discrete_events = [cb1, cb2‵‵‵], +# continuous_events = [cb3]) +# sol = testsol(osys7, u0, p, (0.0, 10.0); tstops = [1.0, 2.0]) +# @test isapprox(sol(10.0)[1], 0.1; atol = 1e-10, rtol = 1e-10) +#end +# +#@testset "SDESystem Discrete Callbacks" begin +# function testsol(ssys, u0, p, tspan; tstops = Float64[], paramtotest = nothing, +# kwargs...) +# sprob = SDEProblem(complete(ssys), u0, tspan, p; kwargs...) +# sol = solve(sprob, RI5(); tstops = tstops, abstol = 1e-10, reltol = 1e-10) +# @test isapprox(sol(1.0000000001)[1] - sol(0.999999999)[1], 1.0; rtol = 1e-4) +# paramtotest === nothing || (@test sol.ps[paramtotest] == 1.0) +# @test isapprox(sol(4.0)[1], 2 * exp(-2.0), atol = 1e-4) +# sol +# end +# +# @parameters k t1 t2 +# @variables A(t) B(t) +# +# cond1 = (t == t1) +# affect1 = [A ~ Pre(A) + 1] +# cb1 = cond1 => affect1 +# cond2 = (t == t2) +# affect2 = [k ~ 1.0] +# cb2 = cond2 => affect2 +# +# ∂ₜ = D +# eqs = [∂ₜ(A) ~ -k * A] +# @named ssys = SDESystem(eqs, [0.0], t, [A], [k, t1, t2], +# discrete_events = [cb1, cb2]) +# u0 = [A => 1.0] +# p = [k => 0.0, t1 => 1.0, t2 => 2.0] +# tspan = (0.0, 4.0) +# testsol(ssys, u0, p, tspan; tstops = [1.0, 2.0], paramtotest = k) +# +# cond1a = (t == t1) +# affect1a = [A ~ Pre(A) + 1, B ~ A] +# cb1a = cond1a => affect1a +# @named ssys1 = SDESystem(eqs, [0.0], t, [A, B], [k, t1, t2], +# discrete_events = [cb1a, cb2]) +# u0′ = [A => 1.0, B => 0.0] +# sol = testsol( +# ssys1, u0′, p, tspan; tstops = [1.0, 2.0], check_length = false, paramtotest = k) +# @test sol(1.0000001, idxs = 2) == 2.0 +# +# # same as above - but with set-time event syntax +# cb1‵ = [1.0] => affect1 # needs to be a Vector for the event to happen only once +# cb2‵ = [2.0] => affect2 +# @named ssys‵ = SDESystem(eqs, [0.0], t, [A], [k], discrete_events = [cb1‵, cb2‵]) +# testsol(ssys‵, u0, p, tspan; paramtotest = k) +# +# # mixing discrete affects +# @named ssys3 = SDESystem(eqs, [0.0], t, [A], [k, t1, t2], +# discrete_events = [cb1, cb2‵]) +# testsol(ssys3, u0, p, tspan; tstops = [1.0], paramtotest = k) +# +# # mixing with a func affect +# function affect!(integrator, u, p, ctx) +# setp(integrator, p.k)(integrator, 1.0) +# nothing +# end +# cb2‵‵ = [2.0] => (affect!, [], [k], [k], nothing) +# @named ssys4 = SDESystem(eqs, [0.0], t, [A], [k, t1], +# discrete_events = [cb1, cb2‵‵]) +# testsol(ssys4, u0, p, tspan; tstops = [1.0], paramtotest = k) +# +# # mixing with symbolic condition in the func affect +# cb2‵‵‵ = (t == t2) => (affect!, [], [k], [k], nothing) +# @named ssys5 = SDESystem(eqs, [0.0], t, [A], [k, t1, t2], +# discrete_events = [cb1, cb2‵‵‵]) +# testsol(ssys5, u0, p, tspan; tstops = [1.0, 2.0], paramtotest = k) +# @named ssys6 = SDESystem(eqs, [0.0], t, [A], [k, t1, t2], +# discrete_events = [cb2‵‵‵, cb1]) +# testsol(ssys6, u0, p, tspan; tstops = [1.0, 2.0], paramtotest = k) +# +# # mix a continuous event too +# cond3 = A ~ 0.1 +# affect3 = [k ~ 0.0] +# cb3 = cond3 => affect3 +# @named ssys7 = SDESystem(eqs, [0.0], t, [A], [k, t1, t2], +# discrete_events = [cb1, cb2‵‵‵], +# continuous_events = [cb3]) +# sol = testsol(ssys7, u0, p, (0.0, 10.0); tstops = [1.0, 2.0]) +# @test isapprox(sol(10.0)[1], 0.1; atol = 1e-10, rtol = 1e-10) +#end + +#@testset "JumpSystem Discrete Callbacks" begin +# function testsol(jsys, u0, p, tspan; tstops = Float64[], paramtotest = nothing, +# N = 40000, kwargs...) +# jsys = complete(jsys) +# dprob = DiscreteProblem(jsys, u0, tspan, p) +# jprob = JumpProblem(jsys, dprob, Direct(); kwargs...) +# sol = solve(jprob, SSAStepper(); tstops = tstops) +# @show sol +# @test (sol(1.000000000001)[1] - sol(0.99999999999)[1]) == 1 +# paramtotest === nothing || (@test sol.ps[paramtotest] == 1.0) +# @test sol(40.0)[1] == 0 +# sol +# end +# +# @parameters k t1 t2 +# @variables A(t) B(t) +# +# cond1 = (t == t1) +# affect1 = [A ~ Pre(A) + 1] +# cb1 = cond1 => affect1 +# cond2 = (t == t2) +# affect2 = [k ~ 1.0] +# cb2 = cond2 => affect2 +# +# eqs = [MassActionJump(k, [A => 1], [A => -1])] +# @named jsys = JumpSystem(eqs, t, [A], [k, t1, t2], discrete_events = [cb1, cb2]) +# u0 = [A => 1] +# p = [k => 0.0, t1 => 1.0, t2 => 2.0] +# tspan = (0.0, 40.0) +# testsol(jsys, u0, p, tspan; tstops = [1.0, 2.0], rng, paramtotest = k) +# +# cond1a = (t == t1) +# affect1a = [A ~ Pre(A) + 1, B ~ A] +# cb1a = cond1a => affect1a +# @named jsys1 = JumpSystem(eqs, t, [A, B], [k, t1, t2], discrete_events = [cb1a, cb2]) +# u0′ = [A => 1, B => 0] +# sol = testsol(jsys1, u0′, p, tspan; tstops = [1.0, 2.0], +# check_length = false, rng, paramtotest = k) +# @test sol(1.000000001, idxs = B) == 2 +# +# # same as above - but with set-time event syntax +# cb1‵ = [1.0] => affect1 # needs to be a Vector for the event to happen only once +# cb2‵ = [2.0] => affect2 +# @named jsys‵ = JumpSystem(eqs, t, [A], [k], discrete_events = [cb1‵, cb2‵]) +# testsol(jsys‵, u0, [p[1]], tspan; rng, paramtotest = k) +# +# # mixing discrete affects +# @named jsys3 = JumpSystem(eqs, t, [A], [k, t1, t2], discrete_events = [cb1, cb2‵]) +# 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 +# end +# cb2‵‵ = [2.0] => (affect!, [], [k], [k], nothing) +# @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) +# @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]) +# testsol(jsys6, u0, p, tspan; tstops = [1.0, 2.0], rng, paramtotest = k) +#end +# +#@testset "Namespacing" begin +# function oscillator_ce(k = 1.0; name) +# sts = @variables x(t)=1.0 v(t)=0.0 F(t) +# ps = @parameters k=k Θ=0.5 +# eqs = [D(x) ~ v, D(v) ~ -k * x + F] +# ev = [x ~ Θ] => [x ~ 1.0, v ~ 0.0] +# ODESystem(eqs, t, sts, ps, continuous_events = [ev]; name) +# end +# +# @named oscce = oscillator_ce() +# eqs = [oscce.F ~ 0] +# @named eqs_sys = ODESystem(eqs, t) +# @named oneosc_ce = compose(eqs_sys, oscce) +# oneosc_ce_simpl = structural_simplify(oneosc_ce) +# +# prob = ODEProblem(oneosc_ce_simpl, [], (0.0, 2.0), []) +# sol = solve(prob, Tsit5(), saveat = 0.1) +# +# @test typeof(oneosc_ce_simpl) == ODESystem +# @test sol[1, 6] < 1.0 # test whether x(t) decreases over time +# @test sol[1, 18] > 0.5 # test whether event happened +#end e = SymbolicContinuousCallback(eqs => nothing) @test e isa SymbolicContinuousCallback @@ -1330,3 +2011,7 @@ end sol2 = solve(ODEProblem(sys2, [], (0.0, 1.0)), Tsit5()) @test 100.0 ∈ sol2[sys2.wd2.θ] end + +# TO teste: +# - Functional affects reinitialize correctly +# - explicit equation of t in a functional affect From e10e5e4d3e25479ad88cd24fbe218043b013617e Mon Sep 17 00:00:00 2001 From: vyudu Date: Tue, 18 Mar 2025 16:20:46 -0400 Subject: [PATCH 043/122] fix NoInit() error --- Project.toml | 3 +- src/ModelingToolkit.jl | 1 + src/systems/callbacks.jl | 104 ++++++++++----------- src/systems/imperative_affect.jl | 5 +- test/symbolic_events.jl | 152 ++++++++++++++++++++++++++++++- 5 files changed, 205 insertions(+), 60 deletions(-) diff --git a/Project.toml b/Project.toml index 49035e30c6..58df778436 100644 --- a/Project.toml +++ b/Project.toml @@ -30,6 +30,7 @@ ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210" FunctionWrappers = "069b7b12-0de2-55c6-9aab-29f3d0a68a2e" FunctionWrappersWrappers = "77dc65aa-8811-40c2-897b-53d922fa7daf" Graphs = "86223c79-3864-5bf0-83f7-82e725a168b6" +ImplicitDiscreteSolve = "3263718b-31ed-49cf-8a0f-35a466e8af96" InteractiveUtils = "b77e0a4c-d291-57a0-90e8-8db25a27a240" JuliaFormatter = "98e50ef6-434e-11e9-1051-2b60c6c9e899" JumpProcesses = "ccbc3e58-028d-4f4c-8cd5-9ae44345cda5" @@ -42,6 +43,7 @@ NaNMath = "77ba4419-2d1f-58cd-9bb1-8ffee604a2e3" NonlinearSolve = "8913a72c-1f9b-4ce2-8d82-65094dcecaec" OffsetArrays = "6fe1bfb0-de20-5000-8ca7-80f57d26f881" OrderedCollections = "bac558e1-5e72-5ebc-8fee-abe8a469f55d" +OrdinaryDiffEqCore = "bbf590c4-e513-4bbe-9b18-05decba2e5d8" PrecompileTools = "aea7be01-6a6a-4083-8856-8a6e6704d82a" RecursiveArrayTools = "731186ca-8d62-57ce-b412-fbd966d074cd" Reexport = "189a3867-3050-52da-a836-e630ba90ab69" @@ -51,7 +53,6 @@ SciMLBase = "0bca4576-84f4-4d90-8ffe-ffa030f20462" SciMLStructures = "53ae85a6-f571-4167-b2af-e1d143709226" Serialization = "9e88b42a-f829-5b0c-bbe9-9e923198166b" Setfield = "efcf1570-3423-57d1-acb7-fd33fddbac46" -SimpleImplicitDiscreteSolve = "3263718b-31ed-49cf-8a0f-35a466e8af96" SimpleNonlinearSolve = "727e6d20-b764-4bd8-a329-72de5adea6c7" SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" SpecialFunctions = "276daf66-3868-5448-9aa4-cd146d93841b" diff --git a/src/ModelingToolkit.jl b/src/ModelingToolkit.jl index 05cb154cdd..7c16e52156 100644 --- a/src/ModelingToolkit.jl +++ b/src/ModelingToolkit.jl @@ -54,6 +54,7 @@ import Moshi using Moshi.Data: @data using NonlinearSolve import SCCNonlinearSolve +using ImplicitDiscreteSolve using Reexport using RecursiveArrayTools import Graphs: SimpleDiGraph, add_edge!, incidence_matrix diff --git a/src/systems/callbacks.jl b/src/systems/callbacks.jl index 0207f552a1..80556566a2 100644 --- a/src/systems/callbacks.jl +++ b/src/systems/callbacks.jl @@ -233,8 +233,9 @@ function make_affect(affect::Vector{Equation}; iv = nothing, algeeqs = Equation[ dvs = OrderedSet() params = OrderedSet() for eq in affect - !haspre(eq) && + if !haspre(eq) && !(symbolic_type(eq.rhs) === NotSymbolic()) @warn "Affect equation $eq has no `Pre` operator. As such it will be interpreted as an algebraic equation to be satisfied after the callback. If you intended to use the value of a variable x before the affect, use Pre(x)." + end collect_vars!(dvs, params, eq, iv; op = Pre) end for eq in algeeqs @@ -299,11 +300,11 @@ function SymbolicContinuousCallbacks(events; algeeqs::Vector{Equation} = Equatio callbacks end -function Base.show(io::IO, cb::SymbolicContinuousCallback) +function Base.show(io::IO, cb::AbstractCallback) indent = get(io, :indent, 0) iio = IOContext(io, :indent => indent + 1) - print(io, "SymbolicContinuousCallback(") - print(iio, "Equations:") + is_discrete(cb) ? print(io, "SymbolicDiscreteCallback(") : print(io, "SymbolicContinuousCallback(") + print(iio, "Conditions:") show(iio, equations(cb)) print(iio, "; ") if affects(cb) != nothing @@ -311,7 +312,7 @@ function Base.show(io::IO, cb::SymbolicContinuousCallback) show(iio, affects(cb)) print(iio, ", ") end - if affect_negs(cb) != nothing + if !is_discrete(cb) && affect_negs(cb) != nothing print(iio, "Negative-edge affect:") show(iio, affect_negs(cb)) print(iio, ", ") @@ -328,11 +329,11 @@ function Base.show(io::IO, cb::SymbolicContinuousCallback) print(iio, ")") end -function Base.show(io::IO, mime::MIME"text/plain", cb::SymbolicContinuousCallback) +function Base.show(io::IO, mime::MIME"text/plain", cb::AbstractCallback) indent = get(io, :indent, 0) iio = IOContext(io, :indent => indent + 1) - println(io, "SymbolicContinuousCallback:") - println(iio, "Equations:") + is_discrete(cb) ? println(io, "SymbolicDiscreteCallback:") : println(io, "SymbolicContinuousCallback:") + println(iio, "Conditions:") show(iio, mime, equations(cb)) print(iio, "\n") if affects(cb) != nothing @@ -340,7 +341,7 @@ function Base.show(io::IO, mime::MIME"text/plain", cb::SymbolicContinuousCallbac show(iio, mime, affects(cb)) print(iio, "\n") end - if affect_negs(cb) != nothing + if !is_discrete(cb) && affect_negs(cb) != nothing print(iio, "Negative-edge affect:\n") show(iio, mime, affect_negs(cb)) print(iio, "\n") @@ -394,8 +395,8 @@ Arguments: - algeeqs: Algebraic equations of the system that must be satisfied after the callback occurs. """ struct SymbolicDiscreteCallback <: AbstractCallback - conditions::Any - affect::Affect + conditions::Union{Real, Vector{<:Real}, Vector{Equation}} + affect::Union{Affect, Nothing} initialize::Union{Affect, Nothing} finalize::Union{Affect, Nothing} @@ -409,6 +410,9 @@ struct SymbolicDiscreteCallback <: AbstractCallback end # Default affect to nothing end +SymbolicDiscreteCallback(p::Pair, args...; kwargs...) = SymbolicDiscreteCallback(p[1], p[2]) +SymbolicDiscreteCallback(cb::SymbolicDiscreteCallback, args...; kwargs...) = cb + """ Generate discrete callbacks. """ @@ -438,29 +442,6 @@ function is_timed_condition(condition::T) where {T} end end -function Base.show(io::IO, db::SymbolicDiscreteCallback) - indent = get(io, :indent, 0) - iio = IOContext(io, :indent => indent + 1) - println(io, "SymbolicDiscreteCallback:") - println(iio, "Conditions:") - print(iio, "; ") - if affects(db) != nothing - print(iio, "Affect:") - show(iio, affects(db)) - print(iio, ", ") - end - if initialize_affects(db) != nothing - print(iio, "Initialization affect:") - show(iio, initialize_affects(db)) - print(iio, ", ") - end - if finalize_affects(db) != nothing - print(iio, "Finalization affect:") - show(iio, finalize_affects(db)) - end - print(iio, ")") -end - function vars!(vars, cb::SymbolicDiscreteCallback; op = Differential) if symbolic_type(conditions(cb)) == NotSymbolic if conditions(cb) isa AbstractArray @@ -529,7 +510,7 @@ function namespace_callback(cb::SymbolicDiscreteCallback, s)::SymbolicDiscreteCa end function Base.hash(cb::SymbolicContinuousCallback, s::UInt) - s = foldr(hash, cb.eqs, init = s) + s = foldr(hash, cb.conditions, init = s) s = hash(cb.affect, s) s = hash(cb.affect_neg, s) s = hash(cb.initialize, s) @@ -538,8 +519,8 @@ function Base.hash(cb::SymbolicContinuousCallback, s::UInt) end function Base.hash(cb::SymbolicDiscreteCallback, s::UInt) - s = hash(cb.condition, s) - s = hash(cb.affects, s) + s = foldr(hash, cb.conditions, init = s) + s = hash(cb.affect, s) s = hash(cb.initialize, s) hash(cb.finalize, s) end @@ -649,7 +630,9 @@ end """ Compile user-defined functional affect. """ -function compile_functional_affect(affect::FunctionalAffect, cb, sys, dvs, ps; kwargs...) +function compile_functional_affect(affect::FunctionalAffect, cb, 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)) @@ -686,7 +669,18 @@ is_discrete(cb::Vector{<:AbstractCallback}) = eltype(cb) isa SymbolicDiscreteCal function generate_continuous_callbacks(sys::AbstractSystem, dvs = unknowns(sys), ps = parameters(sys; initial_parameters = true); kwargs...) cbs = continuous_events(sys) isempty(cbs) && return nothing - generate_callback(cbs, sys; kwargs...) + cb_classes = Dict{SciMLBase.RootfindOpt, Vector{SymbolicContinuousCallback}}() + for cb in cbs + _cbs = get!(() -> SymbolicContinuousCallback[], cb_classes, cb.rootfind) + push!(_cbs, cb) + end + cb_classes = sort!(OrderedDict(cb_classes)) + compiled_callbacks = [generate_callback(cb, sys; kwargs...) for (rf, cb) in cb_classes] + if length(compiled_callbacks) == 1 + return only(compiled_callbacks) + else + return CallbackSet(compiled_callbacks...) + end end function generate_discrete_callbacks(sys::AbstractSystem, dvs = unknowns(sys), ps = parameters(sys; initial_parameters = true); kwargs...) @@ -716,9 +710,9 @@ function generate_callback(cbs::Vector{SymbolicContinuousCallback}, sys; kwargs. finals = [] for cb in cbs affect = compile_affect(cb.affect, cb, sys, default = (args...) -> ()) - push!(affects, affect) - push!(affect_negs, compile_affect(cb.affect_neg, cb, sys, default = affect)) + affect_neg = (cb.affect_neg === cb.affect) ? affect : compile_affect(cb.affect_neg, cb, sys, default = (args...) -> ()) + push!(affect_negs, affect_neg) push!(inits, compile_affect(cb.initialize, cb, sys, default = nothing)) push!(finals, compile_affect(cb.finalize, cb, sys, default = nothing)) end @@ -728,8 +722,6 @@ function generate_callback(cbs::Vector{SymbolicContinuousCallback}, sys; kwargs. eq2affect = reduce(vcat, [fill(i, num_eqs[i]) for i in eachindex(affects)]) eqs = reduce(vcat, eqs) - @assert length(eq2affect) == length(eqs) - @assert maximum(eq2affect) == length(affects) affect = function (integ, idx) affects[eq2affect[idx]](integ) @@ -744,7 +736,7 @@ function generate_callback(cbs::Vector{SymbolicContinuousCallback}, sys; kwargs. return VectorContinuousCallback( trigger, affect, affect_neg, length(eqs); initialize, finalize, - rootfind = cbs[1].rootfind, initializealg = SciMLBase.NoInit) + rootfind = cbs[1].rootfind, initializealg = SciMLBase.NoInit()) end function generate_callback(cb, sys; kwargs...) @@ -762,16 +754,16 @@ function generate_callback(cb, sys; kwargs...) if is_discrete(cb) if is_timed && conditions(cb) isa AbstractVector return PresetTimeCallback(trigger, affect; initialize, - finalize, initializealg = SciMLBase.NoInit) + finalize, initializealg = SciMLBase.NoInit()) elseif is_timed return PeriodicCallback(affect, trigger; initialize, finalize) else return DiscreteCallback(trigger, affect; initialize, - finalize, initializealg = SciMLBase.NoInit) + finalize, initializealg = SciMLBase.NoInit()) end else return ContinuousCallback(trigger, affect, affect_neg; initialize, finalize, - rootfind = cb.rootfind, initializealg = SciMLBase.NoInit) + rootfind = cb.rootfind, initializealg = SciMLBase.NoInit()) end end @@ -793,27 +785,25 @@ Notes """ function compile_affect( aff::Union{Nothing, Affect}, cb::AbstractCallback, sys::AbstractSystem; default = nothing, kwargs...) + isnothing(aff) && return default + save_idxs = if !(has_index_cache(sys) && (ic = get_index_cache(sys)) !== nothing) Int[] else get(ic.callback_to_clocks, cb, Int[]) end - isnothing(aff) && return default - - ps = parameters(aff) - dvs = unknowns(aff) - dvs_to_modify = setdiff(dvs, getfield.(observed(sys), :lhs)) - if aff isa AffectSystem affsys = system(aff) aff_map = aff_to_sys(aff) sys_map = Dict([v => k for (k, v) in aff_map]) reinit = has_alg_eqs(sys) + ps_to_modify = discretes(aff) + dvs_to_modify = setdiff(unknowns(aff), getfield.(observed(sys), :lhs)) function affect!(integrator) pmap = Pair[] - for pre_p in previous_vals(aff) + for pre_p in parameters(affsys) p = only(arguments(unwrap(pre_p))) pval = isparameter(p) ? integrator.ps[p] : integrator[p] push!(pmap, pre_p => pval) @@ -825,17 +815,17 @@ function compile_affect( for u in dvs_to_modify integrator[u] = affsol[sys_map[u]] end - for p in discretes(aff) + for p in ps_to_modify integrator.ps[p] = affsol[sys_map[p]] end for idx in save_idxs - SciMLBase.save_discretes!(integ, idx) + SciMLBase.save_discretes!(integrator, idx) end sys isa JumpSystem && reset_aggregated_jumps!(integrator) end elseif aff isa FunctionalAffect || aff isa ImperativeAffect - compile_functional_affect(aff, cb, sys, dvs, ps; kwargs...) + compile_functional_affect(aff, cb, sys; kwargs...) end end diff --git a/src/systems/imperative_affect.jl b/src/systems/imperative_affect.jl index 991a16a23a..c36b250fbf 100644 --- a/src/systems/imperative_affect.jl +++ b/src/systems/imperative_affect.jl @@ -155,7 +155,7 @@ function check_assignable(sys, sym) end end -function compile_functional_affect(affect::ImperativeAffect, cb, sys, dvs, ps; kwargs...) +function compile_functional_affect(affect::ImperativeAffect, cb, sys; kwargs...) #= Implementation sketch: generate observed function (oop), should save to a component array under obs_syms @@ -179,6 +179,9 @@ function compile_functional_affect(affect::ImperativeAffect, cb, sys, dvs, ps; k return (syms_dedup, exprs_dedup) end + dvs = unknowns(sys) + ps = parameters(sys) + obs_exprs = observed(affect) if !affect.skip_checks for oexpr in obs_exprs diff --git a/test/symbolic_events.jl b/test/symbolic_events.jl index d7e219ccd1..b55cd88889 100644 --- a/test/symbolic_events.jl +++ b/test/symbolic_events.jl @@ -728,6 +728,7 @@ affect_neg = [x ~ 1] # @test sol[1, 18] > 0.5 # test whether event happened #end +<<<<<<< HEAD e = SymbolicContinuousCallback(eqs => nothing) @test e isa SymbolicContinuousCallback @test isequal(equations(e), eqs) @@ -1556,6 +1557,155 @@ end @test sign.(cos.(required_crossings_c1 .- 1e-6)) == sign.(last.(cr1)) @test sign.(cos.(3 * (required_crossings_c2 .+ 1e-6))) == sign.(last.(cr2)) end +======= +#@testset "Additional SymbolicContinuousCallback options" begin +# # 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]) +# cr1 = [] +# cr2 = [] +# evt1 = ModelingToolkit.SymbolicContinuousCallback( +# [c1 ~ 0], (record_crossings, [c1 => :v], [], [], cr1)) +# evt2 = ModelingToolkit.SymbolicContinuousCallback( +# [c2 ~ 0], (record_crossings, [c2 => :v], [], [], cr2)) +# @named trigsys = ODESystem(eqs, t; continuous_events = [evt1, evt2]) +# trigsys_ss = structural_simplify(trigsys) +# prob = ODEProblem(trigsys_ss, [], (0.0, 2π)) +# sol = solve(prob, Tsit5()) +# required_crossings_c1 = [π / 2, 3 * π / 2] +# required_crossings_c2 = [π / 6, π / 2, 5 * π / 6, 7 * π / 6, 3 * π / 2, 11 * π / 6] +# @test maximum(abs.(first.(cr1) .- required_crossings_c1)) < 1e-4 +# @test maximum(abs.(first.(cr2) .- required_crossings_c2)) < 1e-4 +# @test sign.(cos.(required_crossings_c1 .- 1e-6)) == sign.(last.(cr1)) +# @test sign.(cos.(3 * (required_crossings_c2 .- 1e-6))) == sign.(last.(cr2)) +# +# # with neg affect (pos * neg + left root find) +# cr1p = [] +# cr2p = [] +# cr1n = [] +# cr2n = [] +# evt1 = ModelingToolkit.SymbolicContinuousCallback( +# [c1 ~ 0], (record_crossings, [c1 => :v], [], [], cr1p); +# affect_neg = (record_crossings, [c1 => :v], [], [], cr1n)) +# evt2 = ModelingToolkit.SymbolicContinuousCallback( +# [c2 ~ 0], (record_crossings, [c2 => :v], [], [], cr2p); +# affect_neg = (record_crossings, [c2 => :v], [], [], cr2n)) +# @named trigsys = ODESystem(eqs, t; continuous_events = [evt1, evt2]) +# trigsys_ss = structural_simplify(trigsys) +# prob = ODEProblem(trigsys_ss, [], (0.0, 2π)) +# sol = solve(prob, Tsit5(); dtmax = 0.01) +# c1_pc = filter((<=)(0) ∘ sin, required_crossings_c1) +# c1_nc = filter((>=)(0) ∘ sin, required_crossings_c1) +# c2_pc = filter(c -> -sin(3c) > 0, required_crossings_c2) +# c2_nc = filter(c -> -sin(3c) < 0, required_crossings_c2) +# @test maximum(abs.(c1_pc .- first.(cr1p))) < 1e-5 +# @test maximum(abs.(c1_nc .- first.(cr1n))) < 1e-5 +# @test maximum(abs.(c2_pc .- first.(cr2p))) < 1e-5 +# @test maximum(abs.(c2_nc .- first.(cr2n))) < 1e-5 +# @test sign.(cos.(c1_pc .- 1e-6)) == sign.(last.(cr1p)) +# @test sign.(cos.(c1_nc .- 1e-6)) == sign.(last.(cr1n)) +# @test sign.(cos.(3 * (c2_pc .- 1e-6))) == sign.(last.(cr2p)) +# @test sign.(cos.(3 * (c2_nc .- 1e-6))) == sign.(last.(cr2n)) +# +# # with nothing neg affect (pos * neg + left root find) +# cr1p = [] +# cr2p = [] +# evt1 = ModelingToolkit.SymbolicContinuousCallback( +# [c1 ~ 0], (record_crossings, [c1 => :v], [], [], cr1p); affect_neg = nothing) +# evt2 = ModelingToolkit.SymbolicContinuousCallback( +# [c2 ~ 0], (record_crossings, [c2 => :v], [], [], cr2p); affect_neg = nothing) +# @named trigsys = ODESystem(eqs, t; continuous_events = [evt1, evt2]) +# trigsys_ss = structural_simplify(trigsys) +# prob = ODEProblem(trigsys_ss, [], (0.0, 2π)) +# sol = solve(prob, Tsit5(); dtmax = 0.01) +# @test maximum(abs.(c1_pc .- first.(cr1p))) < 1e-5 +# @test maximum(abs.(c2_pc .- first.(cr2p))) < 1e-5 +# @test sign.(cos.(c1_pc .- 1e-6)) == sign.(last.(cr1p)) +# @test sign.(cos.(3 * (c2_pc .- 1e-6))) == sign.(last.(cr2p)) +# +# #mixed +# cr1p = [] +# cr2p = [] +# cr1n = [] +# cr2n = [] +# evt1 = ModelingToolkit.SymbolicContinuousCallback( +# [c1 ~ 0], (record_crossings, [c1 => :v], [], [], cr1p); affect_neg = nothing) +# evt2 = ModelingToolkit.SymbolicContinuousCallback( +# [c2 ~ 0], (record_crossings, [c2 => :v], [], [], cr2p); +# affect_neg = (record_crossings, [c2 => :v], [], [], cr2n)) +# @named trigsys = ODESystem(eqs, t; continuous_events = [evt1, evt2]) +# trigsys_ss = structural_simplify(trigsys) +# prob = ODEProblem(trigsys_ss, [], (0.0, 2π)) +# sol = solve(prob, Tsit5(); dtmax = 0.01) +# c1_pc = filter((<=)(0) ∘ sin, required_crossings_c1) +# c2_pc = filter(c -> -sin(3c) > 0, required_crossings_c2) +# c2_nc = filter(c -> -sin(3c) < 0, required_crossings_c2) +# @test maximum(abs.(c1_pc .- first.(cr1p))) < 1e-5 +# @test maximum(abs.(c2_pc .- first.(cr2p))) < 1e-5 +# @test maximum(abs.(c2_nc .- first.(cr2n))) < 1e-5 +# @test sign.(cos.(c1_pc .- 1e-6)) == sign.(last.(cr1p)) +# @test sign.(cos.(3 * (c2_pc .- 1e-6))) == sign.(last.(cr2p)) +# @test sign.(cos.(3 * (c2_nc .- 1e-6))) == sign.(last.(cr2n)) +# +# # baseline affect w/ right rootfind (pos + neg + right root find) +# @variables c1(t)=1.0 c2(t)=1.0 # c1 = cos(t), c2 = cos(3t) +# cr1 = [] +# cr2 = [] +# evt1 = ModelingToolkit.SymbolicContinuousCallback( +# [c1 ~ 0], (record_crossings, [c1 => :v], [], [], cr1); +# rootfind = SciMLBase.RightRootFind) +# evt2 = ModelingToolkit.SymbolicContinuousCallback( +# [c2 ~ 0], (record_crossings, [c2 => :v], [], [], cr2); +# rootfind = SciMLBase.RightRootFind) +# @named trigsys = ODESystem(eqs, t; continuous_events = [evt1, evt2]) +# trigsys_ss = structural_simplify(trigsys) +# prob = ODEProblem(trigsys_ss, [], (0.0, 2π)) +# sol = solve(prob, Tsit5(); dtmax = 0.01) +# required_crossings_c1 = [π / 2, 3 * π / 2] +# required_crossings_c2 = [π / 6, π / 2, 5 * π / 6, 7 * π / 6, 3 * π / 2, 11 * π / 6] +# @test maximum(abs.(first.(cr1) .- required_crossings_c1)) < 1e-4 +# @test maximum(abs.(first.(cr2) .- required_crossings_c2)) < 1e-4 +# @test sign.(cos.(required_crossings_c1 .+ 1e-6)) == sign.(last.(cr1)) +# @test sign.(cos.(3 * (required_crossings_c2 .+ 1e-6))) == sign.(last.(cr2)) +# +# # baseline affect w/ mixed rootfind (pos + neg + right root find) +# cr1 = [] +# cr2 = [] +# evt1 = ModelingToolkit.SymbolicContinuousCallback( +# [c1 ~ 0], (record_crossings, [c1 => :v], [], [], cr1); +# rootfind = SciMLBase.LeftRootFind) +# evt2 = ModelingToolkit.SymbolicContinuousCallback( +# [c2 ~ 0], (record_crossings, [c2 => :v], [], [], cr2); +# rootfind = SciMLBase.RightRootFind) +# @named trigsys = ODESystem(eqs, t; continuous_events = [evt1, evt2]) +# trigsys_ss = structural_simplify(trigsys) +# prob = ODEProblem(trigsys_ss, [], (0.0, 2π)) +# sol = solve(prob, Tsit5()) +# @test maximum(abs.(first.(cr1) .- required_crossings_c1)) < 1e-4 +# @test maximum(abs.(first.(cr2) .- required_crossings_c2)) < 1e-4 +# @test sign.(cos.(required_crossings_c1 .- 1e-6)) == sign.(last.(cr1)) +# @test sign.(cos.(3 * (required_crossings_c2 .+ 1e-6))) == sign.(last.(cr2)) +# +# #flip order and ensure results are okay +# cr1 = [] +# cr2 = [] +# evt1 = ModelingToolkit.SymbolicContinuousCallback( +# [c1 ~ 0], (record_crossings, [c1 => :v], [], [], cr1); +# rootfind = SciMLBase.LeftRootFind) +# evt2 = ModelingToolkit.SymbolicContinuousCallback( +# [c2 ~ 0], (record_crossings, [c2 => :v], [], [], cr2); +# rootfind = SciMLBase.RightRootFind) +# @named trigsys = ODESystem(eqs, t; continuous_events = [evt2, evt1]) +# trigsys_ss = structural_simplify(trigsys) +# prob = ODEProblem(trigsys_ss, [], (0.0, 2π)) +# sol = solve(prob, Tsit5()) +# @test maximum(abs.(first.(cr1) .- required_crossings_c1)) < 1e-4 +# @test maximum(abs.(first.(cr2) .- required_crossings_c2)) < 1e-4 +# @test sign.(cos.(required_crossings_c1 .- 1e-6)) == sign.(last.(cr1)) +# @test sign.(cos.(3 * (required_crossings_c2 .+ 1e-6))) == sign.(last.(cr2)) +#end +>>>>>>> 5de75ea625 (fix NoInit() error) @testset "Discrete event reinitialization (#3142)" begin @connector LiquidPort begin @@ -1642,7 +1792,7 @@ end @testset "Discrete variable timeseries" begin @variables x(t) @parameters a(t) b(t) c(t) - cb1 = [x ~ 1.0] => [a ~ -a] + cb1 = [x ~ 1.0] => [a ~ -Pre(a)] function save_affect!(integ, u, p, ctx) integ.ps[p.b] = 5.0 end From 095b6b690849bb6c66d691356527cc109296524b Mon Sep 17 00:00:00 2001 From: vyudu Date: Thu, 20 Mar 2025 14:55:28 -0400 Subject: [PATCH 044/122] fix: fix initialization and finalization affects --- .../bipartite_tearing/modia_tearing.jl | 6 +- src/structural_transformation/utils.jl | 1 + src/systems/callbacks.jl | 134 +++++++++++------- .../implicit_discrete_system.jl | 5 +- src/systems/imperative_affect.jl | 18 +-- src/systems/systemstructure.jl | 1 + 6 files changed, 93 insertions(+), 72 deletions(-) diff --git a/src/structural_transformation/bipartite_tearing/modia_tearing.jl b/src/structural_transformation/bipartite_tearing/modia_tearing.jl index 5da873afdf..59b32abd56 100644 --- a/src/structural_transformation/bipartite_tearing/modia_tearing.jl +++ b/src/structural_transformation/bipartite_tearing/modia_tearing.jl @@ -96,7 +96,7 @@ function tear_graph_modia(structure::SystemStructure, isder::F = nothing, ieqs = Int[] filtered_vars = BitSet() free_eqs = free_equations(graph, var_sccs, var_eq_matching, varfilter) - is_overdetemined = !isempty(free_eqs) + is_overdetermined = !isempty(free_eqs) for vars in var_sccs for var in vars if varfilter(var) @@ -112,7 +112,7 @@ function tear_graph_modia(structure::SystemStructure, isder::F = nothing, filtered_vars, isder) # If the systems is overdetemined, we cannot assume the free equations # will not form algebraic loops with equations in the sccs. - if !is_overdetemined + if !is_overdetermined vargraph.ne = 0 for var in vars vargraph.matching[var] = unassigned @@ -121,7 +121,7 @@ function tear_graph_modia(structure::SystemStructure, isder::F = nothing, empty!(ieqs) empty!(filtered_vars) end - if is_overdetemined + if is_overdetermined free_vars = findall(x -> !(x isa Int), var_eq_matching) tear_graph_block_modia!(var_eq_matching, ict, solvable_graph, free_eqs, BitSet(free_vars), isder) diff --git a/src/structural_transformation/utils.jl b/src/structural_transformation/utils.jl index 14628f2958..ebcb834bb1 100644 --- a/src/structural_transformation/utils.jl +++ b/src/structural_transformation/utils.jl @@ -218,6 +218,7 @@ function find_eq_solvables!(state::TearingState, ieq, to_rm = Int[], coeffs = no all_int_vars = true coeffs === nothing || empty!(coeffs) empty!(to_rm) + for j in 𝑠neighbors(graph, ieq) var = fullvars[j] isirreducible(var) && (all_int_vars = false; continue) diff --git a/src/systems/callbacks.jl b/src/systems/callbacks.jl index 80556566a2..054a1032b4 100644 --- a/src/systems/callbacks.jl +++ b/src/systems/callbacks.jl @@ -217,7 +217,7 @@ struct SymbolicContinuousCallback <: AbstractCallback end # Default affect to nothing end -SymbolicContinuousCallback(p::Pair, args...; kwargs...) = SymbolicContinuousCallback(p[1], p[2]) +SymbolicContinuousCallback(p::Pair, args...; kwargs...) = SymbolicContinuousCallback(p[1], p[2], args...; kwargs...) SymbolicContinuousCallback(cb::SymbolicContinuousCallback, args...; kwargs...) = cb make_affect(affect::Nothing; kwargs...) = nothing @@ -395,7 +395,7 @@ Arguments: - algeeqs: Algebraic equations of the system that must be satisfied after the callback occurs. """ struct SymbolicDiscreteCallback <: AbstractCallback - conditions::Union{Real, Vector{<:Real}, Vector{Equation}} + conditions::Any affect::Union{Affect, Nothing} initialize::Union{Affect, Nothing} finalize::Union{Affect, Nothing} @@ -410,7 +410,7 @@ struct SymbolicDiscreteCallback <: AbstractCallback end # Default affect to nothing end -SymbolicDiscreteCallback(p::Pair, args...; kwargs...) = SymbolicDiscreteCallback(p[1], p[2]) +SymbolicDiscreteCallback(p::Pair, args...; kwargs...) = SymbolicDiscreteCallback(p[1], p[2], args...; kwargs...) SymbolicDiscreteCallback(cb::SymbolicDiscreteCallback, args...; kwargs...) = cb """ @@ -630,7 +630,7 @@ end """ Compile user-defined functional affect. """ -function compile_functional_affect(affect::FunctionalAffect, cb, sys; kwargs...) +function compile_functional_affect(affect::FunctionalAffect, sys; kwargs...) dvs = unknowns(sys) ps = parameters(sys) dvs_ind = Dict(reverse(en) for en in enumerate(dvs)) @@ -639,11 +639,9 @@ function compile_functional_affect(affect::FunctionalAffect, cb, sys; kwargs...) 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)] - save_idxs = get(ic.callback_to_clocks, cb, Int[]) else ps_ind = Dict(reverse(en) for en in enumerate(ps)) p_inds = map(sym -> get(ps_ind, sym, sym), parameters(affect)) - save_idxs = Int[] end # HACK: filter out eliminated symbols. Not clear this is the right thing to do # (MTK should keep these symbols) @@ -652,13 +650,9 @@ function compile_functional_affect(affect::FunctionalAffect, cb, sys; kwargs...) 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), - save_idxs = save_idxs - function (integ) + let u = u, p = p, user_affect = func(affect), ctx = context(affect) + (integ) -> begin user_affect(integ, u, p, ctx) - for idx in save_idxs - SciMLBase.save_discretes!(integ, idx) - end end end end @@ -670,6 +664,8 @@ function generate_continuous_callbacks(sys::AbstractSystem, dvs = unknowns(sys), cbs = continuous_events(sys) isempty(cbs) && return nothing cb_classes = Dict{SciMLBase.RootfindOpt, Vector{SymbolicContinuousCallback}}() + + # Sort the callbacks by their rootfinding method for cb in cbs _cbs = get!(() -> SymbolicContinuousCallback[], cb_classes, cb.rootfind) push!(_cbs, cb) @@ -709,12 +705,12 @@ function generate_callback(cbs::Vector{SymbolicContinuousCallback}, sys; kwargs. inits = [] finals = [] for cb in cbs - affect = compile_affect(cb.affect, cb, sys, default = (args...) -> ()) + affect = compile_affect(cb.affect, cb, sys, default = nothing) push!(affects, affect) - affect_neg = (cb.affect_neg === cb.affect) ? affect : compile_affect(cb.affect_neg, cb, sys, default = (args...) -> ()) + affect_neg = (cb.affect_neg == cb.affect) ? affect : compile_affect(cb.affect_neg, cb, sys, default = nothing) push!(affect_negs, affect_neg) - push!(inits, compile_affect(cb.initialize, cb, sys, default = nothing)) - push!(finals, compile_affect(cb.finalize, cb, sys, default = nothing)) + push!(inits, compile_affect(cb.initialize, cb, sys; default = nothing, is_init = true)) + push!(finals, compile_affect(cb.finalize, cb, sys; default = nothing)) end # Since there may be different number of conditions and affects, @@ -746,10 +742,16 @@ function generate_callback(cb, sys; kwargs...) trigger = is_timed ? conditions(cb) : compile_condition(cb, sys, dvs, ps; kwargs...) affect = compile_affect(cb.affect, cb, sys, default = (args...) -> ()) - affect_neg = hasfield(typeof(cb), :affect_neg) ? - compile_affect(cb.affect_neg, cb, sys, default = affect) : nothing - initialize = compile_affect(cb.initialize, cb, sys, default = SciMLBase.INITIALIZE_DEFAULT) - finalize = compile_affect(cb.finalize, cb, sys, default = SciMLBase.FINALIZE_DEFAULT) + affect_neg = if is_discrete(cb) + nothing + else + (cb.affect == cb.affect_neg) ? affect : compile_affect(cb.affect_neg, cb, sys, default = nothing) + end + init = compile_affect(cb.initialize, cb, sys, default = SciMLBase.INITIALIZE_DEFAULT, is_init = true) + final = compile_affect(cb.finalize, cb, sys, default = SciMLBase.FINALIZE_DEFAULT) + + initialize = isnothing(cb.initialize) ? init : ((c, u, t, i) -> init(i)) + finalize = isnothing(cb.finalize) ? final : ((c, u, t, i) -> final(i)) if is_discrete(cb) if is_timed && conditions(cb) isa AbstractVector @@ -784,24 +786,73 @@ Notes - `kwargs` are passed through to `Symbolics.build_function`. """ function compile_affect( - aff::Union{Nothing, Affect}, cb::AbstractCallback, sys::AbstractSystem; default = nothing, kwargs...) - isnothing(aff) && return default - + aff::Union{Nothing, Affect}, cb::AbstractCallback, sys::AbstractSystem; default = nothing, is_init = false, kwargs...) save_idxs = if !(has_index_cache(sys) && (ic = get_index_cache(sys)) !== nothing) Int[] else get(ic.callback_to_clocks, cb, Int[]) end - if aff isa AffectSystem - affsys = system(aff) - aff_map = aff_to_sys(aff) - sys_map = Dict([v => k for (k, v) in aff_map]) - reinit = has_alg_eqs(sys) - ps_to_modify = discretes(aff) - dvs_to_modify = setdiff(unknowns(aff), getfield.(observed(sys), :lhs)) + f = if isnothing(aff) + default + elseif aff isa AffectSystem + compile_equational_affect(aff, sys) + elseif aff isa FunctionalAffect || aff isa ImperativeAffect + compile_functional_affect(aff, sys; kwargs...) + end + wrap_save_discretes(f, save_idxs; is_init) +end + +# Init can be: user defined function, nothing, or INITIALIZE_DEFAULT +function wrap_save_discretes(f, save_idxs; is_init = false) + if isempty(save_idxs) || f === SciMLBase.FINALIZE_DEFAULT || (isnothing(f) && !is_init) + return f + elseif f === SciMLBase.INITIALIZE_DEFAULT + let save_idxs = save_idxs + (c, u, t, i) -> begin + f(c, u, t, i) + for idx in save_idxs + SciMLBase.save_discretes!(i, idx) + end + end + end + else + let save_idxs = save_idxs + (i) -> begin + isnothing(f) || f(i) + for idx in save_idxs + SciMLBase.save_discretes!(i, idx) + end + end + end + end +end + +""" +Initialize and Finalize for VectorContinuousCallback. +""" +function compile_vector_optional_affect(funs, default) + all(isnothing, funs) && return default + return let funs = funs + function (cb, u, t, integ) + for func in funs + isnothing(func) ? continue : func(integ) + end + end + end +end + +function compile_equational_affect(aff::AffectSystem, sys; kwargs...) + affsys = system(aff) + aff_map = aff_to_sys(aff) + sys_map = Dict([v => k for (k, v) in aff_map]) + ps_to_modify = discretes(aff) + dvs_to_modify = setdiff(unknowns(aff), getfield.(observed(sys), :lhs)) + #TODO: Add an optimization for systems without algebraic equations - function affect!(integrator) + return let dvs_to_modify = dvs_to_modify, aff_map = aff_map, sys_map = sys_map, affsys = affsys, ps_to_modify = ps_to_modify + + @inline function affect!(integrator) pmap = Pair[] for pre_p in parameters(affsys) p = only(arguments(unwrap(pre_p))) @@ -809,7 +860,7 @@ function compile_affect( push!(pmap, pre_p => pval) end guesses = Pair[u => integrator[aff_map[u]] for u in unknowns(affsys)] - affprob = ImplicitDiscreteProblem(affsys, Pair[], (0, 1), pmap; guesses, build_initializeprob = reinit) + affprob = ImplicitDiscreteProblem(affsys, Pair[], (0, 1), pmap; guesses, build_initializeprob = false) affsol = init(affprob, SimpleIDSolve()) for u in dvs_to_modify @@ -818,28 +869,9 @@ function compile_affect( for p in ps_to_modify integrator.ps[p] = affsol[sys_map[p]] end - for idx in save_idxs - SciMLBase.save_discretes!(integrator, idx) - end sys isa JumpSystem && reset_aggregated_jumps!(integrator) end - elseif aff isa FunctionalAffect || aff isa ImperativeAffect - compile_functional_affect(aff, cb, sys; kwargs...) - end -end - -""" -Initialize and Finalize for VectorContinuousCallback. -""" -function compile_vector_optional_affect(funs, default) - all(isnothing, funs) && return default - return let funs = funs - function (cb, u, t, integ) - for func in funs - isnothing(func) ? continue : func(integ) - end - end end end diff --git a/src/systems/discrete_system/implicit_discrete_system.jl b/src/systems/discrete_system/implicit_discrete_system.jl index b977ba992e..768a3a2191 100644 --- a/src/systems/discrete_system/implicit_discrete_system.jl +++ b/src/systems/discrete_system/implicit_discrete_system.jl @@ -270,7 +270,7 @@ function flatten(sys::ImplicitDiscreteSystem, noeqs = false) end function generate_function( - sys::ImplicitDiscreteSystem, dvs = unknowns(sys), ps = parameters(sys); wrap_code = identity, kwargs...) + sys::ImplicitDiscreteSystem, dvs = unknowns(sys), ps = parameters(sys); wrap_code = identity, cachesyms::Tuple = (), kwargs...) iv = get_iv(sys) # Algebraic equations get shifted forward 1, to match with differential equations exprs = map(equations(sys)) do eq @@ -286,8 +286,9 @@ function generate_function( u_next = map(Shift(iv, 1), dvs) u = dvs + p = (reorder_parameters(sys, unwrap.(ps))..., cachesyms...) build_function_wrapper( - sys, exprs, u_next, u, ps..., iv; p_start = 3, extra_assignments, kwargs...) + sys, exprs, u_next, u, p..., iv; p_start = 3, extra_assignments, kwargs...) end function shift_u0map_forward(sys::ImplicitDiscreteSystem, u0map, defs) diff --git a/src/systems/imperative_affect.jl b/src/systems/imperative_affect.jl index c36b250fbf..81e4cf724f 100644 --- a/src/systems/imperative_affect.jl +++ b/src/systems/imperative_affect.jl @@ -109,10 +109,6 @@ function namespace_affect(affect::ImperativeAffect, s) affect.skip_checks) end -function compile_affect(affect::ImperativeAffect, cb, sys, dvs, ps; kwargs...) - compile_functional_affect(affect, cb, sys, dvs, ps; kwargs...) -end - function invalid_variables(sys, expr) filter(x -> !any(isequal(x), all_symbols(sys)), reduce(vcat, vars(expr); init = [])) end @@ -155,7 +151,7 @@ function check_assignable(sys, sym) end end -function compile_functional_affect(affect::ImperativeAffect, cb, sys; kwargs...) +function compile_functional_affect(affect::ImperativeAffect, sys; kwargs...) #= Implementation sketch: generate observed function (oop), should save to a component array under obs_syms @@ -235,14 +231,8 @@ function compile_functional_affect(affect::ImperativeAffect, cb, sys; kwargs...) upd_funs = NamedTuple{mod_names}((setu.((sys,), first.(mod_pairs))...,)) - if has_index_cache(sys) && (ic = get_index_cache(sys)) !== nothing - save_idxs = get(ic.callback_to_clocks, cb, Int[]) - else - save_idxs = Int[] - end - let user_affect = func(affect), ctx = context(affect) - function (integ) + @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) upd_component_array = NamedTuple{mod_names}(modvals) @@ -256,10 +246,6 @@ function compile_functional_affect(affect::ImperativeAffect, cb, sys; kwargs...) # write the new values back to the integrator _generated_writeback(integ, upd_funs, upd_vals) - - for idx in save_idxs - SciMLBase.save_discretes!(integ, idx) - end end end end diff --git a/src/systems/systemstructure.jl b/src/systems/systemstructure.jl index 08c95fbbb2..e70502574a 100644 --- a/src/systems/systemstructure.jl +++ b/src/systems/systemstructure.jl @@ -719,6 +719,7 @@ function _structural_simplify!(state::TearingState; simplify = false, inputs = Any[], outputs = Any[], disturbance_inputs = Any[], kwargs...) + if fully_determined isa Bool check_consistency &= fully_determined else From 424783133f03ae8de72d8f383de047a83f7f8363 Mon Sep 17 00:00:00 2001 From: vyudu Date: Thu, 20 Mar 2025 14:58:09 -0400 Subject: [PATCH 045/122] uncomment tests --- test/symbolic_events.jl | 903 ++-------------------------------------- 1 file changed, 35 insertions(+), 868 deletions(-) diff --git a/test/symbolic_events.jl b/test/symbolic_events.jl index b55cd88889..763dfcbbea 100644 --- a/test/symbolic_events.jl +++ b/test/symbolic_events.jl @@ -18,717 +18,35 @@ eqs = [D(x) ~ 1] affect = [x ~ 0] affect_neg = [x ~ 1] -#@testset "SymbolicContinuousCallback constructors" begin -# e = SymbolicContinuousCallback(eqs[]) -# @test e isa SymbolicContinuousCallback -# @test isequal(equations(e), eqs) -# @test e.affect == nothing -# @test e.affect_neg == nothing -# @test e.rootfind == SciMLBase.LeftRootFind -# -# e = SymbolicContinuousCallback(eqs) -# @test e isa SymbolicContinuousCallback -# @test isequal(equations(e), eqs) -# @test e.affect == nothing -# @test e.affect_neg == nothing -# @test e.rootfind == SciMLBase.LeftRootFind -# -# e = SymbolicContinuousCallback(eqs, nothing) -# @test e isa SymbolicContinuousCallback -# @test isequal(equations(e), eqs) -# @test e.affect == nothing -# @test e.affect_neg == nothing -# @test e.rootfind == SciMLBase.LeftRootFind -# -# e = SymbolicContinuousCallback(eqs[], nothing) -# @test e isa SymbolicContinuousCallback -# @test isequal(equations(e), eqs) -# @test e.affect == nothing -# @test e.affect_neg == nothing -# @test e.rootfind == SciMLBase.LeftRootFind -# -# e = SymbolicContinuousCallback(eqs => nothing) -# @test e isa SymbolicContinuousCallback -# @test isequal(equations(e), eqs) -# @test e.affect == nothing -# @test e.affect_neg == nothing -# @test e.rootfind == SciMLBase.LeftRootFind -# -# e = SymbolicContinuousCallback(eqs[] => nothing) -# @test e isa SymbolicContinuousCallback -# @test isequal(equations(e), eqs) -# @test e.affect == nothing -# @test e.affect_neg == nothing -# @test e.rootfind == SciMLBase.LeftRootFind -# -# ## With affect -# e = SymbolicContinuousCallback(eqs[], affect) -# @test e isa SymbolicContinuousCallback -# @test isequal(equations(e), eqs) -# @test observed(system(affects(e))) == affect -# @test observed(system(affect_negs(e))) == affect -# @test e.rootfind == SciMLBase.LeftRootFind -# -# # with only positive edge affect -# e = SymbolicContinuousCallback(eqs[], affect, affect_neg = nothing) -# @test e isa SymbolicContinuousCallback -# @test isequal(equations(e), eqs) -# @test observed(system(affects(e))) == affect -# @test isnothing(e.affect_neg) -# @test e.rootfind == SciMLBase.LeftRootFind -# -# # with explicit edge affects -# e = SymbolicContinuousCallback(eqs[], affect, affect_neg = affect_neg) -# @test e isa SymbolicContinuousCallback -# @test isequal(equations(e), eqs) -# @test observed(system(affects(e))) == affect -# @test observed(system(affect_negs(e))) == affect_neg -# @test e.rootfind == SciMLBase.LeftRootFind -# -# # with different root finding ops -# e = SymbolicContinuousCallback( -# eqs[], affect, affect_neg = affect_neg, rootfind = SciMLBase.LeftRootFind) -# @test e isa SymbolicContinuousCallback -# @test isequal(equations(e), eqs) -# @test e.rootfind == SciMLBase.LeftRootFind -# -# # test plural constructor -# e = SymbolicContinuousCallbacks(eqs[]) -# @test e isa Vector{SymbolicContinuousCallback} -# @test isequal(equations(e[]), eqs) -# @test e[].affect == nothing -# -# e = SymbolicContinuousCallbacks(eqs) -# @test e isa Vector{SymbolicContinuousCallback} -# @test isequal(equations(e[]), eqs) -# @test e[].affect == nothing -# -# e = SymbolicContinuousCallbacks(eqs[] => affect) -# @test e isa Vector{SymbolicContinuousCallback} -# @test isequal(equations(e[]), eqs) -# @test e[].affect isa AffectSystem -# -# e = SymbolicContinuousCallbacks(eqs => affect) -# @test e isa Vector{SymbolicContinuousCallback} -# @test isequal(equations(e[]), eqs) -# @test e[].affect isa AffectSystem -# -# e = SymbolicContinuousCallbacks([eqs[] => affect]) -# @test e isa Vector{SymbolicContinuousCallback} -# @test isequal(equations(e[]), eqs) -# @test e[].affect isa AffectSystem -# -# e = SymbolicContinuousCallbacks([eqs => affect]) -# @test e isa Vector{SymbolicContinuousCallback} -# @test isequal(equations(e[]), eqs) -# @test e[].affect isa AffectSystem -#end -# -#@testset "ImperativeAffect constructors" begin -# fmfa(o, x, i, c) = nothing -# m = ModelingToolkit.ImperativeAffect(fmfa) -# @test m isa ModelingToolkit.ImperativeAffect -# @test m.f == fmfa -# @test m.obs == [] -# @test m.obs_syms == [] -# @test m.modified == [] -# @test m.mod_syms == [] -# @test m.ctx === nothing -# -# m = ModelingToolkit.ImperativeAffect(fmfa, (;)) -# @test m isa ModelingToolkit.ImperativeAffect -# @test m.f == fmfa -# @test m.obs == [] -# @test m.obs_syms == [] -# @test m.modified == [] -# @test m.mod_syms == [] -# @test m.ctx === nothing -# -# m = ModelingToolkit.ImperativeAffect(fmfa, (; x)) -# @test m isa ModelingToolkit.ImperativeAffect -# @test m.f == fmfa -# @test isequal(m.obs, []) -# @test m.obs_syms == [] -# @test isequal(m.modified, [x]) -# @test m.mod_syms == [:x] -# @test m.ctx === nothing -# -# m = ModelingToolkit.ImperativeAffect(fmfa, (; y = x)) -# @test m isa ModelingToolkit.ImperativeAffect -# @test m.f == fmfa -# @test isequal(m.obs, []) -# @test m.obs_syms == [] -# @test isequal(m.modified, [x]) -# @test m.mod_syms == [:y] -# @test m.ctx === nothing -# -# m = ModelingToolkit.ImperativeAffect(fmfa; observed = (; y = x)) -# @test m isa ModelingToolkit.ImperativeAffect -# @test m.f == fmfa -# @test isequal(m.obs, [x]) -# @test m.obs_syms == [:y] -# @test m.modified == [] -# @test m.mod_syms == [] -# @test m.ctx === nothing -# -# m = ModelingToolkit.ImperativeAffect(fmfa; modified = (; x)) -# @test m isa ModelingToolkit.ImperativeAffect -# @test m.f == fmfa -# @test isequal(m.obs, []) -# @test m.obs_syms == [] -# @test isequal(m.modified, [x]) -# @test m.mod_syms == [:x] -# @test m.ctx === nothing -# -# m = ModelingToolkit.ImperativeAffect(fmfa; modified = (; y = x)) -# @test m isa ModelingToolkit.ImperativeAffect -# @test m.f == fmfa -# @test isequal(m.obs, []) -# @test m.obs_syms == [] -# @test isequal(m.modified, [x]) -# @test m.mod_syms == [:y] -# @test m.ctx === nothing -# -# m = ModelingToolkit.ImperativeAffect(fmfa, (; x), (; x)) -# @test m isa ModelingToolkit.ImperativeAffect -# @test m.f == fmfa -# @test isequal(m.obs, [x]) -# @test m.obs_syms == [:x] -# @test isequal(m.modified, [x]) -# @test m.mod_syms == [:x] -# @test m.ctx === nothing -# -# m = ModelingToolkit.ImperativeAffect(fmfa, (; y = x), (; y = x)) -# @test m isa ModelingToolkit.ImperativeAffect -# @test m.f == fmfa -# @test isequal(m.obs, [x]) -# @test m.obs_syms == [:y] -# @test isequal(m.modified, [x]) -# @test m.mod_syms == [:y] -# @test m.ctx === nothing -# -# m = ModelingToolkit.ImperativeAffect( -# fmfa; modified = (; y = x), observed = (; y = x)) -# @test m isa ModelingToolkit.ImperativeAffect -# @test m.f == fmfa -# @test isequal(m.obs, [x]) -# @test m.obs_syms == [:y] -# @test isequal(m.modified, [x]) -# @test m.mod_syms == [:y] -# @test m.ctx === nothing -# -# m = ModelingToolkit.ImperativeAffect( -# fmfa; modified = (; y = x), observed = (; y = x), ctx = 3) -# @test m isa ModelingToolkit.ImperativeAffect -# @test m.f == fmfa -# @test isequal(m.obs, [x]) -# @test m.obs_syms == [:y] -# @test isequal(m.modified, [x]) -# @test m.mod_syms == [:y] -# @test m.ctx === 3 -# -# m = ModelingToolkit.ImperativeAffect(fmfa, (; x), (; x), 3) -# @test m isa ModelingToolkit.ImperativeAffect -# @test m.f == fmfa -# @test isequal(m.obs, [x]) -# @test m.obs_syms == [:x] -# @test isequal(m.modified, [x]) -# @test m.mod_syms == [:x] -# @test m.ctx === 3 -#end - -#@testset "Condition Compilation" begin -# @named sys = ODESystem(eqs, t, continuous_events = [x ~ 1]) -# @test getfield(sys, :continuous_events)[] == -# SymbolicContinuousCallback(Equation[x ~ 1], nothing) -# @test isequal(equations(getfield(sys, :continuous_events))[], x ~ 1) -# fsys = flatten(sys) -# @test isequal(equations(getfield(fsys, :continuous_events))[], x ~ 1) -# -# @named sys2 = ODESystem([D(x) ~ 1], t, continuous_events = [x ~ 2], systems = [sys]) -# @test getfield(sys2, :continuous_events)[] == -# SymbolicContinuousCallback(Equation[x ~ 2], nothing) -# @test all(ModelingToolkit.continuous_events(sys2) .== [ -# SymbolicContinuousCallback(Equation[x ~ 2], nothing), -# SymbolicContinuousCallback(Equation[sys.x ~ 1], nothing) -# ]) -# -# @test isequal(equations(getfield(sys2, :continuous_events))[1], x ~ 2) -# @test length(ModelingToolkit.continuous_events(sys2)) == 2 -# @test isequal(equations(ModelingToolkit.continuous_events(sys2)[1])[], x ~ 2) -# @test isequal(equations(ModelingToolkit.continuous_events(sys2)[2])[], sys.x ~ 1) -# -# sys = complete(sys) -# sys_nosplit = complete(sys; split = false) -# sys2 = complete(sys2) -# -# # Test proper rootfinding -# prob = ODEProblem(sys, Pair[], (0.0, 2.0)) -# p0 = 0 -# t0 = 0 -# @test get_callback(prob) isa ModelingToolkit.DiffEqCallbacks.ContinuousCallback -# cb = ModelingToolkit.generate_continuous_callbacks(sys) -# cond = cb.condition -# out = [0.0] -# cond.f_iip(out, [0], p0, t0) -# @test out[] ≈ -1 # signature is u,p,t -# cond.f_iip(out, [1], p0, t0) -# @test out[] ≈ 0 # signature is u,p,t -# cond.f_iip(out, [2], p0, t0) -# @test out[] ≈ 1 # signature is u,p,t -# -# prob = ODEProblem(sys, Pair[], (0.0, 2.0)) -# prob_nosplit = ODEProblem(sys_nosplit, Pair[], (0.0, 2.0)) -# sol = solve(prob, Tsit5()) -# sol_nosplit = solve(prob_nosplit, Tsit5()) -# @test minimum(t -> abs(t - 1), sol.t) < 1e-10 # test that the solver stepped at the root -# @test minimum(t -> abs(t - 1), sol_nosplit.t) < 1e-10 # test that the solver stepped at the root -# -# # Test user-provided callback is respected -# test_callback = DiscreteCallback(x -> x, x -> x) -# prob = ODEProblem(sys, Pair[], (0.0, 2.0), callback = test_callback) -# prob_nosplit = ODEProblem(sys_nosplit, Pair[], (0.0, 2.0), callback = test_callback) -# cbs = get_callback(prob) -# cbs_nosplit = get_callback(prob_nosplit) -# @test cbs isa CallbackSet -# @test cbs.discrete_callbacks[1] == test_callback -# @test cbs_nosplit isa CallbackSet -# @test cbs_nosplit.discrete_callbacks[1] == test_callback -# -# prob = ODEProblem(sys2, Pair[], (0.0, 3.0)) -# cb = get_callback(prob) -# @test cb isa ModelingToolkit.DiffEqCallbacks.VectorContinuousCallback -# -# cond = cb.condition -# out = [0.0, 0.0] -# # the root to find is 2 -# cond.f_iip(out, [0, 0], p0, t0) -# @test out[1] ≈ -2 # signature is u,p,t -# cond.f_iip(out, [1, 0], p0, t0) -# @test out[1] ≈ -1 # signature is u,p,t -# cond.f_iip(out, [2, 0], p0, t0) # this should return 0 -# @test out[1] ≈ 0 # signature is u,p,t -# -# # the root to find is 1 -# out = [0.0, 0.0] -# cond.f_iip(out, [0, 0], p0, t0) -# @test out[2] ≈ -1 # signature is u,p,t -# cond.f_iip(out, [0, 1], p0, t0) # this should return 0 -# @test out[2] ≈ 0 # signature is u,p,t -# cond.f_iip(out, [0, 2], p0, t0) -# @test out[2] ≈ 1 # signature is u,p,t -# -# sol = solve(prob, Tsit5()) -# @test minimum(t -> abs(t - 1), sol.t) < 1e-10 # test that the solver stepped at the first root -# @test minimum(t -> abs(t - 2), sol.t) < 1e-10 # test that the solver stepped at the second root -# -# @named sys = ODESystem(eqs, t, continuous_events = [x ~ 1, x ~ 2]) # two root eqs using the same unknown -# sys = complete(sys) -# prob = ODEProblem(sys, Pair[], (0.0, 3.0)) -# @test get_callback(prob) isa ModelingToolkit.DiffEqCallbacks.VectorContinuousCallback -# sol = solve(prob, Tsit5()) -# @test minimum(t -> abs(t - 1), sol.t) < 1e-10 # test that the solver stepped at the first root -# @test minimum(t -> abs(t - 2), sol.t) < 1e-10 # test that the solver stepped at the second root -#end - -#@testset "Bouncing Ball" begin -# ###### 1D Bounce -# @variables x(t)=1 v(t)=0 -# -# root_eqs = [x ~ 0] -# affect = [v ~ -Pre(v)] -# -# @named ball = ODESystem( -# [D(x) ~ v -# D(v) ~ -9.8], t, continuous_events = root_eqs => affect) -# -# @test only(continuous_events(ball)) == -# SymbolicContinuousCallback(Equation[x ~ 0], Equation[v ~ -Pre(v)]) -# ball = structural_simplify(ball) -# -# @test length(ModelingToolkit.continuous_events(ball)) == 1 -# -# tspan = (0.0, 5.0) -# prob = ODEProblem(ball, Pair[], tspan) -# sol = solve(prob, Tsit5()) -# @test 0 <= minimum(sol[x]) <= 1e-10 # the ball never went through the floor but got very close -# -# ###### 2D bouncing ball -# @variables x(t)=1 y(t)=0 vx(t)=0 vy(t)=1 -# -# events = [[x ~ 0] => [vx ~ -Pre(vx)] -# [y ~ -1.5, y ~ 1.5] => [vy ~ -Pre(vy)]] -# -# @named ball = ODESystem( -# [D(x) ~ vx -# D(y) ~ vy -# D(vx) ~ -9.8 -# D(vy) ~ -0.01vy], t; continuous_events = events) -# -# _ball = ball -# ball = structural_simplify(_ball) -# ball_nosplit = structural_simplify(_ball; split = false) -# -# tspan = (0.0, 5.0) -# prob = ODEProblem(ball, Pair[], tspan) -# prob_nosplit = ODEProblem(ball_nosplit, Pair[], tspan) -# -# cb = get_callback(prob) -# @test cb isa ModelingToolkit.DiffEqCallbacks.VectorContinuousCallback -# @test getfield(ball, :continuous_events)[1] == -# SymbolicContinuousCallback(Equation[x ~ 0], Equation[vx ~ -Pre(vx)]) -# @test getfield(ball, :continuous_events)[2] == -# SymbolicContinuousCallback(Equation[y ~ -1.5, y ~ 1.5], Equation[vy ~ -Pre(vy)]) -# cond = cb.condition -# out = [0.0, 0.0, 0.0] -# p0 = 0. -# t0 = 0. -# cond.f_iip(out, [0, 0, 0, 0], p0, t0) -# @test out ≈ [0, 1.5, -1.5] -# -# sol = solve(prob, Tsit5()) -# sol_nosplit = solve(prob_nosplit, Tsit5()) -# @test 0 <= minimum(sol[x]) <= 1e-10 # the ball never went through the floor but got very close -# @test minimum(sol[y]) ≈ -1.5 # check wall conditions -# @test maximum(sol[y]) ≈ 1.5 # check wall conditions -# @test 0 <= minimum(sol_nosplit[x]) <= 1e-10 # the ball never went through the floor but got very close -# @test minimum(sol_nosplit[y]) ≈ -1.5 # check wall conditions -# @test maximum(sol_nosplit[y]) ≈ 1.5 # check wall conditions -# -# ## Test multi-variable affect -# # in this test, there are two variables affected by a single event. -# events = [[x ~ 0] => [vx ~ -Pre(vx), vy ~ -Pre(vy)]] -# -# @named ball = ODESystem([D(x) ~ vx -# D(y) ~ vy -# D(vx) ~ -1 -# D(vy) ~ 0], t; continuous_events = events) -# -# ball_nosplit = structural_simplify(ball) -# ball = structural_simplify(ball) -# -# tspan = (0.0, 5.0) -# prob = ODEProblem(ball, Pair[], tspan) -# prob_nosplit = ODEProblem(ball_nosplit, Pair[], tspan) -# sol = solve(prob, Tsit5()) -# sol_nosplit = solve(prob_nosplit, Tsit5()) -# @test 0 <= minimum(sol[x]) <= 1e-10 # the ball never went through the floor but got very close -# @test -minimum(sol[y]) ≈ maximum(sol[y]) ≈ sqrt(2) # the ball will never go further than √2 in either direction (gravity was changed to 1 to get this particular number) -# @test 0 <= minimum(sol_nosplit[x]) <= 1e-10 # the ball never went through the floor but got very close -# @test -minimum(sol_nosplit[y]) ≈ maximum(sol_nosplit[y]) ≈ sqrt(2) # the ball will never go further than √2 in either direction (gravity was changed to 1 to get this particular number) -#end -# -## issue https://github.com/SciML/ModelingToolkit.jl/issues/1386 -## tests that it works for ODAESystem -#@testset "ODAESystem" begin -# @variables vs(t) v(t) vmeasured(t) -# eq = [vs ~ sin(2pi * t) -# D(v) ~ vs - v -# D(vmeasured) ~ 0.0] -# ev = [sin(20pi * t) ~ 0.0] => [vmeasured ~ Pre(v)] -# @named sys = ODESystem(eq, t, continuous_events = ev) -# sys = structural_simplify(sys) -# prob = ODEProblem(sys, zeros(2), (0.0, 5.1)) -# sol = solve(prob, Tsit5()) -# @test all(minimum((0:0.1:5) .- sol.t', dims = 2) .< 0.0001) # test that the solver stepped every 0.1s as dictated by event -# @test sol([0.25])[vmeasured][] == sol([0.23])[vmeasured][] # test the hold property -#end -# -### https://github.com/SciML/ModelingToolkit.jl/issues/1528 -#@testset "Handle Empty Events" begin -# Dₜ = D -# -# @parameters u(t) [input = true] # Indicate that this is a controlled input -# @parameters y(t) [output = true] # Indicate that this is a measured output -# -# function Mass(; name, m = 1.0, p = 0, v = 0) -# ps = @parameters m = m -# sts = @variables pos(t)=p vel(t)=v -# eqs = Dₜ(pos) ~ vel -# ODESystem(eqs, t, [pos, vel], ps; name) -# end -# function Spring(; name, k = 1e4) -# ps = @parameters k = k -# @variables x(t) = 0 # Spring deflection -# ODESystem(Equation[], t, [x], ps; name) -# end -# function Damper(; name, c = 10) -# ps = @parameters c = c -# @variables vel(t) = 0 -# ODESystem(Equation[], t, [vel], ps; name) -# end -# function SpringDamper(; name, k = false, c = false) -# spring = Spring(; name = :spring, k) -# damper = Damper(; name = :damper, c) -# compose(ODESystem(Equation[], t; name), -# spring, damper) -# end -# connect_sd(sd, m1, m2) = [ -# sd.spring.x ~ m1.pos - m2.pos, sd.damper.vel ~ m1.vel - m2.vel] -# sd_force(sd) = -sd.spring.k * sd.spring.x - sd.damper.c * sd.damper.vel -# @named mass1 = Mass(; m = 1) -# @named mass2 = Mass(; m = 1) -# @named sd = SpringDamper(; k = 1000, c = 10) -# function Model(u, d = 0) -# eqs = [connect_sd(sd, mass1, mass2) -# Dₜ(mass1.vel) ~ (sd_force(sd) + u) / mass1.m -# Dₜ(mass2.vel) ~ (-sd_force(sd) + d) / mass2.m] -# @named _model = ODESystem(eqs, t; observed = [y ~ mass2.pos]) -# @named model = compose(_model, mass1, mass2, sd) -# end -# model = Model(sin(30t)) -# sys = structural_simplify(model) -# @test isempty(ModelingToolkit.continuous_events(sys)) -#end -# -#@testset "ODESystem Discrete Callbacks" begin -# function testsol(osys, u0, p, tspan; tstops = Float64[], paramtotest = nothing, -# kwargs...) -# oprob = ODEProblem(complete(osys), u0, tspan, p; kwargs...) -# sol = solve(oprob, Tsit5(); tstops = tstops, abstol = 1e-10, reltol = 1e-10) -# @test isapprox(sol(1.0000000001)[1] - sol(0.999999999)[1], 1.0; rtol = 1e-6) -# paramtotest === nothing || (@test sol.ps[paramtotest] == 1.0) -# @test isapprox(sol(4.0)[1], 2 * exp(-2.0)) -# sol -# end -# -# @parameters k t1 t2 -# @variables A(t) B(t) -# -# cond1 = (t == t1) -# affect1 = [A ~ Pre(A) + 1] -# cb1 = cond1 => affect1 -# cond2 = (t == t2) -# affect2 = [k ~ 1.0] -# cb2 = cond2 => affect2 -# -# ∂ₜ = D -# eqs = [∂ₜ(A) ~ -k * A] -# @named osys = ODESystem(eqs, t, [A], [k, t1, t2], discrete_events = [cb1, cb2]) -# u0 = [A => 1.0] -# p = [k => 0.0, t1 => 1.0, t2 => 2.0] -# tspan = (0.0, 4.0) -# testsol(osys, u0, p, tspan; tstops = [1.0, 2.0], paramtotest = k) -# -# cond1a = (t == t1) -# affect1a = [A ~ Pre(A) + 1, B ~ A] -# cb1a = cond1a => affect1a -# @named osys1 = ODESystem(eqs, t, [A, B], [k, t1, t2], discrete_events = [cb1a, cb2]) -# u0′ = [A => 1.0, B => 0.0] -# sol = testsol( -# osys1, u0′, p, tspan; tstops = [1.0, 2.0], check_length = false, paramtotest = k) -# @test sol(1.0000001, idxs = B) == 2.0 -# -# # same as above - but with set-time event syntax -# cb1‵ = [1.0] => affect1 # needs to be a Vector for the event to happen only once -# cb2‵ = [2.0] => affect2 -# @named osys‵ = ODESystem(eqs, t, [A], [k], discrete_events = [cb1‵, cb2‵]) -# testsol(osys‵, u0, p, tspan; paramtotest = k) -# -# # mixing discrete affects -# @named osys3 = ODESystem(eqs, t, [A], [k, t1, t2], discrete_events = [cb1, cb2‵]) -# testsol(osys3, 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 -# end -# cb2‵‵ = [2.0] => (affect!, [], [k], [k], nothing) -# @named osys4 = ODESystem(eqs, t, [A], [k, t1], discrete_events = [cb1, cb2‵‵]) -# oprob4 = ODEProblem(complete(osys4), u0, tspan, p) -# testsol(osys4, u0, p, tspan; tstops = [1.0], paramtotest = k) -# -# # mixing with symbolic condition in the func affect -# cb2‵‵‵ = (t == t2) => (affect!, [], [k], [k], nothing) -# @named osys5 = ODESystem(eqs, t, [A], [k, t1, t2], discrete_events = [cb1, cb2‵‵‵]) -# testsol(osys5, u0, p, tspan; tstops = [1.0, 2.0]) -# @named osys6 = ODESystem(eqs, t, [A], [k, t1, t2], discrete_events = [cb2‵‵‵, cb1]) -# testsol(osys6, u0, p, tspan; tstops = [1.0, 2.0], paramtotest = k) -# -# # mix a continuous event too -# cond3 = A ~ 0.1 -# affect3 = [k ~ 0.0] -# cb3 = cond3 => affect3 -# @named osys7 = ODESystem(eqs, t, [A], [k, t1, t2], discrete_events = [cb1, cb2‵‵‵], -# continuous_events = [cb3]) -# sol = testsol(osys7, u0, p, (0.0, 10.0); tstops = [1.0, 2.0]) -# @test isapprox(sol(10.0)[1], 0.1; atol = 1e-10, rtol = 1e-10) -#end -# -#@testset "SDESystem Discrete Callbacks" begin -# function testsol(ssys, u0, p, tspan; tstops = Float64[], paramtotest = nothing, -# kwargs...) -# sprob = SDEProblem(complete(ssys), u0, tspan, p; kwargs...) -# sol = solve(sprob, RI5(); tstops = tstops, abstol = 1e-10, reltol = 1e-10) -# @test isapprox(sol(1.0000000001)[1] - sol(0.999999999)[1], 1.0; rtol = 1e-4) -# paramtotest === nothing || (@test sol.ps[paramtotest] == 1.0) -# @test isapprox(sol(4.0)[1], 2 * exp(-2.0), atol = 1e-4) -# sol -# end -# -# @parameters k t1 t2 -# @variables A(t) B(t) -# -# cond1 = (t == t1) -# affect1 = [A ~ Pre(A) + 1] -# cb1 = cond1 => affect1 -# cond2 = (t == t2) -# affect2 = [k ~ 1.0] -# cb2 = cond2 => affect2 -# -# ∂ₜ = D -# eqs = [∂ₜ(A) ~ -k * A] -# @named ssys = SDESystem(eqs, [0.0], t, [A], [k, t1, t2], -# discrete_events = [cb1, cb2]) -# u0 = [A => 1.0] -# p = [k => 0.0, t1 => 1.0, t2 => 2.0] -# tspan = (0.0, 4.0) -# testsol(ssys, u0, p, tspan; tstops = [1.0, 2.0], paramtotest = k) -# -# cond1a = (t == t1) -# affect1a = [A ~ Pre(A) + 1, B ~ A] -# cb1a = cond1a => affect1a -# @named ssys1 = SDESystem(eqs, [0.0], t, [A, B], [k, t1, t2], -# discrete_events = [cb1a, cb2]) -# u0′ = [A => 1.0, B => 0.0] -# sol = testsol( -# ssys1, u0′, p, tspan; tstops = [1.0, 2.0], check_length = false, paramtotest = k) -# @test sol(1.0000001, idxs = 2) == 2.0 -# -# # same as above - but with set-time event syntax -# cb1‵ = [1.0] => affect1 # needs to be a Vector for the event to happen only once -# cb2‵ = [2.0] => affect2 -# @named ssys‵ = SDESystem(eqs, [0.0], t, [A], [k], discrete_events = [cb1‵, cb2‵]) -# testsol(ssys‵, u0, p, tspan; paramtotest = k) -# -# # mixing discrete affects -# @named ssys3 = SDESystem(eqs, [0.0], t, [A], [k, t1, t2], -# discrete_events = [cb1, cb2‵]) -# testsol(ssys3, u0, p, tspan; tstops = [1.0], paramtotest = k) -# -# # mixing with a func affect -# function affect!(integrator, u, p, ctx) -# setp(integrator, p.k)(integrator, 1.0) -# nothing -# end -# cb2‵‵ = [2.0] => (affect!, [], [k], [k], nothing) -# @named ssys4 = SDESystem(eqs, [0.0], t, [A], [k, t1], -# discrete_events = [cb1, cb2‵‵]) -# testsol(ssys4, u0, p, tspan; tstops = [1.0], paramtotest = k) -# -# # mixing with symbolic condition in the func affect -# cb2‵‵‵ = (t == t2) => (affect!, [], [k], [k], nothing) -# @named ssys5 = SDESystem(eqs, [0.0], t, [A], [k, t1, t2], -# discrete_events = [cb1, cb2‵‵‵]) -# testsol(ssys5, u0, p, tspan; tstops = [1.0, 2.0], paramtotest = k) -# @named ssys6 = SDESystem(eqs, [0.0], t, [A], [k, t1, t2], -# discrete_events = [cb2‵‵‵, cb1]) -# testsol(ssys6, u0, p, tspan; tstops = [1.0, 2.0], paramtotest = k) -# -# # mix a continuous event too -# cond3 = A ~ 0.1 -# affect3 = [k ~ 0.0] -# cb3 = cond3 => affect3 -# @named ssys7 = SDESystem(eqs, [0.0], t, [A], [k, t1, t2], -# discrete_events = [cb1, cb2‵‵‵], -# continuous_events = [cb3]) -# sol = testsol(ssys7, u0, p, (0.0, 10.0); tstops = [1.0, 2.0]) -# @test isapprox(sol(10.0)[1], 0.1; atol = 1e-10, rtol = 1e-10) -#end - -#@testset "JumpSystem Discrete Callbacks" begin -# function testsol(jsys, u0, p, tspan; tstops = Float64[], paramtotest = nothing, -# N = 40000, kwargs...) -# jsys = complete(jsys) -# dprob = DiscreteProblem(jsys, u0, tspan, p) -# jprob = JumpProblem(jsys, dprob, Direct(); kwargs...) -# sol = solve(jprob, SSAStepper(); tstops = tstops) -# @show sol -# @test (sol(1.000000000001)[1] - sol(0.99999999999)[1]) == 1 -# paramtotest === nothing || (@test sol.ps[paramtotest] == 1.0) -# @test sol(40.0)[1] == 0 -# sol -# end -# -# @parameters k t1 t2 -# @variables A(t) B(t) -# -# cond1 = (t == t1) -# affect1 = [A ~ Pre(A) + 1] -# cb1 = cond1 => affect1 -# cond2 = (t == t2) -# affect2 = [k ~ 1.0] -# cb2 = cond2 => affect2 -# -# eqs = [MassActionJump(k, [A => 1], [A => -1])] -# @named jsys = JumpSystem(eqs, t, [A], [k, t1, t2], discrete_events = [cb1, cb2]) -# u0 = [A => 1] -# p = [k => 0.0, t1 => 1.0, t2 => 2.0] -# tspan = (0.0, 40.0) -# testsol(jsys, u0, p, tspan; tstops = [1.0, 2.0], rng, paramtotest = k) -# -# cond1a = (t == t1) -# affect1a = [A ~ Pre(A) + 1, B ~ A] -# cb1a = cond1a => affect1a -# @named jsys1 = JumpSystem(eqs, t, [A, B], [k, t1, t2], discrete_events = [cb1a, cb2]) -# u0′ = [A => 1, B => 0] -# sol = testsol(jsys1, u0′, p, tspan; tstops = [1.0, 2.0], -# check_length = false, rng, paramtotest = k) -# @test sol(1.000000001, idxs = B) == 2 -# -# # same as above - but with set-time event syntax -# cb1‵ = [1.0] => affect1 # needs to be a Vector for the event to happen only once -# cb2‵ = [2.0] => affect2 -# @named jsys‵ = JumpSystem(eqs, t, [A], [k], discrete_events = [cb1‵, cb2‵]) -# testsol(jsys‵, u0, [p[1]], tspan; rng, paramtotest = k) -# -# # mixing discrete affects -# @named jsys3 = JumpSystem(eqs, t, [A], [k, t1, t2], discrete_events = [cb1, cb2‵]) -# 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 -# end -# cb2‵‵ = [2.0] => (affect!, [], [k], [k], nothing) -# @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) -# @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]) -# testsol(jsys6, u0, p, tspan; tstops = [1.0, 2.0], rng, paramtotest = k) -#end -# -#@testset "Namespacing" begin -# function oscillator_ce(k = 1.0; name) -# sts = @variables x(t)=1.0 v(t)=0.0 F(t) -# ps = @parameters k=k Θ=0.5 -# eqs = [D(x) ~ v, D(v) ~ -k * x + F] -# ev = [x ~ Θ] => [x ~ 1.0, v ~ 0.0] -# ODESystem(eqs, t, sts, ps, continuous_events = [ev]; name) -# end -# -# @named oscce = oscillator_ce() -# eqs = [oscce.F ~ 0] -# @named eqs_sys = ODESystem(eqs, t) -# @named oneosc_ce = compose(eqs_sys, oscce) -# oneosc_ce_simpl = structural_simplify(oneosc_ce) -# -# prob = ODEProblem(oneosc_ce_simpl, [], (0.0, 2.0), []) -# sol = solve(prob, Tsit5(), saveat = 0.1) -# -# @test typeof(oneosc_ce_simpl) == ODESystem -# @test sol[1, 6] < 1.0 # test whether x(t) decreases over time -# @test sol[1, 18] > 0.5 # test whether event happened -#end - -<<<<<<< HEAD +@testset "SymbolicContinuousCallback constructors" begin + e = SymbolicContinuousCallback(eqs[]) + @test e isa SymbolicContinuousCallback + @test isequal(equations(e), eqs) + @test e.affect == nothing + @test e.affect_neg == nothing + @test e.rootfind == SciMLBase.LeftRootFind + + e = SymbolicContinuousCallback(eqs) + @test e isa SymbolicContinuousCallback + @test isequal(equations(e), eqs) + @test e.affect == nothing + @test e.affect_neg == nothing + @test e.rootfind == SciMLBase.LeftRootFind + + e = SymbolicContinuousCallback(eqs, nothing) + @test e isa SymbolicContinuousCallback + @test isequal(equations(e), eqs) + @test e.affect == nothing + @test e.affect_neg == nothing + @test e.rootfind == SciMLBase.LeftRootFind + + e = SymbolicContinuousCallback(eqs[], nothing) + @test e isa SymbolicContinuousCallback + @test isequal(equations(e), eqs) + @test e.affect == nothing + @test e.affect_neg == nothing + @test e.rootfind == SciMLBase.LeftRootFind + e = SymbolicContinuousCallback(eqs => nothing) @test e isa SymbolicContinuousCallback @test isequal(equations(e), eqs) @@ -1337,7 +655,7 @@ end @variables A(t) B(t) cond1 = (t == t1) - affect1 = [A ~ A + 1] + affect1 = [A ~ Pre(A) + 1] cb1 = cond1 => affect1 cond2 = (t == t2) affect2 = [k ~ 1.0] @@ -1406,8 +724,8 @@ end sol = solve(prob, Tsit5(), saveat = 0.1) @test typeof(oneosc_ce_simpl) == ODESystem - @test sol[oscce.x, 6] < 1.0 # test whether x(t) decreases over time - @test sol[oscce.x, 18] > 0.5 # test whether event happened + @test sol[1, 6] < 1.0 # test whether x(t) decreases over time + @test sol[1, 18] > 0.5 # test whether event happened end @testset "Additional SymbolicContinuousCallback options" begin @@ -1557,155 +875,6 @@ end @test sign.(cos.(required_crossings_c1 .- 1e-6)) == sign.(last.(cr1)) @test sign.(cos.(3 * (required_crossings_c2 .+ 1e-6))) == sign.(last.(cr2)) end -======= -#@testset "Additional SymbolicContinuousCallback options" begin -# # 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]) -# cr1 = [] -# cr2 = [] -# evt1 = ModelingToolkit.SymbolicContinuousCallback( -# [c1 ~ 0], (record_crossings, [c1 => :v], [], [], cr1)) -# evt2 = ModelingToolkit.SymbolicContinuousCallback( -# [c2 ~ 0], (record_crossings, [c2 => :v], [], [], cr2)) -# @named trigsys = ODESystem(eqs, t; continuous_events = [evt1, evt2]) -# trigsys_ss = structural_simplify(trigsys) -# prob = ODEProblem(trigsys_ss, [], (0.0, 2π)) -# sol = solve(prob, Tsit5()) -# required_crossings_c1 = [π / 2, 3 * π / 2] -# required_crossings_c2 = [π / 6, π / 2, 5 * π / 6, 7 * π / 6, 3 * π / 2, 11 * π / 6] -# @test maximum(abs.(first.(cr1) .- required_crossings_c1)) < 1e-4 -# @test maximum(abs.(first.(cr2) .- required_crossings_c2)) < 1e-4 -# @test sign.(cos.(required_crossings_c1 .- 1e-6)) == sign.(last.(cr1)) -# @test sign.(cos.(3 * (required_crossings_c2 .- 1e-6))) == sign.(last.(cr2)) -# -# # with neg affect (pos * neg + left root find) -# cr1p = [] -# cr2p = [] -# cr1n = [] -# cr2n = [] -# evt1 = ModelingToolkit.SymbolicContinuousCallback( -# [c1 ~ 0], (record_crossings, [c1 => :v], [], [], cr1p); -# affect_neg = (record_crossings, [c1 => :v], [], [], cr1n)) -# evt2 = ModelingToolkit.SymbolicContinuousCallback( -# [c2 ~ 0], (record_crossings, [c2 => :v], [], [], cr2p); -# affect_neg = (record_crossings, [c2 => :v], [], [], cr2n)) -# @named trigsys = ODESystem(eqs, t; continuous_events = [evt1, evt2]) -# trigsys_ss = structural_simplify(trigsys) -# prob = ODEProblem(trigsys_ss, [], (0.0, 2π)) -# sol = solve(prob, Tsit5(); dtmax = 0.01) -# c1_pc = filter((<=)(0) ∘ sin, required_crossings_c1) -# c1_nc = filter((>=)(0) ∘ sin, required_crossings_c1) -# c2_pc = filter(c -> -sin(3c) > 0, required_crossings_c2) -# c2_nc = filter(c -> -sin(3c) < 0, required_crossings_c2) -# @test maximum(abs.(c1_pc .- first.(cr1p))) < 1e-5 -# @test maximum(abs.(c1_nc .- first.(cr1n))) < 1e-5 -# @test maximum(abs.(c2_pc .- first.(cr2p))) < 1e-5 -# @test maximum(abs.(c2_nc .- first.(cr2n))) < 1e-5 -# @test sign.(cos.(c1_pc .- 1e-6)) == sign.(last.(cr1p)) -# @test sign.(cos.(c1_nc .- 1e-6)) == sign.(last.(cr1n)) -# @test sign.(cos.(3 * (c2_pc .- 1e-6))) == sign.(last.(cr2p)) -# @test sign.(cos.(3 * (c2_nc .- 1e-6))) == sign.(last.(cr2n)) -# -# # with nothing neg affect (pos * neg + left root find) -# cr1p = [] -# cr2p = [] -# evt1 = ModelingToolkit.SymbolicContinuousCallback( -# [c1 ~ 0], (record_crossings, [c1 => :v], [], [], cr1p); affect_neg = nothing) -# evt2 = ModelingToolkit.SymbolicContinuousCallback( -# [c2 ~ 0], (record_crossings, [c2 => :v], [], [], cr2p); affect_neg = nothing) -# @named trigsys = ODESystem(eqs, t; continuous_events = [evt1, evt2]) -# trigsys_ss = structural_simplify(trigsys) -# prob = ODEProblem(trigsys_ss, [], (0.0, 2π)) -# sol = solve(prob, Tsit5(); dtmax = 0.01) -# @test maximum(abs.(c1_pc .- first.(cr1p))) < 1e-5 -# @test maximum(abs.(c2_pc .- first.(cr2p))) < 1e-5 -# @test sign.(cos.(c1_pc .- 1e-6)) == sign.(last.(cr1p)) -# @test sign.(cos.(3 * (c2_pc .- 1e-6))) == sign.(last.(cr2p)) -# -# #mixed -# cr1p = [] -# cr2p = [] -# cr1n = [] -# cr2n = [] -# evt1 = ModelingToolkit.SymbolicContinuousCallback( -# [c1 ~ 0], (record_crossings, [c1 => :v], [], [], cr1p); affect_neg = nothing) -# evt2 = ModelingToolkit.SymbolicContinuousCallback( -# [c2 ~ 0], (record_crossings, [c2 => :v], [], [], cr2p); -# affect_neg = (record_crossings, [c2 => :v], [], [], cr2n)) -# @named trigsys = ODESystem(eqs, t; continuous_events = [evt1, evt2]) -# trigsys_ss = structural_simplify(trigsys) -# prob = ODEProblem(trigsys_ss, [], (0.0, 2π)) -# sol = solve(prob, Tsit5(); dtmax = 0.01) -# c1_pc = filter((<=)(0) ∘ sin, required_crossings_c1) -# c2_pc = filter(c -> -sin(3c) > 0, required_crossings_c2) -# c2_nc = filter(c -> -sin(3c) < 0, required_crossings_c2) -# @test maximum(abs.(c1_pc .- first.(cr1p))) < 1e-5 -# @test maximum(abs.(c2_pc .- first.(cr2p))) < 1e-5 -# @test maximum(abs.(c2_nc .- first.(cr2n))) < 1e-5 -# @test sign.(cos.(c1_pc .- 1e-6)) == sign.(last.(cr1p)) -# @test sign.(cos.(3 * (c2_pc .- 1e-6))) == sign.(last.(cr2p)) -# @test sign.(cos.(3 * (c2_nc .- 1e-6))) == sign.(last.(cr2n)) -# -# # baseline affect w/ right rootfind (pos + neg + right root find) -# @variables c1(t)=1.0 c2(t)=1.0 # c1 = cos(t), c2 = cos(3t) -# cr1 = [] -# cr2 = [] -# evt1 = ModelingToolkit.SymbolicContinuousCallback( -# [c1 ~ 0], (record_crossings, [c1 => :v], [], [], cr1); -# rootfind = SciMLBase.RightRootFind) -# evt2 = ModelingToolkit.SymbolicContinuousCallback( -# [c2 ~ 0], (record_crossings, [c2 => :v], [], [], cr2); -# rootfind = SciMLBase.RightRootFind) -# @named trigsys = ODESystem(eqs, t; continuous_events = [evt1, evt2]) -# trigsys_ss = structural_simplify(trigsys) -# prob = ODEProblem(trigsys_ss, [], (0.0, 2π)) -# sol = solve(prob, Tsit5(); dtmax = 0.01) -# required_crossings_c1 = [π / 2, 3 * π / 2] -# required_crossings_c2 = [π / 6, π / 2, 5 * π / 6, 7 * π / 6, 3 * π / 2, 11 * π / 6] -# @test maximum(abs.(first.(cr1) .- required_crossings_c1)) < 1e-4 -# @test maximum(abs.(first.(cr2) .- required_crossings_c2)) < 1e-4 -# @test sign.(cos.(required_crossings_c1 .+ 1e-6)) == sign.(last.(cr1)) -# @test sign.(cos.(3 * (required_crossings_c2 .+ 1e-6))) == sign.(last.(cr2)) -# -# # baseline affect w/ mixed rootfind (pos + neg + right root find) -# cr1 = [] -# cr2 = [] -# evt1 = ModelingToolkit.SymbolicContinuousCallback( -# [c1 ~ 0], (record_crossings, [c1 => :v], [], [], cr1); -# rootfind = SciMLBase.LeftRootFind) -# evt2 = ModelingToolkit.SymbolicContinuousCallback( -# [c2 ~ 0], (record_crossings, [c2 => :v], [], [], cr2); -# rootfind = SciMLBase.RightRootFind) -# @named trigsys = ODESystem(eqs, t; continuous_events = [evt1, evt2]) -# trigsys_ss = structural_simplify(trigsys) -# prob = ODEProblem(trigsys_ss, [], (0.0, 2π)) -# sol = solve(prob, Tsit5()) -# @test maximum(abs.(first.(cr1) .- required_crossings_c1)) < 1e-4 -# @test maximum(abs.(first.(cr2) .- required_crossings_c2)) < 1e-4 -# @test sign.(cos.(required_crossings_c1 .- 1e-6)) == sign.(last.(cr1)) -# @test sign.(cos.(3 * (required_crossings_c2 .+ 1e-6))) == sign.(last.(cr2)) -# -# #flip order and ensure results are okay -# cr1 = [] -# cr2 = [] -# evt1 = ModelingToolkit.SymbolicContinuousCallback( -# [c1 ~ 0], (record_crossings, [c1 => :v], [], [], cr1); -# rootfind = SciMLBase.LeftRootFind) -# evt2 = ModelingToolkit.SymbolicContinuousCallback( -# [c2 ~ 0], (record_crossings, [c2 => :v], [], [], cr2); -# rootfind = SciMLBase.RightRootFind) -# @named trigsys = ODESystem(eqs, t; continuous_events = [evt2, evt1]) -# trigsys_ss = structural_simplify(trigsys) -# prob = ODEProblem(trigsys_ss, [], (0.0, 2π)) -# sol = solve(prob, Tsit5()) -# @test maximum(abs.(first.(cr1) .- required_crossings_c1)) < 1e-4 -# @test maximum(abs.(first.(cr2) .- required_crossings_c2)) < 1e-4 -# @test sign.(cos.(required_crossings_c1 .- 1e-6)) == sign.(last.(cr1)) -# @test sign.(cos.(3 * (required_crossings_c2 .+ 1e-6))) == sign.(last.(cr2)) -#end ->>>>>>> 5de75ea625 (fix NoInit() error) @testset "Discrete event reinitialization (#3142)" begin @connector LiquidPort begin @@ -1984,7 +1153,7 @@ end f = ModelingToolkit.FunctionalAffect( f = (i, u, p, c) -> seen = true, sts = [], pars = [], discretes = []) cb1 = ModelingToolkit.SymbolicContinuousCallback( - [x ~ 0], nothing, initialize = [x ~ 1.5], finalize = f) + [x ~ 0], Equation[], initialize = [x ~ 1.5], finalize = f) @mtkbuild sys = ODESystem(D(x) ~ -1, t, [x], []; continuous_events = [cb1]) prob = ODEProblem(sys, [x => 1.0], (0.0, 2), []) sol = solve(prob, Tsit5(); dtmax = 0.01) @@ -2069,9 +1238,7 @@ end @variables x(t) [irreducible = true] y(t) [irreducible = true] eqs = [x ~ y, D(x) ~ -1] cb = [x ~ 0.0] => [x ~ 0, y ~ 1] - @mtkbuild pend = ODESystem(eqs, t; continuous_events = [cb]) - prob = ODEProblem(pend, [x => 1], (0.0, 3.0), guesses = [y => x]) - @test_throws "DAE initialization failed" solve(prob, Rodas5()) + @test_throws ErrorException @mtkbuild pend = ODESystem(eqs, t; continuous_events = [cb]) cb = [x ~ 0.0] => [y ~ 1] @mtkbuild pend = ODESystem(eqs, t; continuous_events = [cb]) @@ -2162,6 +1329,6 @@ end @test 100.0 ∈ sol2[sys2.wd2.θ] end -# TO teste: +# TODO: test: # - Functional affects reinitialize correctly # - explicit equation of t in a functional affect From ed98038f4659577dfa92dbea536cbd10d51abc9b Mon Sep 17 00:00:00 2001 From: vyudu Date: Thu, 20 Mar 2025 17:24:23 -0400 Subject: [PATCH 046/122] fix: most tests passing --- src/systems/callbacks.jl | 12 +++++++----- test/symbolic_events.jl | 9 ++++----- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/src/systems/callbacks.jl b/src/systems/callbacks.jl index 054a1032b4..f11698638f 100644 --- a/src/systems/callbacks.jl +++ b/src/systems/callbacks.jl @@ -685,6 +685,8 @@ function generate_discrete_callbacks(sys::AbstractSystem, dvs = unknowns(sys), p [generate_callback(db, sys; kwargs...) for db in dbs] end +const EMPTY_FUNCTION = (args...) -> () + """ Codegen a DifferentialEquations callback. A (set of) continuous callback with multiple equations becomes a VectorContinuousCallback. Continuous callbacks with only one equation will become a ContinuousCallback. @@ -705,9 +707,9 @@ function generate_callback(cbs::Vector{SymbolicContinuousCallback}, sys; kwargs. inits = [] finals = [] for cb in cbs - affect = compile_affect(cb.affect, cb, sys, default = nothing) + affect = compile_affect(cb.affect, cb, sys, default = EMPTY_FUNCTION) push!(affects, affect) - affect_neg = (cb.affect_neg == cb.affect) ? affect : compile_affect(cb.affect_neg, cb, sys, default = nothing) + affect_neg = (cb.affect_neg === cb.affect) ? affect : compile_affect(cb.affect_neg, cb, sys, default = EMPTY_FUNCTION) push!(affect_negs, affect_neg) push!(inits, compile_affect(cb.initialize, cb, sys; default = nothing, is_init = true)) push!(finals, compile_affect(cb.finalize, cb, sys; default = nothing)) @@ -741,11 +743,11 @@ function generate_callback(cb, sys; kwargs...) ps = parameters(sys; initial_parameters = true) trigger = is_timed ? conditions(cb) : compile_condition(cb, sys, dvs, ps; kwargs...) - affect = compile_affect(cb.affect, cb, sys, default = (args...) -> ()) + affect = compile_affect(cb.affect, cb, sys, default = EMPTY_FUNCTION) affect_neg = if is_discrete(cb) nothing else - (cb.affect == cb.affect_neg) ? affect : compile_affect(cb.affect_neg, cb, sys, default = nothing) + (cb.affect === cb.affect_neg) ? affect : compile_affect(cb.affect_neg, cb, sys, default = EMPTY_FUNCTION) end init = compile_affect(cb.initialize, cb, sys, default = SciMLBase.INITIALIZE_DEFAULT, is_init = true) final = compile_affect(cb.finalize, cb, sys, default = SciMLBase.FINALIZE_DEFAULT) @@ -860,7 +862,7 @@ function compile_equational_affect(aff::AffectSystem, sys; kwargs...) push!(pmap, pre_p => pval) end guesses = Pair[u => integrator[aff_map[u]] for u in unknowns(affsys)] - affprob = ImplicitDiscreteProblem(affsys, Pair[], (0, 1), pmap; guesses, build_initializeprob = false) + affprob = ImplicitDiscreteProblem(affsys, Pair[], (integrator.t, integrator.t), pmap; guesses, build_initializeprob = false) affsol = init(affprob, SimpleIDSolve()) for u in dvs_to_modify diff --git a/test/symbolic_events.jl b/test/symbolic_events.jl index 763dfcbbea..4a7e8e90c0 100644 --- a/test/symbolic_events.jl +++ b/test/symbolic_events.jl @@ -644,7 +644,6 @@ end dprob = DiscreteProblem(jsys, u0, tspan, p) jprob = JumpProblem(jsys, dprob, Direct(); kwargs...) sol = solve(jprob, SSAStepper(); tstops = tstops) - @show sol @test (sol(1.000000000001)[1] - sol(0.99999999999)[1]) == 1 paramtotest === nothing || (@test sol.ps[paramtotest] == 1.0) @test sol(40.0)[1] == 0 @@ -1153,7 +1152,7 @@ end f = ModelingToolkit.FunctionalAffect( f = (i, u, p, c) -> seen = true, sts = [], pars = [], discretes = []) cb1 = ModelingToolkit.SymbolicContinuousCallback( - [x ~ 0], Equation[], initialize = [x ~ 1.5], finalize = f) + [x ~ 0], nothing, initialize = [x ~ 1.5], finalize = f) @mtkbuild sys = ODESystem(D(x) ~ -1, t, [x], []; continuous_events = [cb1]) prob = ODEProblem(sys, [x => 1.0], (0.0, 2), []) sol = solve(prob, Tsit5(); dtmax = 0.01) @@ -1166,7 +1165,7 @@ end f = ModelingToolkit.FunctionalAffect( f = (i, u, p, c) -> seen = true, sts = [], pars = [], discretes = []) cb1 = ModelingToolkit.SymbolicContinuousCallback( - [x ~ 0], Equation[], initialize = [x ~ 1.5], finalize = f) + [x ~ 0], nothing, initialize = [x ~ 1.5], finalize = f) inited = false finaled = false a = ModelingToolkit.FunctionalAffect( @@ -1174,7 +1173,7 @@ end b = ModelingToolkit.FunctionalAffect( f = (i, u, p, c) -> finaled = true, sts = [], pars = [], discretes = []) cb2 = ModelingToolkit.SymbolicContinuousCallback( - [x ~ 0.1], Equation[], initialize = a, finalize = b) + [x ~ 0.1], nothing, initialize = a, finalize = b) @mtkbuild sys = ODESystem(D(x) ~ -1, t, [x], []; continuous_events = [cb1, cb2]) prob = ODEProblem(sys, [x => 1.0], (0.0, 2), []) sol = solve(prob, Tsit5()) @@ -1238,7 +1237,7 @@ end @variables x(t) [irreducible = true] y(t) [irreducible = true] eqs = [x ~ y, D(x) ~ -1] cb = [x ~ 0.0] => [x ~ 0, y ~ 1] - @test_throws ErrorException @mtkbuild pend = ODESystem(eqs, t; continuous_events = [cb]) + @test_throws Exception @mtkbuild pend = ODESystem(eqs, t; continuous_events = [cb]) cb = [x ~ 0.0] => [y ~ 1] @mtkbuild pend = ODESystem(eqs, t; continuous_events = [cb]) From f1b65351734d5fce786229ca15583aeb75c47aea Mon Sep 17 00:00:00 2001 From: vyudu Date: Sat, 22 Mar 2025 02:38:40 -0400 Subject: [PATCH 047/122] feat: add optimization for explicit affects --- src/systems/callbacks.jl | 185 +++++++++++++++++++------------ src/systems/imperative_affect.jl | 2 +- src/systems/jumps/jumpsystem.jl | 20 ++-- test/accessor_functions.jl | 4 +- test/symbolic_events.jl | 1 + 5 files changed, 124 insertions(+), 88 deletions(-) diff --git a/src/systems/callbacks.jl b/src/systems/callbacks.jl index f11698638f..f26405ea58 100644 --- a/src/systems/callbacks.jl +++ b/src/systems/callbacks.jl @@ -69,6 +69,7 @@ struct AffectSystem discretes::Vector """Maps the symbols of unknowns/observed in the ImplicitDiscreteSystem to its corresponding unknown/parameter in the parent system.""" aff_to_sys::Dict + explicit::Bool end system(a::AffectSystem) = a.system @@ -77,6 +78,7 @@ unknowns(a::AffectSystem) = a.unknowns parameters(a::AffectSystem) = a.parameters aff_to_sys(a::AffectSystem) = a.aff_to_sys previous_vals(a::AffectSystem) = parameters(system(a)) +is_explicit(a::AffectSystem) = a.explicit function Base.show(iio::IO, aff::AffectSystem) eqs = vcat(equations(system(aff)), observed(system(aff))) @@ -105,6 +107,8 @@ Base.nameof(::Pre) = :Pre Base.show(io::IO, x::Pre) = print(io, "Pre") input_timedomain(::Pre, _ = nothing) = ContinuousClock() output_timedomain(::Pre, _ = nothing) = ContinuousClock() +unPre(x::Num) = unPre(unwrap(x)) +unPre(x::BasicSymbolic) = operation(x) isa Pre ? only(arguments(x)) : x function (p::Pre)(x) iw = Symbolics.iswrapped(x) @@ -229,24 +233,28 @@ function make_affect(affect::Vector{Equation}; iv = nothing, algeeqs = Equation[ isempty(affect) && return nothing isempty(algeeqs) && @warn "No algebraic equations were found. If the system has no algebraic equations, this can be disregarded. Otherwise pass in `algeeqs` to the SymbolicContinuousCallback constructor." + explicit = true affect = scalarize(affect) dvs = OrderedSet() params = OrderedSet() + params = OrderedSet() for eq in affect if !haspre(eq) && !(symbolic_type(eq.rhs) === NotSymbolic()) @warn "Affect equation $eq has no `Pre` operator. As such it will be interpreted as an algebraic equation to be satisfied after the callback. If you intended to use the value of a variable x before the affect, use Pre(x)." + explicit = false end collect_vars!(dvs, params, eq, iv; op = Pre) end for eq in algeeqs collect_vars!(dvs, params, eq, iv) + expilcit = false end if isnothing(iv) iv = isempty(dvs) ? iv : only(arguments(dvs[1])) isnothing(iv) && @warn "No independent variable specified and could not be inferred. If the iv appears in an affect equation explicitly, like x ~ t + 1, then it must be specified as an argument to the SymbolicContinuousCallback or SymbolicDiscreteCallback constructor. Otherwise this warning can be disregarded." end - # System parameters should become unknowns in the ImplicitDiscreteSystem. + # Parameters in affect equations should become unknowns in the ImplicitDiscreteSystem. cb_params = Any[] discretes = Any[] p_as_dvs = Any[] @@ -268,15 +276,15 @@ function make_affect(affect::Vector{Equation}; iv = nothing, algeeqs = Equation[ aff_map = Dict(zip(p_as_dvs, discretes)) rev_map = Dict([v => k for (k, v) in aff_map]) affect = Symbolics.substitute(affect, rev_map) - @mtkbuild affectsys = ImplicitDiscreteSystem(vcat(affect, algeeqs), iv, collect(union(dvs, p_as_dvs)), cb_params) + @named affectsys = ImplicitDiscreteSystem(vcat(affect, algeeqs), iv, collect(union(dvs, p_as_dvs)), cb_params) # get accessed parameters p from Pre(p) in the callback parameters - params = filter(isparameter, map(x -> only(arguments(unwrap(x))), cb_params)) + params = filter(isparameter, map(x -> unPre(x), cb_params)) # add unknowns to the map for u in dvs aff_map[u] = u end - return AffectSystem(affectsys, collect(dvs), params, discretes, aff_map) + return AffectSystem(affectsys, collect(dvs), params, discretes, aff_map, explicit) end function make_affect(affect; kwargs...) @@ -468,7 +476,7 @@ end ########## Namespacing Utilities ########### ############################################ -function namespace_affect(affect::FunctionalAffect, s) +function namespace_affects(affect::FunctionalAffect, s) FunctionalAffect(func(affect), renamespace.((s,), unknowns(affect)), unknowns_syms(affect), @@ -478,35 +486,35 @@ function namespace_affect(affect::FunctionalAffect, s) context(affect)) end -function namespace_affect(affect::AffectSystem, s) +function namespace_affects(affect::AffectSystem, s) AffectSystem(renamespace(s, system(affect)), renamespace.((s,), unknowns(affect)), renamespace.((s,), parameters(affect)), renamespace.((s,), discretes(affect)), - Dict([k => renamespace(s, v) for (k, v) in aff_to_sys(affect)])) + Dict([k => renamespace(s, v) for (k, v) in aff_to_sys(affect)]), is_explicit(affect)) end -namespace_affect(af::Nothing, s) = nothing +namespace_affects(af::Nothing, s) = nothing function namespace_callback(cb::SymbolicContinuousCallback, s)::SymbolicContinuousCallback SymbolicContinuousCallback( namespace_equation.(equations(cb), (s,)), - namespace_affect(affects(cb), s), - affect_neg = namespace_affect(affect_negs(cb), s), - initialize = namespace_affect(initialize_affects(cb), s), - finalize = namespace_affect(finalize_affects(cb), s), + namespace_affects(affects(cb), s), + affect_neg = namespace_affects(affect_negs(cb), s), + initialize = namespace_affects(initialize_affects(cb), s), + finalize = namespace_affects(finalize_affects(cb), s), rootfind = cb.rootfind) end -function namespace_condition(condition, s) +function namespace_conditions(condition, s) is_timed_condition(condition) ? condition : namespace_expr(condition, s) end function namespace_callback(cb::SymbolicDiscreteCallback, s)::SymbolicDiscreteCallback SymbolicDiscreteCallback( - namespace_condition(condition(cb), s), + namespace_conditions(conditions(cb), s), namespace_affects(affects(cb), s), - namespace_affects(initialize_affects(cb), s), - namespace_affects(finalize_affects(cb), s)) + initialize = namespace_affects(initialize_affects(cb), s), + finalize = namespace_affects(finalize_affects(cb), s)) end function Base.hash(cb::SymbolicContinuousCallback, s::UInt) @@ -623,8 +631,6 @@ function compile_condition(cbs::Union{AbstractCallback, Vector{<:AbstractCallbac end end end - - cond end """ @@ -707,12 +713,12 @@ function generate_callback(cbs::Vector{SymbolicContinuousCallback}, sys; kwargs. inits = [] finals = [] for cb in cbs - affect = compile_affect(cb.affect, cb, sys, default = EMPTY_FUNCTION) + affect = compile_affect(cb.affect, cb, sys, default = EMPTY_FUNCTION, kwargs...) push!(affects, affect) - affect_neg = (cb.affect_neg === cb.affect) ? affect : compile_affect(cb.affect_neg, cb, sys, default = EMPTY_FUNCTION) + affect_neg = (cb.affect_neg === cb.affect) ? affect : compile_affect(cb.affect_neg, cb, sys, default = EMPTY_FUNCTION, kwargs...) push!(affect_negs, affect_neg) - push!(inits, compile_affect(cb.initialize, cb, sys; default = nothing, is_init = true)) - push!(finals, compile_affect(cb.finalize, cb, sys; default = nothing)) + push!(inits, compile_affect(cb.initialize, cb, sys; default = nothing, is_init = true), kwargs...) + push!(finals, compile_affect(cb.finalize, cb, sys; default = nothing), kwargs...) end # Since there may be different number of conditions and affects, @@ -729,8 +735,8 @@ function generate_callback(cbs::Vector{SymbolicContinuousCallback}, sys; kwargs. isnothing(f) && return f(integ) end - initialize = compile_vector_optional_affect(inits, SciMLBase.INITIALIZE_DEFAULT) - finalize = compile_vector_optional_affect(finals, SciMLBase.FINALIZE_DEFAULT) + initialize = wrap_vector_optional_affect(inits, SciMLBase.INITIALIZE_DEFAULT) + finalize = wrap_vector_optional_affect(finals, SciMLBase.FINALIZE_DEFAULT) return VectorContinuousCallback( trigger, affect, affect_neg, length(eqs); initialize, finalize, @@ -743,14 +749,14 @@ function generate_callback(cb, sys; kwargs...) ps = parameters(sys; initial_parameters = true) trigger = is_timed ? conditions(cb) : compile_condition(cb, sys, dvs, ps; kwargs...) - affect = compile_affect(cb.affect, cb, sys, default = EMPTY_FUNCTION) + affect = compile_affect(cb.affect, cb, sys, default = EMPTY_FUNCTION, kwargs...) affect_neg = if is_discrete(cb) nothing else - (cb.affect === cb.affect_neg) ? affect : compile_affect(cb.affect_neg, cb, sys, default = EMPTY_FUNCTION) + (cb.affect === cb.affect_neg) ? affect : compile_affect(cb.affect_neg, cb, sys, default = EMPTY_FUNCTION, kwargs...) end - init = compile_affect(cb.initialize, cb, sys, default = SciMLBase.INITIALIZE_DEFAULT, is_init = true) - final = compile_affect(cb.finalize, cb, sys, default = SciMLBase.FINALIZE_DEFAULT) + init = compile_affect(cb.initialize, cb, sys, default = SciMLBase.INITIALIZE_DEFAULT, is_init = true, kwargs...) + final = compile_affect(cb.finalize, cb, sys, default = SciMLBase.FINALIZE_DEFAULT, kwargs...) initialize = isnothing(cb.initialize) ? init : ((c, u, t, i) -> init(i)) finalize = isnothing(cb.finalize) ? final : ((c, u, t, i) -> final(i)) @@ -795,32 +801,29 @@ function compile_affect( get(ic.callback_to_clocks, cb, Int[]) end - f = if isnothing(aff) - default + if isnothing(aff) + full_args = is_init && (default === SciMLBase.INITIALIZE_DEFAULT) + is_init ? wrap_save_discretes(f, save_idxs; full_args) : default elseif aff isa AffectSystem - compile_equational_affect(aff, sys) + f = compile_equational_affect(aff, sys; kwargs...) + wrap_save_discretes(f, save_idxs) elseif aff isa FunctionalAffect || aff isa ImperativeAffect - compile_functional_affect(aff, sys; kwargs...) + f = compile_functional_affect(aff, sys; kwargs...) + wrap_save_discretes(f, save_idxs; full_args = true) end - wrap_save_discretes(f, save_idxs; is_init) end -# Init can be: user defined function, nothing, or INITIALIZE_DEFAULT -function wrap_save_discretes(f, save_idxs; is_init = false) - if isempty(save_idxs) || f === SciMLBase.FINALIZE_DEFAULT || (isnothing(f) && !is_init) - return f - elseif f === SciMLBase.INITIALIZE_DEFAULT - let save_idxs = save_idxs - (c, u, t, i) -> begin - f(c, u, t, i) +function wrap_save_discretes(f, save_idxs; full_args = false) + let save_idxs = save_idxs + if full_args + return (c, u, t, i) -> begin + isnothing(f) || f(c, u, t, i) for idx in save_idxs SciMLBase.save_discretes!(i, idx) end end - end - else - let save_idxs = save_idxs - (i) -> begin + else + return (i) -> begin isnothing(f) || f(i) for idx in save_idxs SciMLBase.save_discretes!(i, idx) @@ -831,9 +834,9 @@ function wrap_save_discretes(f, save_idxs; is_init = false) end """ -Initialize and Finalize for VectorContinuousCallback. +Initialize and finalize for VectorContinuousCallback. """ -function compile_vector_optional_affect(funs, default) +function wrap_vector_optional_affect(funs, default) all(isnothing, funs) && return default return let funs = funs function (cb, u, t, integ) @@ -844,35 +847,71 @@ function compile_vector_optional_affect(funs, default) end end -function compile_equational_affect(aff::AffectSystem, sys; kwargs...) +function add_integrator_header( + sys::AbstractSystem, integrator = gensym(:MTKIntegrator), out = :u) + expr -> Func([DestructuredArgs(expr.args, integrator, inds = [:u, :p, :t])], [], + expr.body), + expr -> Func( + [DestructuredArgs(expr.args, integrator, inds = [out, :u, :p, :t])], [], + expr.body) +end + +""" +Compile an affect defined by a set of equations. Systems with algebraic equations will solve implicit discrete problems to obtain their next state. Systems without will generate functions that perform explicit updates. +""" +function compile_equational_affect(aff::AffectSystem, sys; reset_jumps = false, kwargs...) affsys = system(aff) - aff_map = aff_to_sys(aff) - sys_map = Dict([v => k for (k, v) in aff_map]) - ps_to_modify = discretes(aff) - dvs_to_modify = setdiff(unknowns(aff), getfield.(observed(sys), :lhs)) - #TODO: Add an optimization for systems without algebraic equations - - return let dvs_to_modify = dvs_to_modify, aff_map = aff_map, sys_map = sys_map, affsys = affsys, ps_to_modify = ps_to_modify - - @inline function affect!(integrator) - pmap = Pair[] - for pre_p in parameters(affsys) - p = only(arguments(unwrap(pre_p))) - pval = isparameter(p) ? integrator.ps[p] : integrator[p] - push!(pmap, pre_p => pval) - end - guesses = Pair[u => integrator[aff_map[u]] for u in unknowns(affsys)] - affprob = ImplicitDiscreteProblem(affsys, Pair[], (integrator.t, integrator.t), pmap; guesses, build_initializeprob = false) + reinit = has_alg_equations(sys) || has_alg_equations(affsys) + ps_to_update = discretes(aff) + dvs_to_update = setdiff(unknowns(aff), getfield.(observed(sys), :lhs)) + + if is_explicit(aff) + update_eqs = equations(affsys) + update_eqs = Symbolics.fast_substitute(equations, Dict([p => unPre(p) for p in parameters(affsys)])) + rhss = map(x -> x.rhs, update_eqs) + lhss = map(x -> x.lhs, update_eqs) + is_p = [lhs ∈ ps_to_update for lhs in lhss] + + dvs = unknowns(sys) + ps = parameters(sys) + t = get_iv(sys) + + u_idxs = indexin((@view lhss[.!is_p]), dvs) + p_idxs = indexin((@view lhss[is_p]), ps) + _ps = reorder_parameters(sys, ps) + integ = gensym(:MTKIntegrator) + + u_up, u_up! = build_function_wrapper(sys, (@view rhss[.!is_p]), dvs, _ps..., t; wrap_code = add_integrator_header(sys, integ, :u), expression = Val{false}, outputidxs = u_idxs) + p_up, p_up! = build_function_wrapper(sys, (@view rhss[is_p]), dvs, _ps..., t; wrap_code = add_integrator_header(sys, integ, :p), expression = Val{false}, outputidxs = p_idxs) + + return (integ) -> begin + u_up!(integ) + p_up!(integ) + reset_jumps && reset_aggregated_jumps!(integ) + end + else + aff_map = aff_to_sys(aff) + sys_map = Dict([v => k for (k, v) in aff_map]) + + return let dvs_to_update = dvs_to_update, aff_map = aff_map, sys_map = sys_map, affsys = affsys, ps_to_update = ps_to_update + (integ) -> begin + pmap = Pair[] + for pre_p in parameters(affsys) + p = unPre(pre_p) + pval = isparameter(p) ? integ.ps[p] : integ[p] + push!(pmap, pre_p => pval) + end + guesses = Pair[u => integ[aff_map[u]] for u in unknowns(affsys)] + affprob = ImplicitDiscreteProblem(affsys, Pair[], (integ.t, integ.t), pmap; guesses, build_initializeprob = false) - affsol = init(affprob, SimpleIDSolve()) - for u in dvs_to_modify - integrator[u] = affsol[sys_map[u]] - end - for p in ps_to_modify - integrator.ps[p] = affsol[sys_map[p]] + affsol = init(affprob, SimpleIDSolve()) + for u in dvs_to_update + integ[u] = affsol[sys_map[u]] + end + for p in ps_to_update + integ.ps[p] = affsol[sys_map[p]] + end end - - sys isa JumpSystem && reset_aggregated_jumps!(integrator) end end end diff --git a/src/systems/imperative_affect.jl b/src/systems/imperative_affect.jl index 81e4cf724f..f01682deb1 100644 --- a/src/systems/imperative_affect.jl +++ b/src/systems/imperative_affect.jl @@ -99,7 +99,7 @@ function Base.hash(a::ImperativeAffect, s::UInt) hash(a.ctx, s) end -function namespace_affect(affect::ImperativeAffect, s) +function namespace_affects(affect::ImperativeAffect, s) ImperativeAffect(func(affect), namespace_expr.(observed(affect), (s,)), observed_syms(affect), diff --git a/src/systems/jumps/jumpsystem.jl b/src/systems/jumps/jumpsystem.jl index 91168fda39..58295651b9 100644 --- a/src/systems/jumps/jumpsystem.jl +++ b/src/systems/jumps/jumpsystem.jl @@ -282,15 +282,14 @@ function generate_rate_function(js::JumpSystem, rate) expression = Val{true}) end -function generate_affect_function(js::JumpSystem, affect, outputidxs) +function generate_affect_function(js::JumpSystem, affect) consts = collect_constants(affect) if !isempty(consts) # The SymbolicUtils._build_function method of this case doesn't support postprocess_fbody csubs = Dict(c => getdefault(c) for c in consts) affect = substitute(affect, csubs) end - compile_affect( - affect, nothing, js, unknowns(js), parameters(js); outputidxs = outputidxs, - expression = Val{true}, checkvars = false) + compile_equational_affect( + affect, js; expression = Val{true}, checkvars = false) end function assemble_vrj( @@ -299,8 +298,7 @@ function assemble_vrj( rate = GeneratedFunctionWrapper{(2, 3, is_split(js))}(rate, nothing) outputvars = (value(affect.lhs) for affect in vrj.affect!) outputidxs = [unknowntoid[var] for var in outputvars] - affect = eval_or_rgf(generate_affect_function(js, vrj.affect!, outputidxs); - eval_expression, eval_module) + affect = eval_or_rgf(generate_affect_function(js, vrj.affect!); eval_expression, eval_module) VariableRateJump(rate, affect; save_positions = vrj.save_positions) end @@ -308,7 +306,7 @@ function assemble_vrj_expr(js, vrj, unknowntoid) rate = generate_rate_function(js, vrj.rate) outputvars = (value(affect.lhs) for affect in vrj.affect!) outputidxs = ((unknowntoid[var] for var in outputvars)...,) - affect = generate_affect_function(js, vrj.affect!, outputidxs) + affect = generate_affect_function(js, vrj.affect!) quote rate = $rate @@ -323,8 +321,7 @@ function assemble_crj( rate = GeneratedFunctionWrapper{(2, 3, is_split(js))}(rate, nothing) outputvars = (value(affect.lhs) for affect in crj.affect!) outputidxs = [unknowntoid[var] for var in outputvars] - affect = eval_or_rgf(generate_affect_function(js, crj.affect!, outputidxs); - eval_expression, eval_module) + affect = eval_or_rgf(generate_affect_function(js, crj.affect!); eval_expression, eval_module) ConstantRateJump(rate, affect) end @@ -332,7 +329,7 @@ function assemble_crj_expr(js, crj, unknowntoid) rate = generate_rate_function(js, crj.rate) outputvars = (value(affect.lhs) for affect in crj.affect!) outputidxs = ((unknowntoid[var] for var in outputvars)...,) - affect = generate_affect_function(js, crj.affect!, outputidxs) + affect = generate_affect_function(js, crj.affect!) quote rate = $rate @@ -574,8 +571,7 @@ function JumpProcesses.JumpProblem(js::JumpSystem, prob, end # handle events, making sure to reset aggregators in the generated affect functions - cbs = process_events(js; callback, eval_expression, eval_module, - postprocess_affect_expr! = _reset_aggregator!) + cbs = process_events(js; callback, eval_expression, eval_module, reset_jumps = true) JumpProblem(prob, aggregator, jset; dep_graph = jtoj, vartojumps_map = vtoj, jumptovars_map = jtov, scale_rates = false, nocopy = true, diff --git a/test/accessor_functions.jl b/test/accessor_functions.jl index 7ce477155b..a9efde3a98 100644 --- a/test/accessor_functions.jl +++ b/test/accessor_functions.jl @@ -54,8 +54,8 @@ let 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]] + cevs = [[t ~ 1.0] => [Y ~ Pre(Y) + 2.0]] + devs = [(t == 2.0) => [Y ~ Pre(Y) + 2.0]] @named sys_bot = ODESystem( eqs_bot, t; systems = [], continuous_events = cevs, discrete_events = devs) @named sys_mid2 = ODESystem( diff --git a/test/symbolic_events.jl b/test/symbolic_events.jl index 4a7e8e90c0..da0a2d0d98 100644 --- a/test/symbolic_events.jl +++ b/test/symbolic_events.jl @@ -1331,3 +1331,4 @@ end # TODO: test: # - Functional affects reinitialize correctly # - explicit equation of t in a functional affect +# - modifying both u and p in an affect From b7a450223a2fd714b9e1747825013d22a3d014cb Mon Sep 17 00:00:00 2001 From: vyudu Date: Sat, 22 Mar 2025 07:08:56 -0400 Subject: [PATCH 048/122] fix: fix FMI tests and parameter dependency tests --- ext/MTKFMIExt.jl | 7 +- src/systems/callbacks.jl | 205 +++++++++++++++++--------------- src/systems/jumps/jumpsystem.jl | 11 +- test/accessor_functions.jl | 17 ++- test/funcaffect.jl | 5 +- test/parameter_dependencies.jl | 6 +- test/symbolic_events.jl | 2 + 7 files changed, 131 insertions(+), 122 deletions(-) diff --git a/ext/MTKFMIExt.jl b/ext/MTKFMIExt.jl index 5cfe9a82ef..912799c4f8 100644 --- a/ext/MTKFMIExt.jl +++ b/ext/MTKFMIExt.jl @@ -93,7 +93,7 @@ with the name `namespace__variable`. - `name`: The name of the system. """ function MTK.FMIComponent(::Val{Ver}; fmu = nothing, tolerance = 1e-6, - communication_step_size = nothing, reinitializealg = SciMLBase.NoInit(), type, name) where {Ver} + communication_step_size = nothing, type, name) where {Ver} if Ver != 2 && Ver != 3 throw(ArgumentError("FMI Version must be `2` or `3`")) end @@ -238,7 +238,7 @@ function MTK.FMIComponent(::Val{Ver}; fmu = nothing, tolerance = 1e-6, finalize_affect = MTK.FunctionalAffect(fmiFinalize!, [], [wrapper], []) step_affect = MTK.FunctionalAffect(Returns(nothing), [], [], []) instance_management_callback = MTK.SymbolicDiscreteCallback( - (t != t - 1), step_affect; finalize = finalize_affect, reinitializealg = reinitializealg) + (t != t - 1), step_affect; finalize = finalize_affect) push!(params, wrapper) append!(observed, der_observed) @@ -279,8 +279,7 @@ function MTK.FMIComponent(::Val{Ver}; fmu = nothing, tolerance = 1e-6, fmiCSStep!; observed = cb_observed, modified = cb_modified, ctx = _functor) instance_management_callback = MTK.SymbolicDiscreteCallback( communication_step_size, step_affect; initialize = initialize_affect, - finalize = finalize_affect, reinitializealg = reinitializealg - ) + finalize = finalize_affect) # guarded in case there are no outputs/states and the variable is `[]`. symbolic_type(__mtk_internal_o) == NotSymbolic() || push!(params, __mtk_internal_o) diff --git a/src/systems/callbacks.jl b/src/systems/callbacks.jl index f26405ea58..47498580da 100644 --- a/src/systems/callbacks.jl +++ b/src/systems/callbacks.jl @@ -55,13 +55,6 @@ function has_functional_affect(cb) (affects(cb) isa FunctionalAffect || affects(cb) isa ImperativeAffect) end -function vars!(vars, aff::FunctionalAffect; op = Differential) - for var in Iterators.flatten((unknowns(aff), parameters(aff), discretes(aff))) - vars!(vars, var) - end - return vars -end - struct AffectSystem system::ImplicitDiscreteSystem unknowns::Vector @@ -81,6 +74,7 @@ previous_vals(a::AffectSystem) = parameters(system(a)) is_explicit(a::AffectSystem) = a.explicit function Base.show(iio::IO, aff::AffectSystem) + println(iio, "Affect system defined by equations:") eqs = vcat(equations(system(aff)), observed(system(aff))) show(iio, eqs) end @@ -90,7 +84,24 @@ function Base.:(==)(a1::AffectSystem, a2::AffectSystem) isequal(discretes(a1), discretes(a2)) && isequal(unknowns(a1), unknowns(a2)) && isequal(parameters(a1), parameters(a2)) && - isequal(aff_to_sys(a1), aff_to_sys(a2)) + isequal(aff_to_sys(a1), aff_to_sys(a2)) && + isequal(is_explicit(a1), is_explicit(a2)) +end + +function Base.hash(a::AffectSystem, s::UInt) + s = hash(system(a), s) + s = hash(unknowns(a), s) + s = hash(parameters(a), s) + s = hash(discretes(a), s) + s = hash(aff_to_sys(a), s) + hash(is_explicit(a), s) +end + +function vars!(vars, aff::Union{FunctionalAffect, AffectSystem}; op = Differential) + for var in Iterators.flatten((unknowns(aff), parameters(aff), discretes(aff))) + vars!(vars, var) + end + vars end """ @@ -233,11 +244,11 @@ function make_affect(affect::Vector{Equation}; iv = nothing, algeeqs = Equation[ isempty(affect) && return nothing isempty(algeeqs) && @warn "No algebraic equations were found. If the system has no algebraic equations, this can be disregarded. Otherwise pass in `algeeqs` to the SymbolicContinuousCallback constructor." + @show affect explicit = true affect = scalarize(affect) dvs = OrderedSet() params = OrderedSet() - params = OrderedSet() for eq in affect if !haspre(eq) && !(symbolic_type(eq.rhs) === NotSymbolic()) @warn "Affect equation $eq has no `Pre` operator. As such it will be interpreted as an algebraic equation to be satisfied after the callback. If you intended to use the value of a variable x before the affect, use Pre(x)." @@ -247,7 +258,7 @@ function make_affect(affect::Vector{Equation}; iv = nothing, algeeqs = Equation[ end for eq in algeeqs collect_vars!(dvs, params, eq, iv) - expilcit = false + explicit = false end if isnothing(iv) iv = isempty(dvs) ? iv : only(arguments(dvs[1])) @@ -366,19 +377,20 @@ function Base.show(io::IO, mime::MIME"text/plain", cb::AbstractCallback) end end -function vars!(vars, cb::SymbolicContinuousCallback; op = Differential) - for eq in equations(cb) - vars!(vars, eq; op) - end - for aff in (affects(cb), affect_negs(cb), initialize_affects(cb), finalize_affects(cb)) - if aff isa AffectSystem - for eq in vcat(observed(system(aff)), equations(system(aff))) +function vars!(vars, cb::AbstractCallback; op = Differential) + if symbolic_type(conditions(cb)) == NotSymbolic + if conditions(cb) isa AbstractArray + for eq in conditions(cb) vars!(vars, eq; op) end - elseif aff !== nothing - vars!(vars, aff; op) end + else + vars!(vars, conditions(cb); op) end + for aff in (affects(cb), initialize_affects(cb), finalize_affects(cb)) + isnothing(aff) || vars!(vars, aff; op) + end + !is_discrete(cb) && vars!(vars, affect_negs(cb); op) return vars end @@ -450,28 +462,6 @@ function is_timed_condition(condition::T) where {T} end end -function vars!(vars, cb::SymbolicDiscreteCallback; op = Differential) - if symbolic_type(conditions(cb)) == NotSymbolic - if conditions(cb) isa AbstractArray - for eq in conditions(cb) - vars!(vars, eq; op) - end - end - else - vars!(vars, conditions(cb); op) - end - for aff in (affects(cb), initialize_affects(cb), finalize_affects(cb)) - if aff isa AffectSystem - for eq in vcat(observed(system(aff)), equations(system(aff))) - vars!(vars, eq; op) - end - elseif aff !== nothing - vars!(vars, aff; op) - end - end - return vars -end - ############################################ ########## Namespacing Utilities ########### ############################################ @@ -517,20 +507,13 @@ function namespace_callback(cb::SymbolicDiscreteCallback, s)::SymbolicDiscreteCa finalize = namespace_affects(finalize_affects(cb), s)) end -function Base.hash(cb::SymbolicContinuousCallback, s::UInt) - s = foldr(hash, cb.conditions, init = s) - s = hash(cb.affect, s) - s = hash(cb.affect_neg, s) - s = hash(cb.initialize, s) - s = hash(cb.finalize, s) - hash(cb.rootfind, s) -end - -function Base.hash(cb::SymbolicDiscreteCallback, s::UInt) - s = foldr(hash, cb.conditions, init = s) - s = hash(cb.affect, s) - s = hash(cb.initialize, s) - hash(cb.finalize, s) +function Base.hash(cb::AbstractCallback, s::UInt) + s = conditions(cb) isa AbstractVector ? foldr(hash, conditions(cb), init = s) : hash(conditions(cb), s) + s = hash(affects(cb), s) + !is_discrete(cb) && (s = hash(affect_negs(cb), s)) + s = hash(initialize_affects(cb), s) + s = hash(finalize_affects(cb), s) + !is_discrete(cb) ? hash(cb.rootfind, s) : s end ########################### @@ -564,15 +547,11 @@ function finalize_affects(cbs::Vector{<:AbstractCallback}) reduce(finalize_affects, vcat, cbs; init = []) end -function Base.:(==)(e1::SymbolicDiscreteCallback, e2::SymbolicDiscreteCallback) - isequal(e1.conditions, e2.conditions) && isequal(e1.affects, e2.affects) && - isequal(e1.initialize, e2.initialize) && isequal(e1.finalize, e2.finalize) -end - -function Base.:(==)(e1::SymbolicContinuousCallback, e2::SymbolicContinuousCallback) - isequal(e1.conditions, e2.conditions) && isequal(e1.affect, e2.affect) && - isequal(e1.initialize, e2.initialize) && isequal(e1.finalize, e2.finalize) && - isequal(e1.affect_neg, e2.affect_neg) && isequal(e1.rootfind, e2.rootfind) +function Base.:(==)(e1::AbstractCallback, e2::AbstractCallback) + (is_discrete(e1) === is_discrete(e2)) || return false + (isequal(e1.conditions, e2.conditions) && isequal(e1.affect, e2.affect) && + isequal(e1.initialize, e2.initialize) && isequal(e1.finalize, e2.finalize)) || return false + is_discrete(e1) || (isequal(e1.affect_neg, e2.affect_neg) && isequal(e1.rootfind, e2.rootfind)) end Base.isempty(cb::AbstractCallback) = isempty(cb.conditions) @@ -600,7 +579,7 @@ function compile_condition(cbs::Union{AbstractCallback, Vector{<:AbstractCallbac cs = collect_constants(condit) if !isempty(cs) cmap = map(x -> x => getdefault(x), cs) - condit = substitute(condit, cmap) + condit = substitute(condit, Dict(cmap)) end if !is_discrete(cbs) @@ -691,7 +670,7 @@ function generate_discrete_callbacks(sys::AbstractSystem, dvs = unknowns(sys), p [generate_callback(db, sys; kwargs...) for db in dbs] end -const EMPTY_FUNCTION = (args...) -> () +EMPTY_AFFECT(args...) = nothing """ Codegen a DifferentialEquations callback. A (set of) continuous callback with multiple equations becomes a VectorContinuousCallback. @@ -713,12 +692,12 @@ function generate_callback(cbs::Vector{SymbolicContinuousCallback}, sys; kwargs. inits = [] finals = [] for cb in cbs - affect = compile_affect(cb.affect, cb, sys, default = EMPTY_FUNCTION, kwargs...) + affect = compile_affect(cb.affect, cb, sys; default = EMPTY_AFFECT, kwargs...) push!(affects, affect) - affect_neg = (cb.affect_neg === cb.affect) ? affect : compile_affect(cb.affect_neg, cb, sys, default = EMPTY_FUNCTION, kwargs...) + affect_neg = (cb.affect_neg === cb.affect) ? affect : compile_affect(cb.affect_neg, cb, sys; default = EMPTY_AFFECT, kwargs...) push!(affect_negs, affect_neg) - push!(inits, compile_affect(cb.initialize, cb, sys; default = nothing, is_init = true), kwargs...) - push!(finals, compile_affect(cb.finalize, cb, sys; default = nothing), kwargs...) + push!(inits, compile_affect(cb.initialize, cb, sys; default = nothing, is_init = true, kwargs...)) + push!(finals, compile_affect(cb.finalize, cb, sys; default = nothing, kwargs...)) end # Since there may be different number of conditions and affects, @@ -749,14 +728,14 @@ function generate_callback(cb, sys; kwargs...) ps = parameters(sys; initial_parameters = true) trigger = is_timed ? conditions(cb) : compile_condition(cb, sys, dvs, ps; kwargs...) - affect = compile_affect(cb.affect, cb, sys, default = EMPTY_FUNCTION, kwargs...) + affect = compile_affect(cb.affect, cb, sys; default = EMPTY_AFFECT, kwargs...) affect_neg = if is_discrete(cb) nothing else - (cb.affect === cb.affect_neg) ? affect : compile_affect(cb.affect_neg, cb, sys, default = EMPTY_FUNCTION, kwargs...) + (cb.affect === cb.affect_neg) ? affect : compile_affect(cb.affect_neg, cb, sys; default = EMPTY_AFFECT, kwargs...) end - init = compile_affect(cb.initialize, cb, sys, default = SciMLBase.INITIALIZE_DEFAULT, is_init = true, kwargs...) - final = compile_affect(cb.finalize, cb, sys, default = SciMLBase.FINALIZE_DEFAULT, kwargs...) + init = compile_affect(cb.initialize, cb, sys; default = SciMLBase.INITIALIZE_DEFAULT, is_init = true, kwargs...) + final = compile_affect(cb.finalize, cb, sys; default = SciMLBase.FINALIZE_DEFAULT, kwargs...) initialize = isnothing(cb.initialize) ? init : ((c, u, t, i) -> init(i)) finalize = isnothing(cb.finalize) ? final : ((c, u, t, i) -> final(i)) @@ -802,28 +781,27 @@ function compile_affect( end if isnothing(aff) - full_args = is_init && (default === SciMLBase.INITIALIZE_DEFAULT) - is_init ? wrap_save_discretes(f, save_idxs; full_args) : default + is_init ? wrap_save_discretes(default, save_idxs) : default elseif aff isa AffectSystem f = compile_equational_affect(aff, sys; kwargs...) wrap_save_discretes(f, save_idxs) elseif aff isa FunctionalAffect || aff isa ImperativeAffect f = compile_functional_affect(aff, sys; kwargs...) - wrap_save_discretes(f, save_idxs; full_args = true) + wrap_save_discretes(f, save_idxs) end end -function wrap_save_discretes(f, save_idxs; full_args = false) +function wrap_save_discretes(f, save_idxs) let save_idxs = save_idxs - if full_args - return (c, u, t, i) -> begin + if f === SciMLBase.INITIALIZE_DEFAULT + (c, u, t, i) -> begin isnothing(f) || f(c, u, t, i) for idx in save_idxs SciMLBase.save_discretes!(i, idx) end end else - return (i) -> begin + (i) -> begin isnothing(f) || f(i) for idx in save_idxs SciMLBase.save_discretes!(i, idx) @@ -859,42 +837,46 @@ end """ Compile an affect defined by a set of equations. Systems with algebraic equations will solve implicit discrete problems to obtain their next state. Systems without will generate functions that perform explicit updates. """ -function compile_equational_affect(aff::AffectSystem, sys; reset_jumps = false, kwargs...) +function compile_equational_affect(aff::Union{AffectSystem, Vector{Equation}}, sys; reset_jumps = false, kwargs...) + aff isa AbstractVector && (aff = make_affect(aff, iv = get_iv(sys))) affsys = system(aff) - reinit = has_alg_equations(sys) || has_alg_equations(affsys) ps_to_update = discretes(aff) dvs_to_update = setdiff(unknowns(aff), getfield.(observed(sys), :lhs)) + aff_map = aff_to_sys(aff) + sys_map = Dict([v => k for (k, v) in aff_map]) if is_explicit(aff) - update_eqs = equations(affsys) - update_eqs = Symbolics.fast_substitute(equations, Dict([p => unPre(p) for p in parameters(affsys)])) + affsys = structural_simplify(affsys) + @assert isempty(equations(affsys)) + update_eqs = Symbolics.fast_substitute(observed(affsys), Dict([p => unPre(p) for p in parameters(affsys)])) rhss = map(x -> x.rhs, update_eqs) - lhss = map(x -> x.lhs, update_eqs) - is_p = [lhs ∈ ps_to_update for lhs in lhss] + lhss = map(x -> aff_map[x.lhs], update_eqs) + is_p = [lhs ∈ Set(ps_to_update) for lhs in lhss] dvs = unknowns(sys) ps = parameters(sys) t = get_iv(sys) u_idxs = indexin((@view lhss[.!is_p]), dvs) - p_idxs = indexin((@view lhss[is_p]), ps) + p_idxs = if has_index_cache(sys) && (get_index_cache(sys) !== nothing) + [parameter_index(sys, p) for p in lhss[is_p]] + else + indexin((@view lhss[is_p]), ps) + end _ps = reorder_parameters(sys, ps) integ = gensym(:MTKIntegrator) - u_up, u_up! = build_function_wrapper(sys, (@view rhss[.!is_p]), dvs, _ps..., t; wrap_code = add_integrator_header(sys, integ, :u), expression = Val{false}, outputidxs = u_idxs) - p_up, p_up! = build_function_wrapper(sys, (@view rhss[is_p]), dvs, _ps..., t; wrap_code = add_integrator_header(sys, integ, :p), expression = Val{false}, outputidxs = p_idxs) + u_up, u_up! = build_function_wrapper(sys, (@view rhss[.!is_p]), dvs, _ps..., t; wrap_code = add_integrator_header(sys, integ, :u), expression = Val{false}, outputidxs = u_idxs, wrap_mtkparameters = false) + p_up, p_up! = build_function_wrapper(sys, (@view rhss[is_p]), dvs, _ps..., t; wrap_code = add_integrator_header(sys, integ, :p), expression = Val{false}, outputidxs = p_idxs, wrap_mtkparameters = false) - return (integ) -> begin + return function explicit_affect!(integ) u_up!(integ) p_up!(integ) reset_jumps && reset_aggregated_jumps!(integ) end else - aff_map = aff_to_sys(aff) - sys_map = Dict([v => k for (k, v) in aff_map]) - return let dvs_to_update = dvs_to_update, aff_map = aff_map, sys_map = sys_map, affsys = affsys, ps_to_update = ps_to_update - (integ) -> begin + function implicit_affect!(integ) pmap = Pair[] for pre_p in parameters(affsys) p = unPre(pre_p) @@ -946,7 +928,7 @@ function discrete_events(sys::AbstractSystem) systems = get_systems(sys) cbs = [obs; 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 @@ -957,6 +939,22 @@ function get_discrete_events(sys::AbstractSystem) getfield(sys, :discrete_events) 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 + + """ continuous_events(sys::AbstractSystem)::Vector{SymbolicContinuousCallback} @@ -984,3 +982,18 @@ function get_continuous_events(sys::AbstractSystem) has_continuous_events(sys) || return SymbolicContinuousCallback[] getfield(sys, :continuous_events) 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 diff --git a/src/systems/jumps/jumpsystem.jl b/src/systems/jumps/jumpsystem.jl index 58295651b9..525ed9eec9 100644 --- a/src/systems/jumps/jumpsystem.jl +++ b/src/systems/jumps/jumpsystem.jl @@ -288,8 +288,8 @@ function generate_affect_function(js::JumpSystem, affect) csubs = Dict(c => getdefault(c) for c in consts) affect = substitute(affect, csubs) end - compile_equational_affect( - affect, js; expression = Val{true}, checkvars = false) + @show dump(affect[1]) + compile_equational_affect(affect, js; expression = Val{true}, checkvars = false) end function assemble_vrj( @@ -298,7 +298,7 @@ function assemble_vrj( rate = GeneratedFunctionWrapper{(2, 3, is_split(js))}(rate, nothing) outputvars = (value(affect.lhs) for affect in vrj.affect!) outputidxs = [unknowntoid[var] for var in outputvars] - affect = eval_or_rgf(generate_affect_function(js, vrj.affect!); eval_expression, eval_module) + affect = generate_affect_function(js, vrj.affect!) VariableRateJump(rate, affect; save_positions = vrj.save_positions) end @@ -309,7 +309,6 @@ function assemble_vrj_expr(js, vrj, unknowntoid) affect = generate_affect_function(js, vrj.affect!) quote rate = $rate - affect = $affect VariableRateJump(rate, affect) end @@ -321,7 +320,7 @@ function assemble_crj( rate = GeneratedFunctionWrapper{(2, 3, is_split(js))}(rate, nothing) outputvars = (value(affect.lhs) for affect in crj.affect!) outputidxs = [unknowntoid[var] for var in outputvars] - affect = eval_or_rgf(generate_affect_function(js, crj.affect!); eval_expression, eval_module) + affect = generate_affect_function(js, crj.affect!) ConstantRateJump(rate, affect) end @@ -332,7 +331,6 @@ function assemble_crj_expr(js, crj, unknowntoid) affect = generate_affect_function(js, crj.affect!) quote rate = $rate - affect = $affect ConstantRateJump(rate, affect) end @@ -543,6 +541,7 @@ function JumpProcesses.JumpProblem(js::JumpSystem, prob, majpmapper = JumpSysMajParamMapper(js, p; jseqs = eqs, rateconsttype = invttype) majs = isempty(eqs.x[1]) ? nothing : assemble_maj(eqs.x[1], unknowntoid, majpmapper) + @show eqs.x[2] crjs = ConstantRateJump[assemble_crj(js, j, unknowntoid; eval_expression, eval_module) for j in eqs.x[2]] vrjs = VariableRateJump[assemble_vrj(js, j, unknowntoid; eval_expression, eval_module) diff --git a/test/accessor_functions.jl b/test/accessor_functions.jl index a9efde3a98..24fb245fed 100644 --- a/test/accessor_functions.jl +++ b/test/accessor_functions.jl @@ -149,20 +149,17 @@ let for sys in [sys_bot, sys_mid2, sys_mid1, sys_top]) # 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 + # as I stored the same single 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] + bot_cev = ModelingToolkit.SymbolicContinuousCallback(cevs[1], algeeqs = [O ~ (d + p_bot) * X_bot + Y]) + mid_dev = ModelingToolkit.SymbolicDiscreteCallback(devs[1], algeeqs = [O ~ (d + p_mid1) * X_mid1 + Y]) @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])..., - [mtk_cev]) + continuous_events_toplevel.([sys_bot, sys_bot_comp, sys_bot_ss])..., + [bot_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])..., - [mtk_dev]) + [sys_mid1, sys_mid1_comp, sys_mid1_ss])..., + [mid_dev]) @test all(sym_issubset( continuous_events_toplevel(sys), get_continuous_events(sys)) for sys in [sys_bot, sys_mid2, sys_mid1, sys_top]) diff --git a/test/funcaffect.jl b/test/funcaffect.jl index 05543c9161..1e7c66f39a 100644 --- a/test/funcaffect.jl +++ b/test/funcaffect.jl @@ -26,8 +26,7 @@ cb1 = ModelingToolkit.SymbolicDiscreteCallback(t == zr, (affect1!, [], [], [], [ @test cb == cb1 @test ModelingToolkit.SymbolicDiscreteCallback(cb) === cb # passthrough @test hash(cb) == hash(cb1) -ModelingToolkit.generate_discrete_callback(cb, sys, ModelingToolkit.get_variables(sys), - ModelingToolkit.get_ps(sys)); +ModelingToolkit.generate_callback(cb, sys); cb = ModelingToolkit.SymbolicContinuousCallback([t ~ zr], (f = affect1!, sts = [], pars = [], discretes = [], @@ -48,7 +47,7 @@ sys1 = ODESystem(eqs, t, [u], [], name = :sys, de = ModelingToolkit.get_discrete_events(sys1) @test length(de) == 1 de = de[1] -@test ModelingToolkit.condition(de) == [4.0] +@test ModelingToolkit.conditions(de) == [4.0] @test ModelingToolkit.has_functional_affect(de) sys2 = ODESystem(eqs, t, [u], [], name = :sys, diff --git a/test/parameter_dependencies.jl b/test/parameter_dependencies.jl index 31881e1ca8..cc2f137392 100644 --- a/test/parameter_dependencies.jl +++ b/test/parameter_dependencies.jl @@ -287,13 +287,13 @@ end @constants h = 1 @variables S(t) I(t) R(t) rate₁ = β * S * I * h - affect₁ = [S ~ S - 1 * h, I ~ I + 1] + affect₁ = [S ~ Pre(S) - 1 * h, I ~ Pre(I) + 1] rate₃ = γ * I * h - affect₃ = [I ~ I * h - 1, R ~ R + 1] + affect₃ = [I ~ Pre(I) * h - 1, R ~ Pre(R) + 1] j₁ = ConstantRateJump(rate₁, affect₁) j₃ = ConstantRateJump(rate₃, affect₃) @named js2 = JumpSystem( - [j₁, j₃], t, [S, I, R], [γ]; parameter_dependencies = [β => 0.01γ]) + [j₃], t, [S, I, R], [γ]; parameter_dependencies = [β => 0.01γ]) @test isequal(only(parameters(js2)), γ) @test Set(full_parameters(js2)) == Set([γ, β]) js2 = complete(js2) diff --git a/test/symbolic_events.jl b/test/symbolic_events.jl index da0a2d0d98..d0f9f29c32 100644 --- a/test/symbolic_events.jl +++ b/test/symbolic_events.jl @@ -1332,3 +1332,5 @@ end # - Functional affects reinitialize correctly # - explicit equation of t in a functional affect # - modifying both u and p in an affect +# - affects that have Pre but are also algebraic in nature +# - reinitialization after affects From 0d08ea12ee6670379ebacb02e287cc9f2526da07 Mon Sep 17 00:00:00 2001 From: vyudu Date: Sat, 22 Mar 2025 07:34:02 -0400 Subject: [PATCH 049/122] more test fixes --- src/systems/callbacks.jl | 2 +- test/symbolic_events.jl | 5 ----- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/src/systems/callbacks.jl b/src/systems/callbacks.jl index 47498580da..d9c7bbfc90 100644 --- a/src/systems/callbacks.jl +++ b/src/systems/callbacks.jl @@ -244,7 +244,6 @@ function make_affect(affect::Vector{Equation}; iv = nothing, algeeqs = Equation[ isempty(affect) && return nothing isempty(algeeqs) && @warn "No algebraic equations were found. If the system has no algebraic equations, this can be disregarded. Otherwise pass in `algeeqs` to the SymbolicContinuousCallback constructor." - @show affect explicit = true affect = scalarize(affect) dvs = OrderedSet() @@ -288,6 +287,7 @@ function make_affect(affect::Vector{Equation}; iv = nothing, algeeqs = Equation[ rev_map = Dict([v => k for (k, v) in aff_map]) affect = Symbolics.substitute(affect, rev_map) @named affectsys = ImplicitDiscreteSystem(vcat(affect, algeeqs), iv, collect(union(dvs, p_as_dvs)), cb_params) + affectsys = complete(affectsys) # get accessed parameters p from Pre(p) in the callback parameters params = filter(isparameter, map(x -> unPre(x), cb_params)) # add unknowns to the map diff --git a/test/symbolic_events.jl b/test/symbolic_events.jl index d0f9f29c32..ada6844f90 100644 --- a/test/symbolic_events.jl +++ b/test/symbolic_events.jl @@ -65,15 +65,12 @@ affect_neg = [x ~ 1] e = SymbolicContinuousCallback(eqs[], affect) @test e isa SymbolicContinuousCallback @test isequal(equations(e), eqs) - @test observed(system(affects(e))) == affect - @test observed(system(affect_negs(e))) == affect @test e.rootfind == SciMLBase.LeftRootFind # with only positive edge affect e = SymbolicContinuousCallback(eqs[], affect, affect_neg = nothing) @test e isa SymbolicContinuousCallback @test isequal(equations(e), eqs) - @test observed(system(affects(e))) == affect @test isnothing(e.affect_neg) @test e.rootfind == SciMLBase.LeftRootFind @@ -81,8 +78,6 @@ affect_neg = [x ~ 1] e = SymbolicContinuousCallback(eqs[], affect, affect_neg = affect_neg) @test e isa SymbolicContinuousCallback @test isequal(equations(e), eqs) - @test observed(system(affects(e))) == affect - @test observed(system(affect_negs(e))) == affect_neg @test e.rootfind == SciMLBase.LeftRootFind # with different root finding ops From 4f9dfea404b339b10b692d646fafb07a3a75d29d Mon Sep 17 00:00:00 2001 From: vyudu Date: Mon, 24 Mar 2025 12:38:46 -0400 Subject: [PATCH 050/122] fix: more tests passing --- src/structural_transformation/utils.jl | 1 + src/systems/callbacks.jl | 24 ++++++++++++------- .../implicit_discrete_system.jl | 2 +- src/systems/jumps/jumpsystem.jl | 2 -- test/symbolic_events.jl | 4 +++- 5 files changed, 21 insertions(+), 12 deletions(-) diff --git a/src/structural_transformation/utils.jl b/src/structural_transformation/utils.jl index ebcb834bb1..7834e33b61 100644 --- a/src/structural_transformation/utils.jl +++ b/src/structural_transformation/utils.jl @@ -551,6 +551,7 @@ end function _distribute_shift(expr, shift) if iscall(expr) op = operation(expr) + (op isa Pre || op isa Initial) && return expr args = arguments(expr) if ModelingToolkit.isvariable(expr) diff --git a/src/systems/callbacks.jl b/src/systems/callbacks.jl index d9c7bbfc90..2cf6e8d907 100644 --- a/src/systems/callbacks.jl +++ b/src/systems/callbacks.jl @@ -242,7 +242,7 @@ make_affect(affect::Affect; kwargs...) = affect function make_affect(affect::Vector{Equation}; iv = nothing, algeeqs = Equation[]) isempty(affect) && return nothing - isempty(algeeqs) && @warn "No algebraic equations were found. If the system has no algebraic equations, this can be disregarded. Otherwise pass in `algeeqs` to the SymbolicContinuousCallback constructor." + isempty(algeeqs) && @warn "No algebraic equations were found for the callback defined by $(join(affect, ", ")). If the system has no algebraic equations, this can be disregarded. Otherwise pass in `algeeqs` to the SymbolicContinuousCallback constructor." explicit = true affect = scalarize(affect) @@ -259,6 +259,8 @@ function make_affect(affect::Vector{Equation}; iv = nothing, algeeqs = Equation[ collect_vars!(dvs, params, eq, iv) explicit = false end + any(isirreducible, dvs) && (explicit = false) + if isnothing(iv) iv = isempty(dvs) ? iv : only(arguments(dvs[1])) isnothing(iv) && @warn "No independent variable specified and could not be inferred. If the iv appears in an affect equation explicitly, like x ~ t + 1, then it must be specified as an argument to the SymbolicContinuousCallback or SymbolicDiscreteCallback constructor. Otherwise this warning can be disregarded." @@ -858,16 +860,19 @@ function compile_equational_affect(aff::Union{AffectSystem, Vector{Equation}}, s t = get_iv(sys) u_idxs = indexin((@view lhss[.!is_p]), dvs) - p_idxs = if has_index_cache(sys) && (get_index_cache(sys) !== nothing) - [parameter_index(sys, p) for p in lhss[is_p]] + + wrap_mtkparameters = has_index_cache(sys) && (get_index_cache(sys) !== nothing) + p_idxs = if wrap_mtkparameters + [parameter_index(sys, p) for (i, p) in enumerate(lhss) + if is_p[i]] else indexin((@view lhss[is_p]), ps) end _ps = reorder_parameters(sys, ps) integ = gensym(:MTKIntegrator) - u_up, u_up! = build_function_wrapper(sys, (@view rhss[.!is_p]), dvs, _ps..., t; wrap_code = add_integrator_header(sys, integ, :u), expression = Val{false}, outputidxs = u_idxs, wrap_mtkparameters = false) - p_up, p_up! = build_function_wrapper(sys, (@view rhss[is_p]), dvs, _ps..., t; wrap_code = add_integrator_header(sys, integ, :p), expression = Val{false}, outputidxs = p_idxs, wrap_mtkparameters = false) + u_up, u_up! = build_function_wrapper(sys, (@view rhss[.!is_p]), dvs, _ps..., t; wrap_code = add_integrator_header(sys, integ, :u), expression = Val{false}, outputidxs = u_idxs, wrap_mtkparameters) + p_up, p_up! = build_function_wrapper(sys, (@view rhss[is_p]), dvs, _ps..., t; wrap_code = add_integrator_header(sys, integ, :p), expression = Val{false}, outputidxs = p_idxs, wrap_mtkparameters) return function explicit_affect!(integ) u_up!(integ) @@ -883,9 +888,12 @@ function compile_equational_affect(aff::Union{AffectSystem, Vector{Equation}}, s pval = isparameter(p) ? integ.ps[p] : integ[p] push!(pmap, pre_p => pval) end - guesses = Pair[u => integ[aff_map[u]] for u in unknowns(affsys)] - affprob = ImplicitDiscreteProblem(affsys, Pair[], (integ.t, integ.t), pmap; guesses, build_initializeprob = false) - + u0 = Pair[] + for u in unknowns(affsys) + uval = isparameter(aff_map[u]) ? integ.ps[u] : integ[u] + push!(u0, u => uval) + end + affprob = ImplicitDiscreteProblem(affsys, u0, (integ.t, integ.t), pmap; build_initializeprob = false, check_length = false) affsol = init(affprob, SimpleIDSolve()) for u in dvs_to_update integ[u] = affsol[sys_map[u]] diff --git a/src/systems/discrete_system/implicit_discrete_system.jl b/src/systems/discrete_system/implicit_discrete_system.jl index 768a3a2191..df33d104fb 100644 --- a/src/systems/discrete_system/implicit_discrete_system.jl +++ b/src/systems/discrete_system/implicit_discrete_system.jl @@ -298,7 +298,7 @@ function shift_u0map_forward(sys::ImplicitDiscreteSystem, u0map, defs) v = u0map[k] if !((op = operation(k)) isa Shift) isnothing(getunshifted(k)) && - error("Initial conditions must be for the past state of the unknowns. Instead of providing the condition for $k, provide the condition for $(Shift(iv, -1)(k)).") + @warn "Initial condition given in term of current state of the unknown. If `build_initializeprob = false, this may be overriden by the implicit discrete solver." updated[k] = v elseif op.steps > 0 diff --git a/src/systems/jumps/jumpsystem.jl b/src/systems/jumps/jumpsystem.jl index 525ed9eec9..ea5e0638d1 100644 --- a/src/systems/jumps/jumpsystem.jl +++ b/src/systems/jumps/jumpsystem.jl @@ -288,7 +288,6 @@ function generate_affect_function(js::JumpSystem, affect) csubs = Dict(c => getdefault(c) for c in consts) affect = substitute(affect, csubs) end - @show dump(affect[1]) compile_equational_affect(affect, js; expression = Val{true}, checkvars = false) end @@ -541,7 +540,6 @@ function JumpProcesses.JumpProblem(js::JumpSystem, prob, majpmapper = JumpSysMajParamMapper(js, p; jseqs = eqs, rateconsttype = invttype) majs = isempty(eqs.x[1]) ? nothing : assemble_maj(eqs.x[1], unknowntoid, majpmapper) - @show eqs.x[2] crjs = ConstantRateJump[assemble_crj(js, j, unknowntoid; eval_expression, eval_module) for j in eqs.x[2]] vrjs = VariableRateJump[assemble_vrj(js, j, unknowntoid; eval_expression, eval_module) diff --git a/test/symbolic_events.jl b/test/symbolic_events.jl index ada6844f90..c1b631b6bc 100644 --- a/test/symbolic_events.jl +++ b/test/symbolic_events.jl @@ -1232,7 +1232,9 @@ end @variables x(t) [irreducible = true] y(t) [irreducible = true] eqs = [x ~ y, D(x) ~ -1] cb = [x ~ 0.0] => [x ~ 0, y ~ 1] - @test_throws Exception @mtkbuild pend = ODESystem(eqs, t; continuous_events = [cb]) + @mtkbuild pend = ODESystem(eqs, t; continuous_events = [cb]) + prob = ODEProblem(pend, [x => 1], (0.0, 3.0), guesses = [y => x]) + @test_broken !SciMLBase.successful_retcode(solve(prob, Rodas5())) cb = [x ~ 0.0] => [y ~ 1] @mtkbuild pend = ODESystem(eqs, t; continuous_events = [cb]) From ee65a30a3dca1a4077149e3870ed1df7b1da1c59 Mon Sep 17 00:00:00 2001 From: vyudu Date: Mon, 24 Mar 2025 15:46:15 -0400 Subject: [PATCH 051/122] fix: more test fixes --- src/systems/callbacks.jl | 11 +++++++---- src/systems/model_parsing.jl | 16 ++++++---------- test/discrete_system.jl | 1 - test/fmi/fmi.jl | 6 ++---- test/jumpsystem.jl | 6 +++--- test/model_parsing.jl | 2 +- 6 files changed, 19 insertions(+), 23 deletions(-) diff --git a/src/systems/callbacks.jl b/src/systems/callbacks.jl index 2cf6e8d907..e274b6efdd 100644 --- a/src/systems/callbacks.jl +++ b/src/systems/callbacks.jl @@ -240,12 +240,11 @@ make_affect(affect::Tuple; kwargs...) = FunctionalAffect(affect...) make_affect(affect::NamedTuple; kwargs...) = FunctionalAffect(; affect...) make_affect(affect::Affect; kwargs...) = affect -function make_affect(affect::Vector{Equation}; iv = nothing, algeeqs = Equation[]) +function make_affect(affect::Vector{Equation}; iv = nothing, algeeqs::Vector{Equation} = Equation[]) isempty(affect) && return nothing isempty(algeeqs) && @warn "No algebraic equations were found for the callback defined by $(join(affect, ", ")). If the system has no algebraic equations, this can be disregarded. Otherwise pass in `algeeqs` to the SymbolicContinuousCallback constructor." explicit = true - affect = scalarize(affect) dvs = OrderedSet() params = OrderedSet() for eq in affect @@ -296,8 +295,9 @@ function make_affect(affect::Vector{Equation}; iv = nothing, algeeqs = Equation[ for u in dvs aff_map[u] = u end + @show explicit - return AffectSystem(affectsys, collect(dvs), params, discretes, aff_map, explicit) + AffectSystem(affectsys, collect(dvs), params, discretes, aff_map, explicit) end function make_affect(affect; kwargs...) @@ -840,7 +840,10 @@ end Compile an affect defined by a set of equations. Systems with algebraic equations will solve implicit discrete problems to obtain their next state. Systems without will generate functions that perform explicit updates. """ function compile_equational_affect(aff::Union{AffectSystem, Vector{Equation}}, sys; reset_jumps = false, kwargs...) - aff isa AbstractVector && (aff = make_affect(aff, iv = get_iv(sys))) + if aff isa AbstractVector + aff = make_affect(aff; iv = get_iv(sys)) + @show is_explicit(aff) + end affsys = system(aff) ps_to_update = discretes(aff) dvs_to_update = setdiff(unknowns(aff), getfield.(observed(sys), :lhs)) diff --git a/src/systems/model_parsing.jl b/src/systems/model_parsing.jl index 024c249363..d0db2c6dd1 100644 --- a/src/systems/model_parsing.jl +++ b/src/systems/model_parsing.jl @@ -77,6 +77,8 @@ function _model_macro(mod, fullname::Union{Expr, Symbol}, expr, isconnector) push!(exprs.args, :(systems = ModelingToolkit.AbstractSystem[])) push!(exprs.args, :(equations = Union{Equation, Vector{Equation}}[])) push!(exprs.args, :(defaults = Dict{Num, Union{Number, Symbol, Function}}())) + push!(exprs.args, :(disc_events = [])) + push!(exprs.args, :(cont_events = [])) Base.remove_linenums!(expr) for arg in expr.args @@ -118,6 +120,8 @@ function _model_macro(mod, fullname::Union{Expr, Symbol}, expr, isconnector) push!(exprs.args, :(push!(parameters, $(ps...)))) push!(exprs.args, :(push!(systems, $(comps...)))) push!(exprs.args, :(push!(variables, $(vs...)))) + push!(exprs.args, :(push!(disc_events, $(d_evts...)))) + push!(exprs.args, :(push!(cont_events, $(c_evts...)))) gui_metadata = isassigned(icon) > 0 ? GUIMetadata(GlobalRef(mod, name), icon[]) : GUIMetadata(GlobalRef(mod, name)) @@ -141,16 +145,6 @@ function _model_macro(mod, fullname::Union{Expr, Symbol}, expr, isconnector) isconnector && push!(exprs.args, :($Setfield.@set!(var"#___sys___".connector_type=$connector_type(var"#___sys___")))) - !isempty(c_evts) && push!(exprs.args, - :($Setfield.@set!(var"#___sys___".continuous_events=$SymbolicContinuousCallback.([ - $(c_evts...) - ])))) - - !isempty(d_evts) && push!(exprs.args, - :($Setfield.@set!(var"#___sys___".discrete_events=$SymbolicDiscreteCallback.([ - $(d_evts...) - ])))) - f = if length(where_types) == 0 :($(Symbol(:__, name, :__))(; name, $(kwargs...)) = $exprs) else @@ -1142,6 +1136,7 @@ function parse_equations!(exprs, eqs, dict, body) end function parse_continuous_events!(c_evts, dict, body) + @show body dict[:continuous_events] = [] Base.remove_linenums!(body) for arg in body.args @@ -1151,6 +1146,7 @@ function parse_continuous_events!(c_evts, dict, body) end function parse_discrete_events!(d_evts, dict, body) + @show body dict[:discrete_events] = [] Base.remove_linenums!(body) for arg in body.args diff --git a/test/discrete_system.jl b/test/discrete_system.jl index b0e2481e56..2c515dda1a 100644 --- a/test/discrete_system.jl +++ b/test/discrete_system.jl @@ -254,7 +254,6 @@ end @variables x(t) y(t) k = ShiftIndex(t) @named sys = DiscreteSystem([x ~ x^2 + y^2, y ~ x(k - 1) + y(k - 1)], t) -@test_throws ["algebraic equations", "ImplicitDiscreteSystem"] structural_simplify(sys) @testset "Passing `nothing` to `u0`" begin @variables x(t) = 1 diff --git a/test/fmi/fmi.jl b/test/fmi/fmi.jl index 98c93398ff..0d10f3204a 100644 --- a/test/fmi/fmi.jl +++ b/test/fmi/fmi.jl @@ -157,8 +157,7 @@ end @testset "v2, CS" begin fmu = loadFMU(joinpath(FMU_DIR, "SimpleAdder.fmu"); type = :CS) @named adder = MTK.FMIComponent( - Val(2); fmu, type = :CS, communication_step_size = 1e-6, - reinitializealg = BrownFullBasicInit()) + Val(2); fmu, type = :CS, communication_step_size = 1e-6) @test MTK.isinput(adder.a) @test MTK.isinput(adder.b) @test MTK.isoutput(adder.out) @@ -210,8 +209,7 @@ end @testset "v3, CS" begin fmu = loadFMU(joinpath(FMU_DIR, "StateSpace.fmu"); type = :CS) @named sspace = MTK.FMIComponent( - Val(3); fmu, communication_step_size = 1e-6, type = :CS, - reinitializealg = BrownFullBasicInit()) + Val(3); fmu, communication_step_size = 1e-6, type = :CS) @test MTK.isinput(sspace.u) @test MTK.isoutput(sspace.y) @test !MTK.isinput(sspace.x) && !MTK.isoutput(sspace.x) diff --git a/test/jumpsystem.jl b/test/jumpsystem.jl index 6c96055270..6367552330 100644 --- a/test/jumpsystem.jl +++ b/test/jumpsystem.jl @@ -11,9 +11,9 @@ rng = StableRNG(12345) @constants h = 1 @variables S(t) I(t) R(t) rate₁ = β * S * I * h -affect₁ = [S ~ S - 1 * h, I ~ I + 1] +affect₁ = [S ~ Pre(S) - 1 * h, I ~ Pre(I) + 1] rate₂ = γ * I + t -affect₂ = [I ~ I - 1, R ~ R + 1] +affect₂ = [I ~ Pre(I) - 1, R ~ Pre(R) + 1] j₁ = ConstantRateJump(rate₁, affect₁) j₂ = VariableRateJump(rate₂, affect₂) @named js = JumpSystem([j₁, j₂], t, [S, I, R], [β, γ]) @@ -59,7 +59,7 @@ jump2.affect!(integrator) # test MT can make and solve a jump problem rate₃ = γ * I * h -affect₃ = [I ~ I * h - 1, R ~ R + 1] +affect₃ = [I ~ Pre(I) * h - 1, R ~ Pre(R) + 1] j₃ = ConstantRateJump(rate₃, affect₃) @named js2 = JumpSystem([j₁, j₃], t, [S, I, R], [β, γ]) js2 = complete(js2) diff --git a/test/model_parsing.jl b/test/model_parsing.jl index fe2bcbfca6..ad457a0ba3 100644 --- a/test/model_parsing.jl +++ b/test/model_parsing.jl @@ -484,7 +484,7 @@ using ModelingToolkit: D_nounits [x ~ 1.5] => [x ~ 5, y ~ 1] end @discrete_events begin - (t == 1.5) => [x ~ x + 5, z ~ 2] + (t == 1.5) => [x ~ Pre(x) + 5, z ~ 2] end end From 0e8b48d7926f26d5707e7c5b429ff4c4f1f47f31 Mon Sep 17 00:00:00 2001 From: vyudu Date: Mon, 24 Mar 2025 19:28:21 -0400 Subject: [PATCH 052/122] fix: use is_diff_equation instead of isdiffeq when finding algeeqs --- src/systems/callbacks.jl | 2 -- src/systems/diffeqs/odesystem.jl | 2 +- src/systems/diffeqs/sdesystem.jl | 2 +- src/systems/model_parsing.jl | 2 -- test/initializationsystem.jl | 4 ++-- 5 files changed, 4 insertions(+), 8 deletions(-) diff --git a/src/systems/callbacks.jl b/src/systems/callbacks.jl index e274b6efdd..eee74360fa 100644 --- a/src/systems/callbacks.jl +++ b/src/systems/callbacks.jl @@ -295,7 +295,6 @@ function make_affect(affect::Vector{Equation}; iv = nothing, algeeqs::Vector{Equ for u in dvs aff_map[u] = u end - @show explicit AffectSystem(affectsys, collect(dvs), params, discretes, aff_map, explicit) end @@ -842,7 +841,6 @@ Compile an affect defined by a set of equations. Systems with algebraic equation function compile_equational_affect(aff::Union{AffectSystem, Vector{Equation}}, sys; reset_jumps = false, kwargs...) if aff isa AbstractVector aff = make_affect(aff; iv = get_iv(sys)) - @show is_explicit(aff) end affsys = system(aff) ps_to_update = discretes(aff) diff --git a/src/systems/diffeqs/odesystem.jl b/src/systems/diffeqs/odesystem.jl index a4a9cb45fe..9c984b87cd 100644 --- a/src/systems/diffeqs/odesystem.jl +++ b/src/systems/diffeqs/odesystem.jl @@ -336,7 +336,7 @@ function ODESystem(deqs::AbstractVector{<:Equation}, iv, dvs, ps; throw(ArgumentError("System names must be unique.")) end - algeeqs = filter(eq -> eq.lhs isa Union{Symbolic, Number} && !isdiffeq(eq), deqs) + algeeqs = filter(eq -> eq.lhs isa Union{Symbolic, Number} && !is_diff_equation(eq), deqs) cont_callbacks = SymbolicContinuousCallbacks(continuous_events; algeeqs, iv) disc_callbacks = SymbolicDiscreteCallbacks(discrete_events; algeeqs, iv) diff --git a/src/systems/diffeqs/sdesystem.jl b/src/systems/diffeqs/sdesystem.jl index 36a2fc2de8..554acd43fc 100644 --- a/src/systems/diffeqs/sdesystem.jl +++ b/src/systems/diffeqs/sdesystem.jl @@ -270,7 +270,7 @@ function SDESystem(deqs::AbstractVector{<:Equation}, neqs::AbstractArray, iv, dv Wfact = RefValue(EMPTY_JAC) Wfact_t = RefValue(EMPTY_JAC) - algeeqs = filter(eq -> eq.lhs isa Union{Symbolic, Number} && !isdiffeq(eq), deqs) + algeeqs = filter(eq -> eq.lhs isa Union{Symbolic, Number} && !is_diff_equation(eq), deqs) cont_callbacks = SymbolicContinuousCallbacks(continuous_events; algeeqs, iv) disc_callbacks = SymbolicDiscreteCallbacks(discrete_events; algeeqs, iv) if is_dde === nothing diff --git a/src/systems/model_parsing.jl b/src/systems/model_parsing.jl index d0db2c6dd1..9a9b95f417 100644 --- a/src/systems/model_parsing.jl +++ b/src/systems/model_parsing.jl @@ -1136,7 +1136,6 @@ function parse_equations!(exprs, eqs, dict, body) end function parse_continuous_events!(c_evts, dict, body) - @show body dict[:continuous_events] = [] Base.remove_linenums!(body) for arg in body.args @@ -1146,7 +1145,6 @@ function parse_continuous_events!(c_evts, dict, body) end function parse_discrete_events!(d_evts, dict, body) - @show body dict[:discrete_events] = [] Base.remove_linenums!(body) for arg in body.args diff --git a/test/initializationsystem.jl b/test/initializationsystem.jl index 5c36fcba3e..311b60580f 100644 --- a/test/initializationsystem.jl +++ b/test/initializationsystem.jl @@ -1290,9 +1290,9 @@ end @parameters β γ S0 @variables S(t)=S0 I(t) R(t) rate₁ = β * S * I - affect₁ = [S ~ S - 1, I ~ I + 1] + affect₁ = [S ~ Pre(S) - 1, I ~ Pre(I) + 1] rate₂ = γ * I - affect₂ = [I ~ I - 1, R ~ R + 1] + affect₂ = [I ~ Pre(I) - 1, R ~ Pre(R) + 1] j₁ = ConstantRateJump(rate₁, affect₁) j₂ = ConstantRateJump(rate₂, affect₂) j₃ = MassActionJump(2 * β + γ, [R => 1], [S => 1, R => -1]) From b59537589912b8a6e343f6679dd8e23673e41281 Mon Sep 17 00:00:00 2001 From: vyudu Date: Tue, 25 Mar 2025 17:26:29 -0400 Subject: [PATCH 053/122] feat: specify discrete_parameters --- src/systems/callbacks.jl | 62 +- .../implicit_discrete_system.jl | 8 + test/jumpsystem.jl | 3 +- test/symbolic_events.jl | 2473 +++++++++-------- 4 files changed, 1303 insertions(+), 1243 deletions(-) diff --git a/src/systems/callbacks.jl b/src/systems/callbacks.jl index eee74360fa..5d34644297 100644 --- a/src/systems/callbacks.jl +++ b/src/systems/callbacks.jl @@ -219,6 +219,7 @@ struct SymbolicContinuousCallback <: AbstractCallback function SymbolicContinuousCallback( conditions::Union{Equation, Vector{Equation}}, affect = nothing; + discrete_parameters = Any[], affect_neg = affect, initialize = nothing, finalize = nothing, @@ -227,8 +228,8 @@ struct SymbolicContinuousCallback <: AbstractCallback algeeqs = Equation[]) conditions = (conditions isa AbstractVector) ? conditions : [conditions] - new(conditions, make_affect(affect; iv, algeeqs), make_affect(affect_neg; iv, algeeqs), - make_affect(initialize; iv, algeeqs), make_affect(finalize; iv, algeeqs), rootfind) + new(conditions, make_affect(affect; iv, algeeqs, discrete_parameters), make_affect(affect_neg; iv, algeeqs, discrete_parameters), + make_affect(initialize; iv, algeeqs, discrete_parameters), make_affect(finalize; iv, algeeqs, discrete_parameters), rootfind) end # Default affect to nothing end @@ -240,10 +241,15 @@ make_affect(affect::Tuple; kwargs...) = FunctionalAffect(affect...) make_affect(affect::NamedTuple; kwargs...) = FunctionalAffect(; affect...) make_affect(affect::Affect; kwargs...) = affect -function make_affect(affect::Vector{Equation}; iv = nothing, algeeqs::Vector{Equation} = Equation[]) +function make_affect(affect::Vector{Equation}; discrete_parameters = Any[], iv = nothing, algeeqs::Vector{Equation} = Equation[]) isempty(affect) && return nothing isempty(algeeqs) && @warn "No algebraic equations were found for the callback defined by $(join(affect, ", ")). If the system has no algebraic equations, this can be disregarded. Otherwise pass in `algeeqs` to the SymbolicContinuousCallback constructor." + for p in discretes + # Check if p is time-dependent + false && error("Non-time dependent parameter $p passed in as a discrete. Must be declared as $p(t).") + end + explicit = true dvs = OrderedSet() params = OrderedSet() @@ -265,38 +271,21 @@ function make_affect(affect::Vector{Equation}; iv = nothing, algeeqs::Vector{Equ isnothing(iv) && @warn "No independent variable specified and could not be inferred. If the iv appears in an affect equation explicitly, like x ~ t + 1, then it must be specified as an argument to the SymbolicContinuousCallback or SymbolicDiscreteCallback constructor. Otherwise this warning can be disregarded." end - # Parameters in affect equations should become unknowns in the ImplicitDiscreteSystem. - cb_params = Any[] - discretes = Any[] - p_as_dvs = Any[] - for p in params - if iscall(p) && (operation(p) isa Pre) - push!(cb_params, p) - elseif iscall(p) && length(arguments(p)) == 1 && - isequal(only(arguments(p)), iv) - push!(discretes, p) - push!(p_as_dvs, tovar(p)) - else - push!(discretes, p) - name = iscall(p) ? nameof(operation(p)) : nameof(p) - p = wrap(Sym{FnType{Tuple{symtype(iv)}, Real}}(name)(iv)) - p = setmetadata(p, Symbolics.VariableSource, (:variables, name)) - push!(p_as_dvs, p) - end - end - aff_map = Dict(zip(p_as_dvs, discretes)) - rev_map = Dict([v => k for (k, v) in aff_map]) - affect = Symbolics.substitute(affect, rev_map) - @named affectsys = ImplicitDiscreteSystem(vcat(affect, algeeqs), iv, collect(union(dvs, p_as_dvs)), cb_params) + pre_params = filter(haspre ∘ value, params) + sys_params = setdiff(params, union(discrete_parameters, pre_params)) + discretes = map(tovar, discrete_parameters) + aff_map = Dict(zip(discretes, discrete_parameters)) + @named affectsys = ImplicitDiscreteSystem(vcat(affect, algeeqs), iv, collect(union(dvs, discretes)), collect(union(pre_params, sys_params))) affectsys = complete(affectsys) # get accessed parameters p from Pre(p) in the callback parameters - params = filter(isparameter, map(x -> unPre(x), cb_params)) + accessed_params = filter(isparameter, map(x -> unPre(x), cb_params)) + union!(accessed_params, sys_params) # add unknowns to the map for u in dvs aff_map[u] = u end - AffectSystem(affectsys, collect(dvs), params, discretes, aff_map, explicit) + AffectSystem(affectsys, collect(dvs), collect(accessed_params), collect(discrete_parameters), aff_map, explicit) end function make_affect(affect; kwargs...) @@ -876,8 +865,8 @@ function compile_equational_affect(aff::Union{AffectSystem, Vector{Equation}}, s p_up, p_up! = build_function_wrapper(sys, (@view rhss[is_p]), dvs, _ps..., t; wrap_code = add_integrator_header(sys, integ, :p), expression = Val{false}, outputidxs = p_idxs, wrap_mtkparameters) return function explicit_affect!(integ) - u_up!(integ) - p_up!(integ) + isempty(dvs_to_update) || u_up!(integ) + isempty(ps_to_update) || p_up!(integ) reset_jumps && reset_aggregated_jumps!(integ) end else @@ -891,11 +880,12 @@ function compile_equational_affect(aff::Union{AffectSystem, Vector{Equation}}, s end u0 = Pair[] for u in unknowns(affsys) - uval = isparameter(aff_map[u]) ? integ.ps[u] : integ[u] + uval = isparameter(aff_map[u]) ? integ.ps[aff_map[u]] : integ[u] push!(u0, u => uval) end affprob = ImplicitDiscreteProblem(affsys, u0, (integ.t, integ.t), pmap; build_initializeprob = false, check_length = false) - affsol = init(affprob, SimpleIDSolve()) + affsol = init(affprob, IDSolve()) + check_error(affsol) && throw(UnsolvableCallbackError(equations(affsys))) for u in dvs_to_update integ[u] = affsol[sys_map[u]] end @@ -907,6 +897,14 @@ function compile_equational_affect(aff::Union{AffectSystem, Vector{Equation}}, s end end +struct UnsolvableCallbackError + eqs::Vector{Equation} +end + +function Base.showerror(io, err::UnsolvableCallbackError) + println(io, "The callback defined by the equations, $(join(err.eqs, "\n")), with discrete parameters is not solvable. Please check the algebraic equations, affect equations, and declared discrete parameters.") +end + merge_cb(::Nothing, ::Nothing) = nothing merge_cb(::Nothing, x) = merge_cb(x, nothing) merge_cb(x, ::Nothing) = x diff --git a/src/systems/discrete_system/implicit_discrete_system.jl b/src/systems/discrete_system/implicit_discrete_system.jl index df33d104fb..b63235bbdc 100644 --- a/src/systems/discrete_system/implicit_discrete_system.jl +++ b/src/systems/discrete_system/implicit_discrete_system.jl @@ -287,6 +287,7 @@ function generate_function( u_next = map(Shift(iv, 1), dvs) u = dvs p = (reorder_parameters(sys, unwrap.(ps))..., cachesyms...) + @show exprs build_function_wrapper( sys, exprs, u_next, u, p..., iv; p_start = 3, extra_assignments, kwargs...) end @@ -381,6 +382,12 @@ function SciMLBase.ImplicitDiscreteFunction{iip, specialize}( f(u_next, u, p, t) = f_oop(u_next, u, p, t) f(resid, u_next, u, p, t) = f_iip(resid, u_next, u, p, t) + if length(dvs) == length(equations(sys)) + resid_prototype = nothing + else + resid_prototype = calculate_resid_prototype(length(equations(sys)), u0, p) + end + if specialize === SciMLBase.FunctionWrapperSpecialize && iip if u0 === nothing || p === nothing || t === nothing error("u0, p, and t must be specified for FunctionWrapperSpecialize on ImplicitDiscreteFunction.") @@ -395,6 +402,7 @@ function SciMLBase.ImplicitDiscreteFunction{iip, specialize}( sys = sys, observed = observedfun, analytic = analytic, + resid_prototype = resid_prototype, kwargs...) end diff --git a/test/jumpsystem.jl b/test/jumpsystem.jl index 6367552330..a9b76b1e89 100644 --- a/test/jumpsystem.jl +++ b/test/jumpsystem.jl @@ -2,6 +2,7 @@ using ModelingToolkit, DiffEqBase, JumpProcesses, Test, LinearAlgebra using Random, StableRNGs, NonlinearSolve using OrdinaryDiffEq using ModelingToolkit: t_nounits as t, D_nounits as D +using BenchmarkTools MT = ModelingToolkit rng = StableRNG(12345) @@ -79,7 +80,7 @@ function getmean(jprob, Nsims; use_stepper = true) end m / Nsims end -m = getmean(jprob, Nsims) +@btime m = $getmean($jprob, $Nsims) # test auto-alg selection works jprobb = JumpProblem(js2, dprob; save_positions = (false, false), rng) diff --git a/test/symbolic_events.jl b/test/symbolic_events.jl index c1b631b6bc..f3f0d51199 100644 --- a/test/symbolic_events.jl +++ b/test/symbolic_events.jl @@ -18,1215 +18,1215 @@ eqs = [D(x) ~ 1] affect = [x ~ 0] affect_neg = [x ~ 1] -@testset "SymbolicContinuousCallback constructors" begin - e = SymbolicContinuousCallback(eqs[]) - @test e isa SymbolicContinuousCallback - @test isequal(equations(e), eqs) - @test e.affect == nothing - @test e.affect_neg == nothing - @test e.rootfind == SciMLBase.LeftRootFind - - e = SymbolicContinuousCallback(eqs) - @test e isa SymbolicContinuousCallback - @test isequal(equations(e), eqs) - @test e.affect == nothing - @test e.affect_neg == nothing - @test e.rootfind == SciMLBase.LeftRootFind - - e = SymbolicContinuousCallback(eqs, nothing) - @test e isa SymbolicContinuousCallback - @test isequal(equations(e), eqs) - @test e.affect == nothing - @test e.affect_neg == nothing - @test e.rootfind == SciMLBase.LeftRootFind - - e = SymbolicContinuousCallback(eqs[], nothing) - @test e isa SymbolicContinuousCallback - @test isequal(equations(e), eqs) - @test e.affect == nothing - @test e.affect_neg == nothing - @test e.rootfind == SciMLBase.LeftRootFind - - e = SymbolicContinuousCallback(eqs => nothing) - @test e isa SymbolicContinuousCallback - @test isequal(equations(e), eqs) - @test e.affect == nothing - @test e.affect_neg == nothing - @test e.rootfind == SciMLBase.LeftRootFind - - e = SymbolicContinuousCallback(eqs[] => nothing) - @test e isa SymbolicContinuousCallback - @test isequal(equations(e), eqs) - @test e.affect == nothing - @test e.affect_neg == nothing - @test e.rootfind == SciMLBase.LeftRootFind - - ## With affect - e = SymbolicContinuousCallback(eqs[], affect) - @test e isa SymbolicContinuousCallback - @test isequal(equations(e), eqs) - @test e.rootfind == SciMLBase.LeftRootFind - - # with only positive edge affect - e = SymbolicContinuousCallback(eqs[], affect, affect_neg = nothing) - @test e isa SymbolicContinuousCallback - @test isequal(equations(e), eqs) - @test isnothing(e.affect_neg) - @test e.rootfind == SciMLBase.LeftRootFind - - # with explicit edge affects - e = SymbolicContinuousCallback(eqs[], affect, affect_neg = affect_neg) - @test e isa SymbolicContinuousCallback - @test isequal(equations(e), eqs) - @test e.rootfind == SciMLBase.LeftRootFind - - # with different root finding ops - e = SymbolicContinuousCallback( - eqs[], affect, affect_neg = affect_neg, rootfind = SciMLBase.LeftRootFind) - @test e isa SymbolicContinuousCallback - @test isequal(equations(e), eqs) - @test e.rootfind == SciMLBase.LeftRootFind - - # test plural constructor - e = SymbolicContinuousCallbacks(eqs[]) - @test e isa Vector{SymbolicContinuousCallback} - @test isequal(equations(e[]), eqs) - @test e[].affect == nothing - - e = SymbolicContinuousCallbacks(eqs) - @test e isa Vector{SymbolicContinuousCallback} - @test isequal(equations(e[]), eqs) - @test e[].affect == nothing - - e = SymbolicContinuousCallbacks(eqs[] => affect) - @test e isa Vector{SymbolicContinuousCallback} - @test isequal(equations(e[]), eqs) - @test e[].affect isa AffectSystem - - e = SymbolicContinuousCallbacks(eqs => affect) - @test e isa Vector{SymbolicContinuousCallback} - @test isequal(equations(e[]), eqs) - @test e[].affect isa AffectSystem - - e = SymbolicContinuousCallbacks([eqs[] => affect]) - @test e isa Vector{SymbolicContinuousCallback} - @test isequal(equations(e[]), eqs) - @test e[].affect isa AffectSystem - - e = SymbolicContinuousCallbacks([eqs => affect]) - @test e isa Vector{SymbolicContinuousCallback} - @test isequal(equations(e[]), eqs) - @test e[].affect isa AffectSystem -end - -@testset "ImperativeAffect constructors" begin - fmfa(o, x, i, c) = nothing - m = ModelingToolkit.ImperativeAffect(fmfa) - @test m isa ModelingToolkit.ImperativeAffect - @test m.f == fmfa - @test m.obs == [] - @test m.obs_syms == [] - @test m.modified == [] - @test m.mod_syms == [] - @test m.ctx === nothing - - m = ModelingToolkit.ImperativeAffect(fmfa, (;)) - @test m isa ModelingToolkit.ImperativeAffect - @test m.f == fmfa - @test m.obs == [] - @test m.obs_syms == [] - @test m.modified == [] - @test m.mod_syms == [] - @test m.ctx === nothing - - m = ModelingToolkit.ImperativeAffect(fmfa, (; x)) - @test m isa ModelingToolkit.ImperativeAffect - @test m.f == fmfa - @test isequal(m.obs, []) - @test m.obs_syms == [] - @test isequal(m.modified, [x]) - @test m.mod_syms == [:x] - @test m.ctx === nothing - - m = ModelingToolkit.ImperativeAffect(fmfa, (; y = x)) - @test m isa ModelingToolkit.ImperativeAffect - @test m.f == fmfa - @test isequal(m.obs, []) - @test m.obs_syms == [] - @test isequal(m.modified, [x]) - @test m.mod_syms == [:y] - @test m.ctx === nothing - - m = ModelingToolkit.ImperativeAffect(fmfa; observed = (; y = x)) - @test m isa ModelingToolkit.ImperativeAffect - @test m.f == fmfa - @test isequal(m.obs, [x]) - @test m.obs_syms == [:y] - @test m.modified == [] - @test m.mod_syms == [] - @test m.ctx === nothing - - m = ModelingToolkit.ImperativeAffect(fmfa; modified = (; x)) - @test m isa ModelingToolkit.ImperativeAffect - @test m.f == fmfa - @test isequal(m.obs, []) - @test m.obs_syms == [] - @test isequal(m.modified, [x]) - @test m.mod_syms == [:x] - @test m.ctx === nothing - - m = ModelingToolkit.ImperativeAffect(fmfa; modified = (; y = x)) - @test m isa ModelingToolkit.ImperativeAffect - @test m.f == fmfa - @test isequal(m.obs, []) - @test m.obs_syms == [] - @test isequal(m.modified, [x]) - @test m.mod_syms == [:y] - @test m.ctx === nothing - - m = ModelingToolkit.ImperativeAffect(fmfa, (; x), (; x)) - @test m isa ModelingToolkit.ImperativeAffect - @test m.f == fmfa - @test isequal(m.obs, [x]) - @test m.obs_syms == [:x] - @test isequal(m.modified, [x]) - @test m.mod_syms == [:x] - @test m.ctx === nothing - - m = ModelingToolkit.ImperativeAffect(fmfa, (; y = x), (; y = x)) - @test m isa ModelingToolkit.ImperativeAffect - @test m.f == fmfa - @test isequal(m.obs, [x]) - @test m.obs_syms == [:y] - @test isequal(m.modified, [x]) - @test m.mod_syms == [:y] - @test m.ctx === nothing - - m = ModelingToolkit.ImperativeAffect( - fmfa; modified = (; y = x), observed = (; y = x)) - @test m isa ModelingToolkit.ImperativeAffect - @test m.f == fmfa - @test isequal(m.obs, [x]) - @test m.obs_syms == [:y] - @test isequal(m.modified, [x]) - @test m.mod_syms == [:y] - @test m.ctx === nothing - - m = ModelingToolkit.ImperativeAffect( - fmfa; modified = (; y = x), observed = (; y = x), ctx = 3) - @test m isa ModelingToolkit.ImperativeAffect - @test m.f == fmfa - @test isequal(m.obs, [x]) - @test m.obs_syms == [:y] - @test isequal(m.modified, [x]) - @test m.mod_syms == [:y] - @test m.ctx === 3 - - m = ModelingToolkit.ImperativeAffect(fmfa, (; x), (; x), 3) - @test m isa ModelingToolkit.ImperativeAffect - @test m.f == fmfa - @test isequal(m.obs, [x]) - @test m.obs_syms == [:x] - @test isequal(m.modified, [x]) - @test m.mod_syms == [:x] - @test m.ctx === 3 -end - -@testset "Condition Compilation" begin - @named sys = ODESystem(eqs, t, continuous_events = [x ~ 1]) - @test getfield(sys, :continuous_events)[] == - SymbolicContinuousCallback(Equation[x ~ 1], nothing) - @test isequal(equations(getfield(sys, :continuous_events))[], x ~ 1) - fsys = flatten(sys) - @test isequal(equations(getfield(fsys, :continuous_events))[], x ~ 1) - - @named sys2 = ODESystem([D(x) ~ 1], t, continuous_events = [x ~ 2], systems = [sys]) - @test getfield(sys2, :continuous_events)[] == - SymbolicContinuousCallback(Equation[x ~ 2], nothing) - @test all(ModelingToolkit.continuous_events(sys2) .== [ - SymbolicContinuousCallback(Equation[x ~ 2], nothing), - SymbolicContinuousCallback(Equation[sys.x ~ 1], nothing) - ]) - - @test isequal(equations(getfield(sys2, :continuous_events))[1], x ~ 2) - @test length(ModelingToolkit.continuous_events(sys2)) == 2 - @test isequal(equations(ModelingToolkit.continuous_events(sys2)[1])[], x ~ 2) - @test isequal(equations(ModelingToolkit.continuous_events(sys2)[2])[], sys.x ~ 1) - - sys = complete(sys) - sys_nosplit = complete(sys; split = false) - sys2 = complete(sys2) - - # Test proper rootfinding - prob = ODEProblem(sys, Pair[], (0.0, 2.0)) - p0 = 0 - t0 = 0 - @test get_callback(prob) isa ModelingToolkit.DiffEqCallbacks.ContinuousCallback - cb = ModelingToolkit.generate_continuous_callbacks(sys) - cond = cb.condition - out = [0.0] - cond.f_iip(out, [0], p0, t0) - @test out[] ≈ -1 # signature is u,p,t - cond.f_iip(out, [1], p0, t0) - @test out[] ≈ 0 # signature is u,p,t - cond.f_iip(out, [2], p0, t0) - @test out[] ≈ 1 # signature is u,p,t - - prob = ODEProblem(sys, Pair[], (0.0, 2.0)) - prob_nosplit = ODEProblem(sys_nosplit, Pair[], (0.0, 2.0)) - sol = solve(prob, Tsit5()) - sol_nosplit = solve(prob_nosplit, Tsit5()) - @test minimum(t -> abs(t - 1), sol.t) < 1e-10 # test that the solver stepped at the root - @test minimum(t -> abs(t - 1), sol_nosplit.t) < 1e-10 # test that the solver stepped at the root - - # Test user-provided callback is respected - test_callback = DiscreteCallback(x -> x, x -> x) - prob = ODEProblem(sys, Pair[], (0.0, 2.0), callback = test_callback) - prob_nosplit = ODEProblem(sys_nosplit, Pair[], (0.0, 2.0), callback = test_callback) - cbs = get_callback(prob) - cbs_nosplit = get_callback(prob_nosplit) - @test cbs isa CallbackSet - @test cbs.discrete_callbacks[1] == test_callback - @test cbs_nosplit isa CallbackSet - @test cbs_nosplit.discrete_callbacks[1] == test_callback - - prob = ODEProblem(sys2, Pair[], (0.0, 3.0)) - cb = get_callback(prob) - @test cb isa ModelingToolkit.DiffEqCallbacks.VectorContinuousCallback - - cond = cb.condition - out = [0.0, 0.0] - # the root to find is 2 - cond.f_iip(out, [0, 0], p0, t0) - @test out[1] ≈ -2 # signature is u,p,t - cond.f_iip(out, [1, 0], p0, t0) - @test out[1] ≈ -1 # signature is u,p,t - cond.f_iip(out, [2, 0], p0, t0) # this should return 0 - @test out[1] ≈ 0 # signature is u,p,t - - # the root to find is 1 - out = [0.0, 0.0] - cond.f_iip(out, [0, 0], p0, t0) - @test out[2] ≈ -1 # signature is u,p,t - cond.f_iip(out, [0, 1], p0, t0) # this should return 0 - @test out[2] ≈ 0 # signature is u,p,t - cond.f_iip(out, [0, 2], p0, t0) - @test out[2] ≈ 1 # signature is u,p,t - - sol = solve(prob, Tsit5()) - @test minimum(t -> abs(t - 1), sol.t) < 1e-10 # test that the solver stepped at the first root - @test minimum(t -> abs(t - 2), sol.t) < 1e-10 # test that the solver stepped at the second root - - @named sys = ODESystem(eqs, t, continuous_events = [x ~ 1, x ~ 2]) # two root eqs using the same unknown - sys = complete(sys) - prob = ODEProblem(sys, Pair[], (0.0, 3.0)) - @test get_callback(prob) isa ModelingToolkit.DiffEqCallbacks.VectorContinuousCallback - sol = solve(prob, Tsit5()) - @test minimum(t -> abs(t - 1), sol.t) < 1e-10 # test that the solver stepped at the first root - @test minimum(t -> abs(t - 2), sol.t) < 1e-10 # test that the solver stepped at the second root -end - -@testset "Bouncing Ball" begin - ###### 1D Bounce - @variables x(t)=1 v(t)=0 - - root_eqs = [x ~ 0] - affect = [v ~ -Pre(v)] - - @named ball = ODESystem( - [D(x) ~ v - D(v) ~ -9.8], t, continuous_events = root_eqs => affect) - - @test only(continuous_events(ball)) == - SymbolicContinuousCallback(Equation[x ~ 0], Equation[v ~ -Pre(v)]) - ball = structural_simplify(ball) - - @test length(ModelingToolkit.continuous_events(ball)) == 1 - - tspan = (0.0, 5.0) - prob = ODEProblem(ball, Pair[], tspan) - sol = solve(prob, Tsit5()) - @test 0 <= minimum(sol[x]) <= 1e-10 # the ball never went through the floor but got very close - - ###### 2D bouncing ball - @variables x(t)=1 y(t)=0 vx(t)=0 vy(t)=1 - - events = [[x ~ 0] => [vx ~ -Pre(vx)] - [y ~ -1.5, y ~ 1.5] => [vy ~ -Pre(vy)]] - - @named ball = ODESystem( - [D(x) ~ vx - D(y) ~ vy - D(vx) ~ -9.8 - D(vy) ~ -0.01vy], t; continuous_events = events) - - _ball = ball - ball = structural_simplify(_ball) - ball_nosplit = structural_simplify(_ball; split = false) - - tspan = (0.0, 5.0) - prob = ODEProblem(ball, Pair[], tspan) - prob_nosplit = ODEProblem(ball_nosplit, Pair[], tspan) - - cb = get_callback(prob) - @test cb isa ModelingToolkit.DiffEqCallbacks.VectorContinuousCallback - @test getfield(ball, :continuous_events)[1] == - SymbolicContinuousCallback(Equation[x ~ 0], Equation[vx ~ -Pre(vx)]) - @test getfield(ball, :continuous_events)[2] == - SymbolicContinuousCallback(Equation[y ~ -1.5, y ~ 1.5], Equation[vy ~ -Pre(vy)]) - cond = cb.condition - out = [0.0, 0.0, 0.0] - p0 = 0. - t0 = 0. - cond.f_iip(out, [0, 0, 0, 0], p0, t0) - @test out ≈ [0, 1.5, -1.5] - - sol = solve(prob, Tsit5()) - sol_nosplit = solve(prob_nosplit, Tsit5()) - @test 0 <= minimum(sol[x]) <= 1e-10 # the ball never went through the floor but got very close - @test minimum(sol[y]) ≈ -1.5 # check wall conditions - @test maximum(sol[y]) ≈ 1.5 # check wall conditions - @test 0 <= minimum(sol_nosplit[x]) <= 1e-10 # the ball never went through the floor but got very close - @test minimum(sol_nosplit[y]) ≈ -1.5 # check wall conditions - @test maximum(sol_nosplit[y]) ≈ 1.5 # check wall conditions - - ## Test multi-variable affect - # in this test, there are two variables affected by a single event. - events = [[x ~ 0] => [vx ~ -Pre(vx), vy ~ -Pre(vy)]] - - @named ball = ODESystem([D(x) ~ vx - D(y) ~ vy - D(vx) ~ -1 - D(vy) ~ 0], t; continuous_events = events) - - ball_nosplit = structural_simplify(ball) - ball = structural_simplify(ball) - - tspan = (0.0, 5.0) - prob = ODEProblem(ball, Pair[], tspan) - prob_nosplit = ODEProblem(ball_nosplit, Pair[], tspan) - sol = solve(prob, Tsit5()) - sol_nosplit = solve(prob_nosplit, Tsit5()) - @test 0 <= minimum(sol[x]) <= 1e-10 # the ball never went through the floor but got very close - @test -minimum(sol[y]) ≈ maximum(sol[y]) ≈ sqrt(2) # the ball will never go further than √2 in either direction (gravity was changed to 1 to get this particular number) - @test 0 <= minimum(sol_nosplit[x]) <= 1e-10 # the ball never went through the floor but got very close - @test -minimum(sol_nosplit[y]) ≈ maximum(sol_nosplit[y]) ≈ sqrt(2) # the ball will never go further than √2 in either direction (gravity was changed to 1 to get this particular number) -end - -# issue https://github.com/SciML/ModelingToolkit.jl/issues/1386 -# tests that it works for ODAESystem -@testset "ODAESystem" begin - @variables vs(t) v(t) vmeasured(t) - eq = [vs ~ sin(2pi * t) - D(v) ~ vs - v - D(vmeasured) ~ 0.0] - ev = [sin(20pi * t) ~ 0.0] => [vmeasured ~ Pre(v)] - @named sys = ODESystem(eq, t, continuous_events = ev) - sys = structural_simplify(sys) - prob = ODEProblem(sys, zeros(2), (0.0, 5.1)) - sol = solve(prob, Tsit5()) - @test all(minimum((0:0.1:5) .- sol.t', dims = 2) .< 0.0001) # test that the solver stepped every 0.1s as dictated by event - @test sol([0.25])[vmeasured][] == sol([0.23])[vmeasured][] # test the hold property -end - -## https://github.com/SciML/ModelingToolkit.jl/issues/1528 -@testset "Handle Empty Events" begin - Dₜ = D - - @parameters u(t) [input = true] # Indicate that this is a controlled input - @parameters y(t) [output = true] # Indicate that this is a measured output - - function Mass(; name, m = 1.0, p = 0, v = 0) - ps = @parameters m = m - sts = @variables pos(t)=p vel(t)=v - eqs = Dₜ(pos) ~ vel - ODESystem(eqs, t, [pos, vel], ps; name) - end - function Spring(; name, k = 1e4) - ps = @parameters k = k - @variables x(t) = 0 # Spring deflection - ODESystem(Equation[], t, [x], ps; name) - end - function Damper(; name, c = 10) - ps = @parameters c = c - @variables vel(t) = 0 - ODESystem(Equation[], t, [vel], ps; name) - end - function SpringDamper(; name, k = false, c = false) - spring = Spring(; name = :spring, k) - damper = Damper(; name = :damper, c) - compose(ODESystem(Equation[], t; name), - spring, damper) - end - connect_sd(sd, m1, m2) = [ - sd.spring.x ~ m1.pos - m2.pos, sd.damper.vel ~ m1.vel - m2.vel] - sd_force(sd) = -sd.spring.k * sd.spring.x - sd.damper.c * sd.damper.vel - @named mass1 = Mass(; m = 1) - @named mass2 = Mass(; m = 1) - @named sd = SpringDamper(; k = 1000, c = 10) - function Model(u, d = 0) - eqs = [connect_sd(sd, mass1, mass2) - Dₜ(mass1.vel) ~ (sd_force(sd) + u) / mass1.m - Dₜ(mass2.vel) ~ (-sd_force(sd) + d) / mass2.m] - @named _model = ODESystem(eqs, t; observed = [y ~ mass2.pos]) - @named model = compose(_model, mass1, mass2, sd) - end - model = Model(sin(30t)) - sys = structural_simplify(model) - @test isempty(ModelingToolkit.continuous_events(sys)) -end - -@testset "ODESystem Discrete Callbacks" begin - function testsol(osys, u0, p, tspan; tstops = Float64[], paramtotest = nothing, - kwargs...) - oprob = ODEProblem(complete(osys), u0, tspan, p; kwargs...) - sol = solve(oprob, Tsit5(); tstops = tstops, abstol = 1e-10, reltol = 1e-10) - @test isapprox(sol(1.0000000001)[1] - sol(0.999999999)[1], 1.0; rtol = 1e-6) - paramtotest === nothing || (@test sol.ps[paramtotest] == 1.0) - @test isapprox(sol(4.0)[1], 2 * exp(-2.0)) - sol - end - - @parameters k t1 t2 - @variables A(t) B(t) - - cond1 = (t == t1) - affect1 = [A ~ Pre(A) + 1] - cb1 = cond1 => affect1 - cond2 = (t == t2) - affect2 = [k ~ 1.0] - cb2 = cond2 => affect2 - - ∂ₜ = D - eqs = [∂ₜ(A) ~ -k * A] - @named osys = ODESystem(eqs, t, [A], [k, t1, t2], discrete_events = [cb1, cb2]) - u0 = [A => 1.0] - p = [k => 0.0, t1 => 1.0, t2 => 2.0] - tspan = (0.0, 4.0) - testsol(osys, u0, p, tspan; tstops = [1.0, 2.0], paramtotest = k) - - cond1a = (t == t1) - affect1a = [A ~ Pre(A) + 1, B ~ A] - cb1a = cond1a => affect1a - @named osys1 = ODESystem(eqs, t, [A, B], [k, t1, t2], discrete_events = [cb1a, cb2]) - u0′ = [A => 1.0, B => 0.0] - sol = testsol( - osys1, u0′, p, tspan; tstops = [1.0, 2.0], check_length = false, paramtotest = k) - @test sol(1.0000001, idxs = B) == 2.0 - - # same as above - but with set-time event syntax - cb1‵ = [1.0] => affect1 # needs to be a Vector for the event to happen only once - cb2‵ = [2.0] => affect2 - @named osys‵ = ODESystem(eqs, t, [A], [k], discrete_events = [cb1‵, cb2‵]) - testsol(osys‵, u0, p, tspan; paramtotest = k) - - # mixing discrete affects - @named osys3 = ODESystem(eqs, t, [A], [k, t1, t2], discrete_events = [cb1, cb2‵]) - testsol(osys3, 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 - end - cb2‵‵ = [2.0] => (affect!, [], [k], [k], nothing) - @named osys4 = ODESystem(eqs, t, [A], [k, t1], discrete_events = [cb1, cb2‵‵]) - oprob4 = ODEProblem(complete(osys4), u0, tspan, p) - testsol(osys4, u0, p, tspan; tstops = [1.0], paramtotest = k) - - # mixing with symbolic condition in the func affect - cb2‵‵‵ = (t == t2) => (affect!, [], [k], [k], nothing) - @named osys5 = ODESystem(eqs, t, [A], [k, t1, t2], discrete_events = [cb1, cb2‵‵‵]) - testsol(osys5, u0, p, tspan; tstops = [1.0, 2.0]) - @named osys6 = ODESystem(eqs, t, [A], [k, t1, t2], discrete_events = [cb2‵‵‵, cb1]) - testsol(osys6, u0, p, tspan; tstops = [1.0, 2.0], paramtotest = k) - - # mix a continuous event too - cond3 = A ~ 0.1 - affect3 = [k ~ 0.0] - cb3 = cond3 => affect3 - @named osys7 = ODESystem(eqs, t, [A], [k, t1, t2], discrete_events = [cb1, cb2‵‵‵], - continuous_events = [cb3]) - sol = testsol(osys7, u0, p, (0.0, 10.0); tstops = [1.0, 2.0]) - @test isapprox(sol(10.0)[1], 0.1; atol = 1e-10, rtol = 1e-10) -end - -@testset "SDESystem Discrete Callbacks" begin - function testsol(ssys, u0, p, tspan; tstops = Float64[], paramtotest = nothing, - kwargs...) - sprob = SDEProblem(complete(ssys), u0, tspan, p; kwargs...) - sol = solve(sprob, RI5(); tstops = tstops, abstol = 1e-10, reltol = 1e-10) - @test isapprox(sol(1.0000000001)[1] - sol(0.999999999)[1], 1.0; rtol = 1e-4) - paramtotest === nothing || (@test sol.ps[paramtotest] == 1.0) - @test isapprox(sol(4.0)[1], 2 * exp(-2.0), atol = 1e-4) - sol - end - - @parameters k t1 t2 - @variables A(t) B(t) - - cond1 = (t == t1) - affect1 = [A ~ Pre(A) + 1] - cb1 = cond1 => affect1 - cond2 = (t == t2) - affect2 = [k ~ 1.0] - cb2 = cond2 => affect2 - - ∂ₜ = D - eqs = [∂ₜ(A) ~ -k * A] - @named ssys = SDESystem(eqs, [0.0], t, [A], [k, t1, t2], - discrete_events = [cb1, cb2]) - u0 = [A => 1.0] - p = [k => 0.0, t1 => 1.0, t2 => 2.0] - tspan = (0.0, 4.0) - testsol(ssys, u0, p, tspan; tstops = [1.0, 2.0], paramtotest = k) - - cond1a = (t == t1) - affect1a = [A ~ Pre(A) + 1, B ~ A] - cb1a = cond1a => affect1a - @named ssys1 = SDESystem(eqs, [0.0], t, [A, B], [k, t1, t2], - discrete_events = [cb1a, cb2]) - u0′ = [A => 1.0, B => 0.0] - sol = testsol( - ssys1, u0′, p, tspan; tstops = [1.0, 2.0], check_length = false, paramtotest = k) - @test sol(1.0000001, idxs = 2) == 2.0 - - # same as above - but with set-time event syntax - cb1‵ = [1.0] => affect1 # needs to be a Vector for the event to happen only once - cb2‵ = [2.0] => affect2 - @named ssys‵ = SDESystem(eqs, [0.0], t, [A], [k], discrete_events = [cb1‵, cb2‵]) - testsol(ssys‵, u0, p, tspan; paramtotest = k) - - # mixing discrete affects - @named ssys3 = SDESystem(eqs, [0.0], t, [A], [k, t1, t2], - discrete_events = [cb1, cb2‵]) - testsol(ssys3, u0, p, tspan; tstops = [1.0], paramtotest = k) - - # mixing with a func affect - function affect!(integrator, u, p, ctx) - setp(integrator, p.k)(integrator, 1.0) - nothing - end - cb2‵‵ = [2.0] => (affect!, [], [k], [k], nothing) - @named ssys4 = SDESystem(eqs, [0.0], t, [A], [k, t1], - discrete_events = [cb1, cb2‵‵]) - testsol(ssys4, u0, p, tspan; tstops = [1.0], paramtotest = k) - - # mixing with symbolic condition in the func affect - cb2‵‵‵ = (t == t2) => (affect!, [], [k], [k], nothing) - @named ssys5 = SDESystem(eqs, [0.0], t, [A], [k, t1, t2], - discrete_events = [cb1, cb2‵‵‵]) - testsol(ssys5, u0, p, tspan; tstops = [1.0, 2.0], paramtotest = k) - @named ssys6 = SDESystem(eqs, [0.0], t, [A], [k, t1, t2], - discrete_events = [cb2‵‵‵, cb1]) - testsol(ssys6, u0, p, tspan; tstops = [1.0, 2.0], paramtotest = k) - - # mix a continuous event too - cond3 = A ~ 0.1 - affect3 = [k ~ 0.0] - cb3 = cond3 => affect3 - @named ssys7 = SDESystem(eqs, [0.0], t, [A], [k, t1, t2], - discrete_events = [cb1, cb2‵‵‵], - continuous_events = [cb3]) - sol = testsol(ssys7, u0, p, (0.0, 10.0); tstops = [1.0, 2.0]) - @test isapprox(sol(10.0)[1], 0.1; atol = 1e-10, rtol = 1e-10) -end - -@testset "JumpSystem Discrete Callbacks" begin - function testsol(jsys, u0, p, tspan; tstops = Float64[], paramtotest = nothing, - N = 40000, kwargs...) - jsys = complete(jsys) - dprob = DiscreteProblem(jsys, u0, tspan, p) - jprob = JumpProblem(jsys, dprob, Direct(); kwargs...) - sol = solve(jprob, SSAStepper(); tstops = tstops) - @test (sol(1.000000000001)[1] - sol(0.99999999999)[1]) == 1 - paramtotest === nothing || (@test sol.ps[paramtotest] == 1.0) - @test sol(40.0)[1] == 0 - sol - end - - @parameters k t1 t2 - @variables A(t) B(t) - - cond1 = (t == t1) - affect1 = [A ~ Pre(A) + 1] - cb1 = cond1 => affect1 - cond2 = (t == t2) - affect2 = [k ~ 1.0] - cb2 = cond2 => affect2 - - eqs = [MassActionJump(k, [A => 1], [A => -1])] - @named jsys = JumpSystem(eqs, t, [A], [k, t1, t2], discrete_events = [cb1, cb2]) - u0 = [A => 1] - p = [k => 0.0, t1 => 1.0, t2 => 2.0] - tspan = (0.0, 40.0) - testsol(jsys, u0, p, tspan; tstops = [1.0, 2.0], rng, paramtotest = k) - - cond1a = (t == t1) - affect1a = [A ~ Pre(A) + 1, B ~ A] - cb1a = cond1a => affect1a - @named jsys1 = JumpSystem(eqs, t, [A, B], [k, t1, t2], discrete_events = [cb1a, cb2]) - u0′ = [A => 1, B => 0] - sol = testsol(jsys1, u0′, p, tspan; tstops = [1.0, 2.0], - check_length = false, rng, paramtotest = k) - @test sol(1.000000001, idxs = B) == 2 - - # same as above - but with set-time event syntax - cb1‵ = [1.0] => affect1 # needs to be a Vector for the event to happen only once - cb2‵ = [2.0] => affect2 - @named jsys‵ = JumpSystem(eqs, t, [A], [k], discrete_events = [cb1‵, cb2‵]) - testsol(jsys‵, u0, [p[1]], tspan; rng, paramtotest = k) - - # mixing discrete affects - @named jsys3 = JumpSystem(eqs, t, [A], [k, t1, t2], discrete_events = [cb1, cb2‵]) - 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 - end - cb2‵‵ = [2.0] => (affect!, [], [k], [k], nothing) - @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) - @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]) - testsol(jsys6, u0, p, tspan; tstops = [1.0, 2.0], rng, paramtotest = k) -end - -@testset "Namespacing" begin - function oscillator_ce(k = 1.0; name) - sts = @variables x(t)=1.0 v(t)=0.0 F(t) - ps = @parameters k=k Θ=0.5 - eqs = [D(x) ~ v, D(v) ~ -k * x + F] - ev = [x ~ Θ] => [x ~ 1.0, v ~ 0.0] - ODESystem(eqs, t, sts, ps, continuous_events = [ev]; name) - end - - @named oscce = oscillator_ce() - eqs = [oscce.F ~ 0] - @named eqs_sys = ODESystem(eqs, t) - @named oneosc_ce = compose(eqs_sys, oscce) - oneosc_ce_simpl = structural_simplify(oneosc_ce) - - prob = ODEProblem(oneosc_ce_simpl, [], (0.0, 2.0), []) - sol = solve(prob, Tsit5(), saveat = 0.1) - - @test typeof(oneosc_ce_simpl) == ODESystem - @test sol[1, 6] < 1.0 # test whether x(t) decreases over time - @test sol[1, 18] > 0.5 # test whether event happened -end - -@testset "Additional SymbolicContinuousCallback options" begin - # 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]) - cr1 = [] - cr2 = [] - evt1 = ModelingToolkit.SymbolicContinuousCallback( - [c1 ~ 0], (record_crossings, [c1 => :v], [], [], cr1)) - evt2 = ModelingToolkit.SymbolicContinuousCallback( - [c2 ~ 0], (record_crossings, [c2 => :v], [], [], cr2)) - @named trigsys = ODESystem(eqs, t; continuous_events = [evt1, evt2]) - trigsys_ss = structural_simplify(trigsys) - prob = ODEProblem(trigsys_ss, [], (0.0, 2π)) - sol = solve(prob, Tsit5()) - required_crossings_c1 = [π / 2, 3 * π / 2] - required_crossings_c2 = [π / 6, π / 2, 5 * π / 6, 7 * π / 6, 3 * π / 2, 11 * π / 6] - @test maximum(abs.(first.(cr1) .- required_crossings_c1)) < 1e-4 - @test maximum(abs.(first.(cr2) .- required_crossings_c2)) < 1e-4 - @test sign.(cos.(required_crossings_c1 .- 1e-6)) == sign.(last.(cr1)) - @test sign.(cos.(3 * (required_crossings_c2 .- 1e-6))) == sign.(last.(cr2)) - - # with neg affect (pos * neg + left root find) - cr1p = [] - cr2p = [] - cr1n = [] - cr2n = [] - evt1 = ModelingToolkit.SymbolicContinuousCallback( - [c1 ~ 0], (record_crossings, [c1 => :v], [], [], cr1p); - affect_neg = (record_crossings, [c1 => :v], [], [], cr1n)) - evt2 = ModelingToolkit.SymbolicContinuousCallback( - [c2 ~ 0], (record_crossings, [c2 => :v], [], [], cr2p); - affect_neg = (record_crossings, [c2 => :v], [], [], cr2n)) - @named trigsys = ODESystem(eqs, t; continuous_events = [evt1, evt2]) - trigsys_ss = structural_simplify(trigsys) - prob = ODEProblem(trigsys_ss, [], (0.0, 2π)) - sol = solve(prob, Tsit5(); dtmax = 0.01) - c1_pc = filter((<=)(0) ∘ sin, required_crossings_c1) - c1_nc = filter((>=)(0) ∘ sin, required_crossings_c1) - c2_pc = filter(c -> -sin(3c) > 0, required_crossings_c2) - c2_nc = filter(c -> -sin(3c) < 0, required_crossings_c2) - @test maximum(abs.(c1_pc .- first.(cr1p))) < 1e-5 - @test maximum(abs.(c1_nc .- first.(cr1n))) < 1e-5 - @test maximum(abs.(c2_pc .- first.(cr2p))) < 1e-5 - @test maximum(abs.(c2_nc .- first.(cr2n))) < 1e-5 - @test sign.(cos.(c1_pc .- 1e-6)) == sign.(last.(cr1p)) - @test sign.(cos.(c1_nc .- 1e-6)) == sign.(last.(cr1n)) - @test sign.(cos.(3 * (c2_pc .- 1e-6))) == sign.(last.(cr2p)) - @test sign.(cos.(3 * (c2_nc .- 1e-6))) == sign.(last.(cr2n)) - - # with nothing neg affect (pos * neg + left root find) - cr1p = [] - cr2p = [] - evt1 = ModelingToolkit.SymbolicContinuousCallback( - [c1 ~ 0], (record_crossings, [c1 => :v], [], [], cr1p); affect_neg = nothing) - evt2 = ModelingToolkit.SymbolicContinuousCallback( - [c2 ~ 0], (record_crossings, [c2 => :v], [], [], cr2p); affect_neg = nothing) - @named trigsys = ODESystem(eqs, t; continuous_events = [evt1, evt2]) - trigsys_ss = structural_simplify(trigsys) - prob = ODEProblem(trigsys_ss, [], (0.0, 2π)) - sol = solve(prob, Tsit5(); dtmax = 0.01) - @test maximum(abs.(c1_pc .- first.(cr1p))) < 1e-5 - @test maximum(abs.(c2_pc .- first.(cr2p))) < 1e-5 - @test sign.(cos.(c1_pc .- 1e-6)) == sign.(last.(cr1p)) - @test sign.(cos.(3 * (c2_pc .- 1e-6))) == sign.(last.(cr2p)) - - #mixed - cr1p = [] - cr2p = [] - cr1n = [] - cr2n = [] - evt1 = ModelingToolkit.SymbolicContinuousCallback( - [c1 ~ 0], (record_crossings, [c1 => :v], [], [], cr1p); affect_neg = nothing) - evt2 = ModelingToolkit.SymbolicContinuousCallback( - [c2 ~ 0], (record_crossings, [c2 => :v], [], [], cr2p); - affect_neg = (record_crossings, [c2 => :v], [], [], cr2n)) - @named trigsys = ODESystem(eqs, t; continuous_events = [evt1, evt2]) - trigsys_ss = structural_simplify(trigsys) - prob = ODEProblem(trigsys_ss, [], (0.0, 2π)) - sol = solve(prob, Tsit5(); dtmax = 0.01) - c1_pc = filter((<=)(0) ∘ sin, required_crossings_c1) - c2_pc = filter(c -> -sin(3c) > 0, required_crossings_c2) - c2_nc = filter(c -> -sin(3c) < 0, required_crossings_c2) - @test maximum(abs.(c1_pc .- first.(cr1p))) < 1e-5 - @test maximum(abs.(c2_pc .- first.(cr2p))) < 1e-5 - @test maximum(abs.(c2_nc .- first.(cr2n))) < 1e-5 - @test sign.(cos.(c1_pc .- 1e-6)) == sign.(last.(cr1p)) - @test sign.(cos.(3 * (c2_pc .- 1e-6))) == sign.(last.(cr2p)) - @test sign.(cos.(3 * (c2_nc .- 1e-6))) == sign.(last.(cr2n)) - - # baseline affect w/ right rootfind (pos + neg + right root find) - @variables c1(t)=1.0 c2(t)=1.0 # c1 = cos(t), c2 = cos(3t) - cr1 = [] - cr2 = [] - evt1 = ModelingToolkit.SymbolicContinuousCallback( - [c1 ~ 0], (record_crossings, [c1 => :v], [], [], cr1); - rootfind = SciMLBase.RightRootFind) - evt2 = ModelingToolkit.SymbolicContinuousCallback( - [c2 ~ 0], (record_crossings, [c2 => :v], [], [], cr2); - rootfind = SciMLBase.RightRootFind) - @named trigsys = ODESystem(eqs, t; continuous_events = [evt1, evt2]) - trigsys_ss = structural_simplify(trigsys) - prob = ODEProblem(trigsys_ss, [], (0.0, 2π)) - sol = solve(prob, Tsit5(); dtmax = 0.01) - required_crossings_c1 = [π / 2, 3 * π / 2] - required_crossings_c2 = [π / 6, π / 2, 5 * π / 6, 7 * π / 6, 3 * π / 2, 11 * π / 6] - @test maximum(abs.(first.(cr1) .- required_crossings_c1)) < 1e-4 - @test maximum(abs.(first.(cr2) .- required_crossings_c2)) < 1e-4 - @test sign.(cos.(required_crossings_c1 .+ 1e-6)) == sign.(last.(cr1)) - @test sign.(cos.(3 * (required_crossings_c2 .+ 1e-6))) == sign.(last.(cr2)) - - # baseline affect w/ mixed rootfind (pos + neg + right root find) - cr1 = [] - cr2 = [] - evt1 = ModelingToolkit.SymbolicContinuousCallback( - [c1 ~ 0], (record_crossings, [c1 => :v], [], [], cr1); - rootfind = SciMLBase.LeftRootFind) - evt2 = ModelingToolkit.SymbolicContinuousCallback( - [c2 ~ 0], (record_crossings, [c2 => :v], [], [], cr2); - rootfind = SciMLBase.RightRootFind) - @named trigsys = ODESystem(eqs, t; continuous_events = [evt1, evt2]) - trigsys_ss = structural_simplify(trigsys) - prob = ODEProblem(trigsys_ss, [], (0.0, 2π)) - sol = solve(prob, Tsit5()) - @test maximum(abs.(first.(cr1) .- required_crossings_c1)) < 1e-4 - @test maximum(abs.(first.(cr2) .- required_crossings_c2)) < 1e-4 - @test sign.(cos.(required_crossings_c1 .- 1e-6)) == sign.(last.(cr1)) - @test sign.(cos.(3 * (required_crossings_c2 .+ 1e-6))) == sign.(last.(cr2)) - - #flip order and ensure results are okay - cr1 = [] - cr2 = [] - evt1 = ModelingToolkit.SymbolicContinuousCallback( - [c1 ~ 0], (record_crossings, [c1 => :v], [], [], cr1); - rootfind = SciMLBase.LeftRootFind) - evt2 = ModelingToolkit.SymbolicContinuousCallback( - [c2 ~ 0], (record_crossings, [c2 => :v], [], [], cr2); - rootfind = SciMLBase.RightRootFind) - @named trigsys = ODESystem(eqs, t; continuous_events = [evt2, evt1]) - trigsys_ss = structural_simplify(trigsys) - prob = ODEProblem(trigsys_ss, [], (0.0, 2π)) - sol = solve(prob, Tsit5()) - @test maximum(abs.(first.(cr1) .- required_crossings_c1)) < 1e-4 - @test maximum(abs.(first.(cr2) .- required_crossings_c2)) < 1e-4 - @test sign.(cos.(required_crossings_c1 .- 1e-6)) == sign.(last.(cr1)) - @test sign.(cos.(3 * (required_crossings_c2 .+ 1e-6))) == sign.(last.(cr2)) -end - -@testset "Discrete event reinitialization (#3142)" begin - @connector LiquidPort begin - p(t)::Float64, [description = "Set pressure in bar", - guess = 1.01325] - Vdot(t)::Float64, - [description = "Volume flow rate in L/min", - guess = 0.0, - connect = Flow] - end - - @mtkmodel PressureSource begin - @components begin - port = LiquidPort() - end - @parameters begin - p_set::Float64 = 1.01325, [description = "Set pressure in bar"] - end - @equations begin - port.p ~ p_set - end - end - - @mtkmodel BinaryValve begin - @constants begin - p_ref::Float64 = 1.0, [description = "Reference pressure drop in bar"] - ρ_ref::Float64 = 1000.0, [description = "Reference density in kg/m^3"] - end - @components begin - port_in = LiquidPort() - port_out = LiquidPort() - end - @parameters begin - k_V::Float64 = 1.0, [description = "Valve coefficient in L/min/bar"] - k_leakage::Float64 = 1e-08, [description = "Leakage coefficient in L/min/bar"] - ρ::Float64 = 1000.0, [description = "Density in kg/m^3"] - end - @variables begin - S(t)::Float64, [description = "Valve state", guess = 1.0, irreducible = true] - Δp(t)::Float64, [description = "Pressure difference in bar", guess = 1.0] - Vdot(t)::Float64, [description = "Volume flow rate in L/min", guess = 1.0] - end - @equations begin - # Port handling - port_in.Vdot ~ -Vdot - port_out.Vdot ~ Vdot - Δp ~ port_in.p - port_out.p - # System behavior - D(S) ~ 0.0 - Vdot ~ S * k_V * sign(Δp) * sqrt(abs(Δp) / p_ref * ρ_ref / ρ) + k_leakage * Δp # softplus alpha function to avoid negative values under the sqrt - end - end - - # Test System - @mtkmodel TestSystem begin - @components begin - pressure_source_1 = PressureSource(p_set = 2.0) - binary_valve_1 = BinaryValve(S = 1.0, k_leakage = 0.0) - binary_valve_2 = BinaryValve(S = 1.0, k_leakage = 0.0) - pressure_source_2 = PressureSource(p_set = 1.0) - end - @equations begin - connect(pressure_source_1.port, binary_valve_1.port_in) - connect(binary_valve_1.port_out, binary_valve_2.port_in) - connect(binary_valve_2.port_out, pressure_source_2.port) - end - @discrete_events begin - [30] => [binary_valve_1.S ~ 0.0, binary_valve_2.Δp ~ 0.0] - [60] => [ - binary_valve_1.S ~ 1.0, binary_valve_2.S ~ 0.0, binary_valve_2.Δp ~ 1.0] - [120] => [binary_valve_1.S ~ 0.0, binary_valve_2.Δp ~ 0.0] - end - end - - # Test Simulation - @mtkbuild sys = TestSystem() - - # Test Simulation - prob = ODEProblem(sys, [], (0.0, 150.0)) - sol = solve(prob) - @test sol[end] == [0.0, 0.0, 0.0] -end - -@testset "Discrete variable timeseries" begin - @variables x(t) - @parameters a(t) b(t) c(t) - cb1 = [x ~ 1.0] => [a ~ -Pre(a)] - function save_affect!(integ, u, p, ctx) - integ.ps[p.b] = 5.0 - end - cb2 = [x ~ 0.5] => (save_affect!, [], [b], [b], nothing) - cb3 = 1.0 => [c ~ t] - - @mtkbuild sys = ODESystem(D(x) ~ cos(t), t, [x], [a, b, c]; - continuous_events = [cb1, cb2], discrete_events = [cb3]) - prob = ODEProblem(sys, [x => 1.0], (0.0, 2pi), [a => 1.0, b => 2.0, c => 0.0]) - @test sort(canonicalize(Discrete(), prob.p)[1]) == [0.0, 1.0, 2.0] - sol = solve(prob, Tsit5()) - - @test sol[a] == [1.0, -1.0] - @test sol[b] == [2.0, 5.0, 5.0] - @test sol[c] == [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0] -end - -@testset "Heater" begin - @variables temp(t) - params = @parameters furnace_on_threshold=0.5 furnace_off_threshold=0.7 furnace_power=1.0 leakage=0.1 furnace_on::Bool=false - eqs = [ - D(temp) ~ furnace_on * furnace_power - temp^2 * leakage - ] - - furnace_off = ModelingToolkit.SymbolicContinuousCallback( - [temp ~ furnace_off_threshold], - ModelingToolkit.ImperativeAffect(modified = (; furnace_on)) do x, o, i, c - @set! x.furnace_on = false - end) - furnace_enable = ModelingToolkit.SymbolicContinuousCallback( - [temp ~ furnace_on_threshold], - ModelingToolkit.ImperativeAffect(modified = (; furnace_on)) do x, o, i, c - @set! x.furnace_on = true - end) - @named sys = ODESystem( - eqs, t, [temp], params; continuous_events = [furnace_off, furnace_enable]) - ss = structural_simplify(sys) - prob = ODEProblem(ss, [temp => 0.0, furnace_on => true], (0.0, 100.0)) - sol = solve(prob, Tsit5(); dtmax = 0.01) - @test all(sol[temp][sol.t .> 1.0] .<= 0.79) && all(sol[temp][sol.t .> 1.0] .>= 0.49) - - furnace_off = ModelingToolkit.SymbolicContinuousCallback( - [temp ~ furnace_off_threshold], - ModelingToolkit.ImperativeAffect(modified = (; furnace_on)) do x, o, c, i - @set! x.furnace_on = false - end; initialize = ModelingToolkit.ImperativeAffect(modified = (; - temp)) do x, o, c, i - @set! x.temp = 0.2 - end) - furnace_enable = ModelingToolkit.SymbolicContinuousCallback( - [temp ~ furnace_on_threshold], - ModelingToolkit.ImperativeAffect(modified = (; furnace_on)) do x, o, c, i - @set! x.furnace_on = true - end) - @named sys = ODESystem( - eqs, t, [temp], params; continuous_events = [furnace_off, furnace_enable]) - ss = structural_simplify(sys) - prob = ODEProblem(ss, [temp => 0.0, furnace_on => true], (0.0, 100.0)) - sol = solve(prob, Tsit5(); dtmax = 0.01) - @test all(sol[temp][sol.t .> 1.0] .<= 0.79) && all(sol[temp][sol.t .> 1.0] .>= 0.49) - @test all(sol[temp][sol.t .!= 0.0] .<= 0.79) && all(sol[temp][sol.t .!= 0.0] .>= 0.2) -end - -@testset "ImperativeAffect errors and warnings" begin - @variables temp(t) - params = @parameters furnace_on_threshold=0.5 furnace_off_threshold=0.7 furnace_power=1.0 leakage=0.1 furnace_on::Bool=false - eqs = [ - D(temp) ~ furnace_on * furnace_power - temp^2 * leakage - ] - - furnace_off = ModelingToolkit.SymbolicContinuousCallback( - [temp ~ furnace_off_threshold], - ModelingToolkit.ImperativeAffect( - modified = (; furnace_on), observed = (; furnace_on)) do x, o, c, i - @set! x.furnace_on = false - end) - @named sys = ODESystem(eqs, t, [temp], params; continuous_events = [furnace_off]) - ss = structural_simplify(sys) - @test_logs (:warn, - "The symbols Any[:furnace_on] are declared as both observed and modified; this is a code smell because it becomes easy to confuse them and assign/not assign a value.") prob=ODEProblem( - ss, [temp => 0.0, furnace_on => true], (0.0, 100.0)) - - @variables tempsq(t) # trivially eliminated - eqs = [tempsq ~ temp^2 - D(temp) ~ furnace_on * furnace_power - temp^2 * leakage] - - furnace_off = ModelingToolkit.SymbolicContinuousCallback( - [temp ~ furnace_off_threshold], - ModelingToolkit.ImperativeAffect( - modified = (; furnace_on, tempsq), observed = (; furnace_on)) do x, o, c, i - @set! x.furnace_on = false - end) - @named sys = ODESystem( - eqs, t, [temp, tempsq], params; continuous_events = [furnace_off]) - ss = structural_simplify(sys) - @test_throws "refers to missing variable(s)" prob=ODEProblem( - ss, [temp => 0.0, furnace_on => true], (0.0, 100.0)) - - @parameters not_actually_here - furnace_off = ModelingToolkit.SymbolicContinuousCallback( - [temp ~ furnace_off_threshold], - ModelingToolkit.ImperativeAffect(modified = (; furnace_on), - observed = (; furnace_on, not_actually_here)) do x, o, c, i - @set! x.furnace_on = false - end) - @named sys = ODESystem( - eqs, t, [temp, tempsq], params; continuous_events = [furnace_off]) - ss = structural_simplify(sys) - @test_throws "refers to missing variable(s)" prob=ODEProblem( - ss, [temp => 0.0, furnace_on => true], (0.0, 100.0)) - - furnace_off = ModelingToolkit.SymbolicContinuousCallback( - [temp ~ furnace_off_threshold], - ModelingToolkit.ImperativeAffect(modified = (; furnace_on), - observed = (; furnace_on)) do x, o, c, i - return (; fictional2 = false) - end) - @named sys = ODESystem( - eqs, t, [temp, tempsq], params; continuous_events = [furnace_off]) - ss = structural_simplify(sys) - prob = ODEProblem( - ss, [temp => 0.0, furnace_on => true], (0.0, 100.0)) - @test_throws "Tried to write back to" solve(prob, Tsit5()) -end - -@testset "Quadrature" begin - @variables theta(t) omega(t) - params = @parameters qA=0 qB=0 hA=0 hB=0 cnt::Int=0 - eqs = [D(theta) ~ omega - omega ~ 1.0] - function decoder(oldA, oldB, newA, newB) - state = (oldA, oldB, newA, newB) - if state == (0, 0, 1, 0) || state == (1, 0, 1, 1) || state == (1, 1, 0, 1) || - state == (0, 1, 0, 0) - return 1 - elseif state == (0, 0, 0, 1) || state == (0, 1, 1, 1) || state == (1, 1, 1, 0) || - state == (1, 0, 0, 0) - return -1 - elseif state == (0, 0, 0, 0) || state == (0, 1, 0, 1) || state == (1, 0, 1, 0) || - state == (1, 1, 1, 1) - return 0 - else - return 0 # err is interpreted as no movement - end - end - qAevt = ModelingToolkit.SymbolicContinuousCallback([cos(100 * theta) ~ 0], - ModelingToolkit.ImperativeAffect((; qA, hA, hB, cnt), (; qB)) do x, o, c, i - @set! x.hA = x.qA - @set! x.hB = o.qB - @set! x.qA = 1 - @set! x.cnt += decoder(x.hA, x.hB, x.qA, o.qB) - x - end, - affect_neg = ModelingToolkit.ImperativeAffect( - (; qA, hA, hB, cnt), (; qB)) do x, o, c, i - @set! x.hA = x.qA - @set! x.hB = o.qB - @set! x.qA = 0 - @set! x.cnt += decoder(x.hA, x.hB, x.qA, o.qB) - x - end; rootfind = SciMLBase.RightRootFind) - qBevt = ModelingToolkit.SymbolicContinuousCallback([cos(100 * theta - π / 2) ~ 0], - ModelingToolkit.ImperativeAffect((; qB, hA, hB, cnt), (; qA)) do x, o, c, i - @set! x.hA = o.qA - @set! x.hB = x.qB - @set! x.qB = 1 - @set! x.cnt += decoder(x.hA, x.hB, o.qA, x.qB) - x - end, - affect_neg = ModelingToolkit.ImperativeAffect( - (; qB, hA, hB, cnt), (; qA)) do x, o, c, i - @set! x.hA = o.qA - @set! x.hB = x.qB - @set! x.qB = 0 - @set! x.cnt += decoder(x.hA, x.hB, o.qA, x.qB) - x - end; rootfind = SciMLBase.RightRootFind) - @named sys = ODESystem( - eqs, t, [theta, omega], params; continuous_events = [qAevt, qBevt]) - ss = structural_simplify(sys) - prob = ODEProblem(ss, [theta => 1e-5], (0.0, pi)) - sol = solve(prob, Tsit5(); dtmax = 0.01) - @test getp(sol, cnt)(sol) == 198 # we get 2 pulses per phase cycle (cos 0 crossing) and we go to 100 cycles; we miss a few due to the initial state -end - -@testset "Initialization" begin - @variables x(t) - seen = false - f = ModelingToolkit.FunctionalAffect( - f = (i, u, p, c) -> seen = true, sts = [], pars = [], discretes = []) - cb1 = ModelingToolkit.SymbolicContinuousCallback( - [x ~ 0], nothing, initialize = [x ~ 1.5], finalize = f) - @mtkbuild sys = ODESystem(D(x) ~ -1, t, [x], []; continuous_events = [cb1]) - prob = ODEProblem(sys, [x => 1.0], (0.0, 2), []) - sol = solve(prob, Tsit5(); dtmax = 0.01) - @test sol[x][1] ≈ 1.0 - @test sol[x][2] ≈ 1.5 # the initialize affect has been applied - @test seen == true - - @variables x(t) - seen = false - f = ModelingToolkit.FunctionalAffect( - f = (i, u, p, c) -> seen = true, sts = [], pars = [], discretes = []) - 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 = []) - cb2 = ModelingToolkit.SymbolicContinuousCallback( - [x ~ 0.1], nothing, initialize = a, finalize = b) - @mtkbuild sys = ODESystem(D(x) ~ -1, t, [x], []; continuous_events = [cb1, cb2]) - prob = ODEProblem(sys, [x => 1.0], (0.0, 2), []) - sol = solve(prob, Tsit5()) - @test sol[x][1] ≈ 1.0 - @test sol[x][2] ≈ 1.5 # the initialize affect has been applied - @test seen == true - @test inited == true - @test finaled == true - - #periodic - inited = false - finaled = false - cb3 = ModelingToolkit.SymbolicDiscreteCallback( - 1.0, [x ~ 2], initialize = a, finalize = b) - @mtkbuild sys = ODESystem(D(x) ~ -1, t, [x], []; discrete_events = [cb3]) - prob = ODEProblem(sys, [x => 1.0], (0.0, 2), []) - sol = solve(prob, Tsit5()) - @test inited == true - @test finaled == true - @test isapprox(sol[x][3], 0.0, atol = 1e-9) - @test sol[x][4] ≈ 2.0 - @test sol[x][5] ≈ 1.0 - - seen = false - inited = false - finaled = false - cb3 = ModelingToolkit.SymbolicDiscreteCallback(1.0, f, initialize = a, finalize = b) - @mtkbuild sys = ODESystem(D(x) ~ -1, t, [x], []; discrete_events = [cb3]) - prob = ODEProblem(sys, [x => 1.0], (0.0, 2), []) - sol = solve(prob, Tsit5()) - @test seen == true - @test inited == true - - #preset - seen = false - inited = false - finaled = false - cb3 = ModelingToolkit.SymbolicDiscreteCallback([1.0], f, initialize = a, finalize = b) - @mtkbuild sys = ODESystem(D(x) ~ -1, t, [x], []; discrete_events = [cb3]) - prob = ODEProblem(sys, [x => 1.0], (0.0, 2), []) - sol = solve(prob, Tsit5()) - @test seen == true - @test inited == true - @test finaled == true - - #equational - seen = false - inited = false - finaled = false - cb3 = ModelingToolkit.SymbolicDiscreteCallback( - t == 1.0, f, initialize = a, finalize = b) - @mtkbuild sys = ODESystem(D(x) ~ -1, t, [x], []; discrete_events = [cb3]) - prob = ODEProblem(sys, [x => 1.0], (0.0, 2), []) - sol = solve(prob, Tsit5(); tstops = 1.0) - @test seen == true - @test inited == true - @test finaled == true -end +#@testset "SymbolicContinuousCallback constructors" begin +# e = SymbolicContinuousCallback(eqs[]) +# @test e isa SymbolicContinuousCallback +# @test isequal(equations(e), eqs) +# @test e.affect == nothing +# @test e.affect_neg == nothing +# @test e.rootfind == SciMLBase.LeftRootFind +# +# e = SymbolicContinuousCallback(eqs) +# @test e isa SymbolicContinuousCallback +# @test isequal(equations(e), eqs) +# @test e.affect == nothing +# @test e.affect_neg == nothing +# @test e.rootfind == SciMLBase.LeftRootFind +# +# e = SymbolicContinuousCallback(eqs, nothing) +# @test e isa SymbolicContinuousCallback +# @test isequal(equations(e), eqs) +# @test e.affect == nothing +# @test e.affect_neg == nothing +# @test e.rootfind == SciMLBase.LeftRootFind +# +# e = SymbolicContinuousCallback(eqs[], nothing) +# @test e isa SymbolicContinuousCallback +# @test isequal(equations(e), eqs) +# @test e.affect == nothing +# @test e.affect_neg == nothing +# @test e.rootfind == SciMLBase.LeftRootFind +# +# e = SymbolicContinuousCallback(eqs => nothing) +# @test e isa SymbolicContinuousCallback +# @test isequal(equations(e), eqs) +# @test e.affect == nothing +# @test e.affect_neg == nothing +# @test e.rootfind == SciMLBase.LeftRootFind +# +# e = SymbolicContinuousCallback(eqs[] => nothing) +# @test e isa SymbolicContinuousCallback +# @test isequal(equations(e), eqs) +# @test e.affect == nothing +# @test e.affect_neg == nothing +# @test e.rootfind == SciMLBase.LeftRootFind +# +# ## With affect +# e = SymbolicContinuousCallback(eqs[], affect) +# @test e isa SymbolicContinuousCallback +# @test isequal(equations(e), eqs) +# @test e.rootfind == SciMLBase.LeftRootFind +# +# # with only positive edge affect +# e = SymbolicContinuousCallback(eqs[], affect, affect_neg = nothing) +# @test e isa SymbolicContinuousCallback +# @test isequal(equations(e), eqs) +# @test isnothing(e.affect_neg) +# @test e.rootfind == SciMLBase.LeftRootFind +# +# # with explicit edge affects +# e = SymbolicContinuousCallback(eqs[], affect, affect_neg = affect_neg) +# @test e isa SymbolicContinuousCallback +# @test isequal(equations(e), eqs) +# @test e.rootfind == SciMLBase.LeftRootFind +# +# # with different root finding ops +# e = SymbolicContinuousCallback( +# eqs[], affect, affect_neg = affect_neg, rootfind = SciMLBase.LeftRootFind) +# @test e isa SymbolicContinuousCallback +# @test isequal(equations(e), eqs) +# @test e.rootfind == SciMLBase.LeftRootFind +# +# # test plural constructor +# e = SymbolicContinuousCallbacks(eqs[]) +# @test e isa Vector{SymbolicContinuousCallback} +# @test isequal(equations(e[]), eqs) +# @test e[].affect == nothing +# +# e = SymbolicContinuousCallbacks(eqs) +# @test e isa Vector{SymbolicContinuousCallback} +# @test isequal(equations(e[]), eqs) +# @test e[].affect == nothing +# +# e = SymbolicContinuousCallbacks(eqs[] => affect) +# @test e isa Vector{SymbolicContinuousCallback} +# @test isequal(equations(e[]), eqs) +# @test e[].affect isa AffectSystem +# +# e = SymbolicContinuousCallbacks(eqs => affect) +# @test e isa Vector{SymbolicContinuousCallback} +# @test isequal(equations(e[]), eqs) +# @test e[].affect isa AffectSystem +# +# e = SymbolicContinuousCallbacks([eqs[] => affect]) +# @test e isa Vector{SymbolicContinuousCallback} +# @test isequal(equations(e[]), eqs) +# @test e[].affect isa AffectSystem +# +# e = SymbolicContinuousCallbacks([eqs => affect]) +# @test e isa Vector{SymbolicContinuousCallback} +# @test isequal(equations(e[]), eqs) +# @test e[].affect isa AffectSystem +#end +# +#@testset "ImperativeAffect constructors" begin +# fmfa(o, x, i, c) = nothing +# m = ModelingToolkit.ImperativeAffect(fmfa) +# @test m isa ModelingToolkit.ImperativeAffect +# @test m.f == fmfa +# @test m.obs == [] +# @test m.obs_syms == [] +# @test m.modified == [] +# @test m.mod_syms == [] +# @test m.ctx === nothing +# +# m = ModelingToolkit.ImperativeAffect(fmfa, (;)) +# @test m isa ModelingToolkit.ImperativeAffect +# @test m.f == fmfa +# @test m.obs == [] +# @test m.obs_syms == [] +# @test m.modified == [] +# @test m.mod_syms == [] +# @test m.ctx === nothing +# +# m = ModelingToolkit.ImperativeAffect(fmfa, (; x)) +# @test m isa ModelingToolkit.ImperativeAffect +# @test m.f == fmfa +# @test isequal(m.obs, []) +# @test m.obs_syms == [] +# @test isequal(m.modified, [x]) +# @test m.mod_syms == [:x] +# @test m.ctx === nothing +# +# m = ModelingToolkit.ImperativeAffect(fmfa, (; y = x)) +# @test m isa ModelingToolkit.ImperativeAffect +# @test m.f == fmfa +# @test isequal(m.obs, []) +# @test m.obs_syms == [] +# @test isequal(m.modified, [x]) +# @test m.mod_syms == [:y] +# @test m.ctx === nothing +# +# m = ModelingToolkit.ImperativeAffect(fmfa; observed = (; y = x)) +# @test m isa ModelingToolkit.ImperativeAffect +# @test m.f == fmfa +# @test isequal(m.obs, [x]) +# @test m.obs_syms == [:y] +# @test m.modified == [] +# @test m.mod_syms == [] +# @test m.ctx === nothing +# +# m = ModelingToolkit.ImperativeAffect(fmfa; modified = (; x)) +# @test m isa ModelingToolkit.ImperativeAffect +# @test m.f == fmfa +# @test isequal(m.obs, []) +# @test m.obs_syms == [] +# @test isequal(m.modified, [x]) +# @test m.mod_syms == [:x] +# @test m.ctx === nothing +# +# m = ModelingToolkit.ImperativeAffect(fmfa; modified = (; y = x)) +# @test m isa ModelingToolkit.ImperativeAffect +# @test m.f == fmfa +# @test isequal(m.obs, []) +# @test m.obs_syms == [] +# @test isequal(m.modified, [x]) +# @test m.mod_syms == [:y] +# @test m.ctx === nothing +# +# m = ModelingToolkit.ImperativeAffect(fmfa, (; x), (; x)) +# @test m isa ModelingToolkit.ImperativeAffect +# @test m.f == fmfa +# @test isequal(m.obs, [x]) +# @test m.obs_syms == [:x] +# @test isequal(m.modified, [x]) +# @test m.mod_syms == [:x] +# @test m.ctx === nothing +# +# m = ModelingToolkit.ImperativeAffect(fmfa, (; y = x), (; y = x)) +# @test m isa ModelingToolkit.ImperativeAffect +# @test m.f == fmfa +# @test isequal(m.obs, [x]) +# @test m.obs_syms == [:y] +# @test isequal(m.modified, [x]) +# @test m.mod_syms == [:y] +# @test m.ctx === nothing +# +# m = ModelingToolkit.ImperativeAffect( +# fmfa; modified = (; y = x), observed = (; y = x)) +# @test m isa ModelingToolkit.ImperativeAffect +# @test m.f == fmfa +# @test isequal(m.obs, [x]) +# @test m.obs_syms == [:y] +# @test isequal(m.modified, [x]) +# @test m.mod_syms == [:y] +# @test m.ctx === nothing +# +# m = ModelingToolkit.ImperativeAffect( +# fmfa; modified = (; y = x), observed = (; y = x), ctx = 3) +# @test m isa ModelingToolkit.ImperativeAffect +# @test m.f == fmfa +# @test isequal(m.obs, [x]) +# @test m.obs_syms == [:y] +# @test isequal(m.modified, [x]) +# @test m.mod_syms == [:y] +# @test m.ctx === 3 +# +# m = ModelingToolkit.ImperativeAffect(fmfa, (; x), (; x), 3) +# @test m isa ModelingToolkit.ImperativeAffect +# @test m.f == fmfa +# @test isequal(m.obs, [x]) +# @test m.obs_syms == [:x] +# @test isequal(m.modified, [x]) +# @test m.mod_syms == [:x] +# @test m.ctx === 3 +#end +# +#@testset "Condition Compilation" begin +# @named sys = ODESystem(eqs, t, continuous_events = [x ~ 1]) +# @test getfield(sys, :continuous_events)[] == +# SymbolicContinuousCallback(Equation[x ~ 1], nothing) +# @test isequal(equations(getfield(sys, :continuous_events))[], x ~ 1) +# fsys = flatten(sys) +# @test isequal(equations(getfield(fsys, :continuous_events))[], x ~ 1) +# +# @named sys2 = ODESystem([D(x) ~ 1], t, continuous_events = [x ~ 2], systems = [sys]) +# @test getfield(sys2, :continuous_events)[] == +# SymbolicContinuousCallback(Equation[x ~ 2], nothing) +# @test all(ModelingToolkit.continuous_events(sys2) .== [ +# SymbolicContinuousCallback(Equation[x ~ 2], nothing), +# SymbolicContinuousCallback(Equation[sys.x ~ 1], nothing) +# ]) +# +# @test isequal(equations(getfield(sys2, :continuous_events))[1], x ~ 2) +# @test length(ModelingToolkit.continuous_events(sys2)) == 2 +# @test isequal(equations(ModelingToolkit.continuous_events(sys2)[1])[], x ~ 2) +# @test isequal(equations(ModelingToolkit.continuous_events(sys2)[2])[], sys.x ~ 1) +# +# sys = complete(sys) +# sys_nosplit = complete(sys; split = false) +# sys2 = complete(sys2) +# +# # Test proper rootfinding +# prob = ODEProblem(sys, Pair[], (0.0, 2.0)) +# p0 = 0 +# t0 = 0 +# @test get_callback(prob) isa ModelingToolkit.DiffEqCallbacks.ContinuousCallback +# cb = ModelingToolkit.generate_continuous_callbacks(sys) +# cond = cb.condition +# out = [0.0] +# cond.f_iip(out, [0], p0, t0) +# @test out[] ≈ -1 # signature is u,p,t +# cond.f_iip(out, [1], p0, t0) +# @test out[] ≈ 0 # signature is u,p,t +# cond.f_iip(out, [2], p0, t0) +# @test out[] ≈ 1 # signature is u,p,t +# +# prob = ODEProblem(sys, Pair[], (0.0, 2.0)) +# prob_nosplit = ODEProblem(sys_nosplit, Pair[], (0.0, 2.0)) +# sol = solve(prob, Tsit5()) +# sol_nosplit = solve(prob_nosplit, Tsit5()) +# @test minimum(t -> abs(t - 1), sol.t) < 1e-10 # test that the solver stepped at the root +# @test minimum(t -> abs(t - 1), sol_nosplit.t) < 1e-10 # test that the solver stepped at the root +# +# # Test user-provided callback is respected +# test_callback = DiscreteCallback(x -> x, x -> x) +# prob = ODEProblem(sys, Pair[], (0.0, 2.0), callback = test_callback) +# prob_nosplit = ODEProblem(sys_nosplit, Pair[], (0.0, 2.0), callback = test_callback) +# cbs = get_callback(prob) +# cbs_nosplit = get_callback(prob_nosplit) +# @test cbs isa CallbackSet +# @test cbs.discrete_callbacks[1] == test_callback +# @test cbs_nosplit isa CallbackSet +# @test cbs_nosplit.discrete_callbacks[1] == test_callback +# +# prob = ODEProblem(sys2, Pair[], (0.0, 3.0)) +# cb = get_callback(prob) +# @test cb isa ModelingToolkit.DiffEqCallbacks.VectorContinuousCallback +# +# cond = cb.condition +# out = [0.0, 0.0] +# # the root to find is 2 +# cond.f_iip(out, [0, 0], p0, t0) +# @test out[1] ≈ -2 # signature is u,p,t +# cond.f_iip(out, [1, 0], p0, t0) +# @test out[1] ≈ -1 # signature is u,p,t +# cond.f_iip(out, [2, 0], p0, t0) # this should return 0 +# @test out[1] ≈ 0 # signature is u,p,t +# +# # the root to find is 1 +# out = [0.0, 0.0] +# cond.f_iip(out, [0, 0], p0, t0) +# @test out[2] ≈ -1 # signature is u,p,t +# cond.f_iip(out, [0, 1], p0, t0) # this should return 0 +# @test out[2] ≈ 0 # signature is u,p,t +# cond.f_iip(out, [0, 2], p0, t0) +# @test out[2] ≈ 1 # signature is u,p,t +# +# sol = solve(prob, Tsit5()) +# @test minimum(t -> abs(t - 1), sol.t) < 1e-10 # test that the solver stepped at the first root +# @test minimum(t -> abs(t - 2), sol.t) < 1e-10 # test that the solver stepped at the second root +# +# @named sys = ODESystem(eqs, t, continuous_events = [x ~ 1, x ~ 2]) # two root eqs using the same unknown +# sys = complete(sys) +# prob = ODEProblem(sys, Pair[], (0.0, 3.0)) +# @test get_callback(prob) isa ModelingToolkit.DiffEqCallbacks.VectorContinuousCallback +# sol = solve(prob, Tsit5()) +# @test minimum(t -> abs(t - 1), sol.t) < 1e-10 # test that the solver stepped at the first root +# @test minimum(t -> abs(t - 2), sol.t) < 1e-10 # test that the solver stepped at the second root +#end +# +#@testset "Bouncing Ball" begin +# ###### 1D Bounce +# @variables x(t)=1 v(t)=0 +# +# root_eqs = [x ~ 0] +# affect = [v ~ -Pre(v)] +# +# @named ball = ODESystem( +# [D(x) ~ v +# D(v) ~ -9.8], t, continuous_events = root_eqs => affect) +# +# @test only(continuous_events(ball)) == +# SymbolicContinuousCallback(Equation[x ~ 0], Equation[v ~ -Pre(v)]) +# ball = structural_simplify(ball) +# +# @test length(ModelingToolkit.continuous_events(ball)) == 1 +# +# tspan = (0.0, 5.0) +# prob = ODEProblem(ball, Pair[], tspan) +# sol = solve(prob, Tsit5()) +# @test 0 <= minimum(sol[x]) <= 1e-10 # the ball never went through the floor but got very close +# +# ###### 2D bouncing ball +# @variables x(t)=1 y(t)=0 vx(t)=0 vy(t)=1 +# +# events = [[x ~ 0] => [vx ~ -Pre(vx)] +# [y ~ -1.5, y ~ 1.5] => [vy ~ -Pre(vy)]] +# +# @named ball = ODESystem( +# [D(x) ~ vx +# D(y) ~ vy +# D(vx) ~ -9.8 +# D(vy) ~ -0.01vy], t; continuous_events = events) +# +# _ball = ball +# ball = structural_simplify(_ball) +# ball_nosplit = structural_simplify(_ball; split = false) +# +# tspan = (0.0, 5.0) +# prob = ODEProblem(ball, Pair[], tspan) +# prob_nosplit = ODEProblem(ball_nosplit, Pair[], tspan) +# +# cb = get_callback(prob) +# @test cb isa ModelingToolkit.DiffEqCallbacks.VectorContinuousCallback +# @test getfield(ball, :continuous_events)[1] == +# SymbolicContinuousCallback(Equation[x ~ 0], Equation[vx ~ -Pre(vx)]) +# @test getfield(ball, :continuous_events)[2] == +# SymbolicContinuousCallback(Equation[y ~ -1.5, y ~ 1.5], Equation[vy ~ -Pre(vy)]) +# cond = cb.condition +# out = [0.0, 0.0, 0.0] +# p0 = 0. +# t0 = 0. +# cond.f_iip(out, [0, 0, 0, 0], p0, t0) +# @test out ≈ [0, 1.5, -1.5] +# +# sol = solve(prob, Tsit5()) +# sol_nosplit = solve(prob_nosplit, Tsit5()) +# @test 0 <= minimum(sol[x]) <= 1e-10 # the ball never went through the floor but got very close +# @test minimum(sol[y]) ≈ -1.5 # check wall conditions +# @test maximum(sol[y]) ≈ 1.5 # check wall conditions +# @test 0 <= minimum(sol_nosplit[x]) <= 1e-10 # the ball never went through the floor but got very close +# @test minimum(sol_nosplit[y]) ≈ -1.5 # check wall conditions +# @test maximum(sol_nosplit[y]) ≈ 1.5 # check wall conditions +# +# ## Test multi-variable affect +# # in this test, there are two variables affected by a single event. +# events = [[x ~ 0] => [vx ~ -Pre(vx), vy ~ -Pre(vy)]] +# +# @named ball = ODESystem([D(x) ~ vx +# D(y) ~ vy +# D(vx) ~ -1 +# D(vy) ~ 0], t; continuous_events = events) +# +# ball_nosplit = structural_simplify(ball) +# ball = structural_simplify(ball) +# +# tspan = (0.0, 5.0) +# prob = ODEProblem(ball, Pair[], tspan) +# prob_nosplit = ODEProblem(ball_nosplit, Pair[], tspan) +# sol = solve(prob, Tsit5()) +# sol_nosplit = solve(prob_nosplit, Tsit5()) +# @test 0 <= minimum(sol[x]) <= 1e-10 # the ball never went through the floor but got very close +# @test -minimum(sol[y]) ≈ maximum(sol[y]) ≈ sqrt(2) # the ball will never go further than √2 in either direction (gravity was changed to 1 to get this particular number) +# @test 0 <= minimum(sol_nosplit[x]) <= 1e-10 # the ball never went through the floor but got very close +# @test -minimum(sol_nosplit[y]) ≈ maximum(sol_nosplit[y]) ≈ sqrt(2) # the ball will never go further than √2 in either direction (gravity was changed to 1 to get this particular number) +#end +# +## issue https://github.com/SciML/ModelingToolkit.jl/issues/1386 +## tests that it works for ODAESystem +#@testset "ODAESystem" begin +# @variables vs(t) v(t) vmeasured(t) +# eq = [vs ~ sin(2pi * t) +# D(v) ~ vs - v +# D(vmeasured) ~ 0.0] +# ev = [sin(20pi * t) ~ 0.0] => [vmeasured ~ Pre(v)] +# @named sys = ODESystem(eq, t, continuous_events = ev) +# sys = structural_simplify(sys) +# prob = ODEProblem(sys, zeros(2), (0.0, 5.1)) +# sol = solve(prob, Tsit5()) +# @test all(minimum((0:0.1:5) .- sol.t', dims = 2) .< 0.0001) # test that the solver stepped every 0.1s as dictated by event +# @test sol([0.25])[vmeasured][] == sol([0.23])[vmeasured][] # test the hold property +#end +# +### https://github.com/SciML/ModelingToolkit.jl/issues/1528 +#@testset "Handle Empty Events" begin +# Dₜ = D +# +# @parameters u(t) [input = true] # Indicate that this is a controlled input +# @parameters y(t) [output = true] # Indicate that this is a measured output +# +# function Mass(; name, m = 1.0, p = 0, v = 0) +# ps = @parameters m = m +# sts = @variables pos(t)=p vel(t)=v +# eqs = Dₜ(pos) ~ vel +# ODESystem(eqs, t, [pos, vel], ps; name) +# end +# function Spring(; name, k = 1e4) +# ps = @parameters k = k +# @variables x(t) = 0 # Spring deflection +# ODESystem(Equation[], t, [x], ps; name) +# end +# function Damper(; name, c = 10) +# ps = @parameters c = c +# @variables vel(t) = 0 +# ODESystem(Equation[], t, [vel], ps; name) +# end +# function SpringDamper(; name, k = false, c = false) +# spring = Spring(; name = :spring, k) +# damper = Damper(; name = :damper, c) +# compose(ODESystem(Equation[], t; name), +# spring, damper) +# end +# connect_sd(sd, m1, m2) = [ +# sd.spring.x ~ m1.pos - m2.pos, sd.damper.vel ~ m1.vel - m2.vel] +# sd_force(sd) = -sd.spring.k * sd.spring.x - sd.damper.c * sd.damper.vel +# @named mass1 = Mass(; m = 1) +# @named mass2 = Mass(; m = 1) +# @named sd = SpringDamper(; k = 1000, c = 10) +# function Model(u, d = 0) +# eqs = [connect_sd(sd, mass1, mass2) +# Dₜ(mass1.vel) ~ (sd_force(sd) + u) / mass1.m +# Dₜ(mass2.vel) ~ (-sd_force(sd) + d) / mass2.m] +# @named _model = ODESystem(eqs, t; observed = [y ~ mass2.pos]) +# @named model = compose(_model, mass1, mass2, sd) +# end +# model = Model(sin(30t)) +# sys = structural_simplify(model) +# @test isempty(ModelingToolkit.continuous_events(sys)) +#end +# +#@testset "ODESystem Discrete Callbacks" begin +# function testsol(osys, u0, p, tspan; tstops = Float64[], paramtotest = nothing, +# kwargs...) +# oprob = ODEProblem(complete(osys), u0, tspan, p; kwargs...) +# sol = solve(oprob, Tsit5(); tstops = tstops, abstol = 1e-10, reltol = 1e-10) +# @test isapprox(sol(1.0000000001)[1] - sol(0.999999999)[1], 1.0; rtol = 1e-6) +# paramtotest === nothing || (@test sol.ps[paramtotest] == 1.0) +# @test isapprox(sol(4.0)[1], 2 * exp(-2.0)) +# sol +# end +# +# @parameters k t1 t2 +# @variables A(t) B(t) +# +# cond1 = (t == t1) +# affect1 = [A ~ Pre(A) + 1] +# cb1 = cond1 => affect1 +# cond2 = (t == t2) +# affect2 = [k ~ 1.0] +# cb2 = cond2 => affect2 +# +# ∂ₜ = D +# eqs = [∂ₜ(A) ~ -k * A] +# @named osys = ODESystem(eqs, t, [A], [k, t1, t2], discrete_events = [cb1, cb2]) +# u0 = [A => 1.0] +# p = [k => 0.0, t1 => 1.0, t2 => 2.0] +# tspan = (0.0, 4.0) +# testsol(osys, u0, p, tspan; tstops = [1.0, 2.0], paramtotest = k) +# +# cond1a = (t == t1) +# affect1a = [A ~ Pre(A) + 1, B ~ A] +# cb1a = cond1a => affect1a +# @named osys1 = ODESystem(eqs, t, [A, B], [k, t1, t2], discrete_events = [cb1a, cb2]) +# u0′ = [A => 1.0, B => 0.0] +# sol = testsol( +# osys1, u0′, p, tspan; tstops = [1.0, 2.0], check_length = false, paramtotest = k) +# @test sol(1.0000001, idxs = B) == 2.0 +# +# # same as above - but with set-time event syntax +# cb1‵ = [1.0] => affect1 # needs to be a Vector for the event to happen only once +# cb2‵ = [2.0] => affect2 +# @named osys‵ = ODESystem(eqs, t, [A], [k], discrete_events = [cb1‵, cb2‵]) +# testsol(osys‵, u0, p, tspan; paramtotest = k) +# +# # mixing discrete affects +# @named osys3 = ODESystem(eqs, t, [A], [k, t1, t2], discrete_events = [cb1, cb2‵]) +# testsol(osys3, 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 +# end +# cb2‵‵ = [2.0] => (affect!, [], [k], [k], nothing) +# @named osys4 = ODESystem(eqs, t, [A], [k, t1], discrete_events = [cb1, cb2‵‵]) +# oprob4 = ODEProblem(complete(osys4), u0, tspan, p) +# testsol(osys4, u0, p, tspan; tstops = [1.0], paramtotest = k) +# +# # mixing with symbolic condition in the func affect +# cb2‵‵‵ = (t == t2) => (affect!, [], [k], [k], nothing) +# @named osys5 = ODESystem(eqs, t, [A], [k, t1, t2], discrete_events = [cb1, cb2‵‵‵]) +# testsol(osys5, u0, p, tspan; tstops = [1.0, 2.0]) +# @named osys6 = ODESystem(eqs, t, [A], [k, t1, t2], discrete_events = [cb2‵‵‵, cb1]) +# testsol(osys6, u0, p, tspan; tstops = [1.0, 2.0], paramtotest = k) +# +# # mix a continuous event too +# cond3 = A ~ 0.1 +# affect3 = [k ~ 0.0] +# cb3 = cond3 => affect3 +# @named osys7 = ODESystem(eqs, t, [A], [k, t1, t2], discrete_events = [cb1, cb2‵‵‵], +# continuous_events = [cb3]) +# sol = testsol(osys7, u0, p, (0.0, 10.0); tstops = [1.0, 2.0]) +# @test isapprox(sol(10.0)[1], 0.1; atol = 1e-10, rtol = 1e-10) +#end +# +#@testset "SDESystem Discrete Callbacks" begin +# function testsol(ssys, u0, p, tspan; tstops = Float64[], paramtotest = nothing, +# kwargs...) +# sprob = SDEProblem(complete(ssys), u0, tspan, p; kwargs...) +# sol = solve(sprob, RI5(); tstops = tstops, abstol = 1e-10, reltol = 1e-10) +# @test isapprox(sol(1.0000000001)[1] - sol(0.999999999)[1], 1.0; rtol = 1e-4) +# paramtotest === nothing || (@test sol.ps[paramtotest] == 1.0) +# @test isapprox(sol(4.0)[1], 2 * exp(-2.0), atol = 1e-4) +# sol +# end +# +# @parameters k t1 t2 +# @variables A(t) B(t) +# +# cond1 = (t == t1) +# affect1 = [A ~ Pre(A) + 1] +# cb1 = cond1 => affect1 +# cond2 = (t == t2) +# affect2 = [k ~ 1.0] +# cb2 = cond2 => affect2 +# +# ∂ₜ = D +# eqs = [∂ₜ(A) ~ -k * A] +# @named ssys = SDESystem(eqs, [0.0], t, [A], [k, t1, t2], +# discrete_events = [cb1, cb2]) +# u0 = [A => 1.0] +# p = [k => 0.0, t1 => 1.0, t2 => 2.0] +# tspan = (0.0, 4.0) +# testsol(ssys, u0, p, tspan; tstops = [1.0, 2.0], paramtotest = k) +# +# cond1a = (t == t1) +# affect1a = [A ~ Pre(A) + 1, B ~ A] +# cb1a = cond1a => affect1a +# @named ssys1 = SDESystem(eqs, [0.0], t, [A, B], [k, t1, t2], +# discrete_events = [cb1a, cb2]) +# u0′ = [A => 1.0, B => 0.0] +# sol = testsol( +# ssys1, u0′, p, tspan; tstops = [1.0, 2.0], check_length = false, paramtotest = k) +# @test sol(1.0000001, idxs = 2) == 2.0 +# +# # same as above - but with set-time event syntax +# cb1‵ = [1.0] => affect1 # needs to be a Vector for the event to happen only once +# cb2‵ = [2.0] => affect2 +# @named ssys‵ = SDESystem(eqs, [0.0], t, [A], [k], discrete_events = [cb1‵, cb2‵]) +# testsol(ssys‵, u0, p, tspan; paramtotest = k) +# +# # mixing discrete affects +# @named ssys3 = SDESystem(eqs, [0.0], t, [A], [k, t1, t2], +# discrete_events = [cb1, cb2‵]) +# testsol(ssys3, u0, p, tspan; tstops = [1.0], paramtotest = k) +# +# # mixing with a func affect +# function affect!(integrator, u, p, ctx) +# setp(integrator, p.k)(integrator, 1.0) +# nothing +# end +# cb2‵‵ = [2.0] => (affect!, [], [k], [k], nothing) +# @named ssys4 = SDESystem(eqs, [0.0], t, [A], [k, t1], +# discrete_events = [cb1, cb2‵‵]) +# testsol(ssys4, u0, p, tspan; tstops = [1.0], paramtotest = k) +# +# # mixing with symbolic condition in the func affect +# cb2‵‵‵ = (t == t2) => (affect!, [], [k], [k], nothing) +# @named ssys5 = SDESystem(eqs, [0.0], t, [A], [k, t1, t2], +# discrete_events = [cb1, cb2‵‵‵]) +# testsol(ssys5, u0, p, tspan; tstops = [1.0, 2.0], paramtotest = k) +# @named ssys6 = SDESystem(eqs, [0.0], t, [A], [k, t1, t2], +# discrete_events = [cb2‵‵‵, cb1]) +# testsol(ssys6, u0, p, tspan; tstops = [1.0, 2.0], paramtotest = k) +# +# # mix a continuous event too +# cond3 = A ~ 0.1 +# affect3 = [k ~ 0.0] +# cb3 = cond3 => affect3 +# @named ssys7 = SDESystem(eqs, [0.0], t, [A], [k, t1, t2], +# discrete_events = [cb1, cb2‵‵‵], +# continuous_events = [cb3]) +# sol = testsol(ssys7, u0, p, (0.0, 10.0); tstops = [1.0, 2.0]) +# @test isapprox(sol(10.0)[1], 0.1; atol = 1e-10, rtol = 1e-10) +#end +# +#@testset "JumpSystem Discrete Callbacks" begin +# function testsol(jsys, u0, p, tspan; tstops = Float64[], paramtotest = nothing, +# N = 40000, kwargs...) +# jsys = complete(jsys) +# dprob = DiscreteProblem(jsys, u0, tspan, p) +# jprob = JumpProblem(jsys, dprob, Direct(); kwargs...) +# sol = solve(jprob, SSAStepper(); tstops = tstops) +# @test (sol(1.000000000001)[1] - sol(0.99999999999)[1]) == 1 +# paramtotest === nothing || (@test sol.ps[paramtotest] == 1.0) +# @test sol(40.0)[1] == 0 +# sol +# end +# +# @parameters k t1 t2 +# @variables A(t) B(t) +# +# cond1 = (t == t1) +# affect1 = [A ~ Pre(A) + 1] +# cb1 = cond1 => affect1 +# cond2 = (t == t2) +# affect2 = [k ~ 1.0] +# cb2 = cond2 => affect2 +# +# eqs = [MassActionJump(k, [A => 1], [A => -1])] +# @named jsys = JumpSystem(eqs, t, [A], [k, t1, t2], discrete_events = [cb1, cb2]) +# u0 = [A => 1] +# p = [k => 0.0, t1 => 1.0, t2 => 2.0] +# tspan = (0.0, 40.0) +# testsol(jsys, u0, p, tspan; tstops = [1.0, 2.0], rng, paramtotest = k) +# +# cond1a = (t == t1) +# affect1a = [A ~ Pre(A) + 1, B ~ A] +# cb1a = cond1a => affect1a +# @named jsys1 = JumpSystem(eqs, t, [A, B], [k, t1, t2], discrete_events = [cb1a, cb2]) +# u0′ = [A => 1, B => 0] +# sol = testsol(jsys1, u0′, p, tspan; tstops = [1.0, 2.0], +# check_length = false, rng, paramtotest = k) +# @test sol(1.000000001, idxs = B) == 2 +# +# # same as above - but with set-time event syntax +# cb1‵ = [1.0] => affect1 # needs to be a Vector for the event to happen only once +# cb2‵ = [2.0] => affect2 +# @named jsys‵ = JumpSystem(eqs, t, [A], [k], discrete_events = [cb1‵, cb2‵]) +# testsol(jsys‵, u0, [p[1]], tspan; rng, paramtotest = k) +# +# # mixing discrete affects +# @named jsys3 = JumpSystem(eqs, t, [A], [k, t1, t2], discrete_events = [cb1, cb2‵]) +# 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 +# end +# cb2‵‵ = [2.0] => (affect!, [], [k], [k], nothing) +# @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) +# @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]) +# testsol(jsys6, u0, p, tspan; tstops = [1.0, 2.0], rng, paramtotest = k) +#end +# +#@testset "Namespacing" begin +# function oscillator_ce(k = 1.0; name) +# sts = @variables x(t)=1.0 v(t)=0.0 F(t) +# ps = @parameters k=k Θ=0.5 +# eqs = [D(x) ~ v, D(v) ~ -k * x + F] +# ev = [x ~ Θ] => [x ~ 1.0, v ~ 0.0] +# ODESystem(eqs, t, sts, ps, continuous_events = [ev]; name) +# end +# +# @named oscce = oscillator_ce() +# eqs = [oscce.F ~ 0] +# @named eqs_sys = ODESystem(eqs, t) +# @named oneosc_ce = compose(eqs_sys, oscce) +# oneosc_ce_simpl = structural_simplify(oneosc_ce) +# +# prob = ODEProblem(oneosc_ce_simpl, [], (0.0, 2.0), []) +# sol = solve(prob, Tsit5(), saveat = 0.1) +# +# @test typeof(oneosc_ce_simpl) == ODESystem +# @test sol[1, 6] < 1.0 # test whether x(t) decreases over time +# @test sol[1, 18] > 0.5 # test whether event happened +#end +# +#@testset "Additional SymbolicContinuousCallback options" begin +# # 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]) +# cr1 = [] +# cr2 = [] +# evt1 = ModelingToolkit.SymbolicContinuousCallback( +# [c1 ~ 0], (record_crossings, [c1 => :v], [], [], cr1)) +# evt2 = ModelingToolkit.SymbolicContinuousCallback( +# [c2 ~ 0], (record_crossings, [c2 => :v], [], [], cr2)) +# @named trigsys = ODESystem(eqs, t; continuous_events = [evt1, evt2]) +# trigsys_ss = structural_simplify(trigsys) +# prob = ODEProblem(trigsys_ss, [], (0.0, 2π)) +# sol = solve(prob, Tsit5()) +# required_crossings_c1 = [π / 2, 3 * π / 2] +# required_crossings_c2 = [π / 6, π / 2, 5 * π / 6, 7 * π / 6, 3 * π / 2, 11 * π / 6] +# @test maximum(abs.(first.(cr1) .- required_crossings_c1)) < 1e-4 +# @test maximum(abs.(first.(cr2) .- required_crossings_c2)) < 1e-4 +# @test sign.(cos.(required_crossings_c1 .- 1e-6)) == sign.(last.(cr1)) +# @test sign.(cos.(3 * (required_crossings_c2 .- 1e-6))) == sign.(last.(cr2)) +# +# # with neg affect (pos * neg + left root find) +# cr1p = [] +# cr2p = [] +# cr1n = [] +# cr2n = [] +# evt1 = ModelingToolkit.SymbolicContinuousCallback( +# [c1 ~ 0], (record_crossings, [c1 => :v], [], [], cr1p); +# affect_neg = (record_crossings, [c1 => :v], [], [], cr1n)) +# evt2 = ModelingToolkit.SymbolicContinuousCallback( +# [c2 ~ 0], (record_crossings, [c2 => :v], [], [], cr2p); +# affect_neg = (record_crossings, [c2 => :v], [], [], cr2n)) +# @named trigsys = ODESystem(eqs, t; continuous_events = [evt1, evt2]) +# trigsys_ss = structural_simplify(trigsys) +# prob = ODEProblem(trigsys_ss, [], (0.0, 2π)) +# sol = solve(prob, Tsit5(); dtmax = 0.01) +# c1_pc = filter((<=)(0) ∘ sin, required_crossings_c1) +# c1_nc = filter((>=)(0) ∘ sin, required_crossings_c1) +# c2_pc = filter(c -> -sin(3c) > 0, required_crossings_c2) +# c2_nc = filter(c -> -sin(3c) < 0, required_crossings_c2) +# @test maximum(abs.(c1_pc .- first.(cr1p))) < 1e-5 +# @test maximum(abs.(c1_nc .- first.(cr1n))) < 1e-5 +# @test maximum(abs.(c2_pc .- first.(cr2p))) < 1e-5 +# @test maximum(abs.(c2_nc .- first.(cr2n))) < 1e-5 +# @test sign.(cos.(c1_pc .- 1e-6)) == sign.(last.(cr1p)) +# @test sign.(cos.(c1_nc .- 1e-6)) == sign.(last.(cr1n)) +# @test sign.(cos.(3 * (c2_pc .- 1e-6))) == sign.(last.(cr2p)) +# @test sign.(cos.(3 * (c2_nc .- 1e-6))) == sign.(last.(cr2n)) +# +# # with nothing neg affect (pos * neg + left root find) +# cr1p = [] +# cr2p = [] +# evt1 = ModelingToolkit.SymbolicContinuousCallback( +# [c1 ~ 0], (record_crossings, [c1 => :v], [], [], cr1p); affect_neg = nothing) +# evt2 = ModelingToolkit.SymbolicContinuousCallback( +# [c2 ~ 0], (record_crossings, [c2 => :v], [], [], cr2p); affect_neg = nothing) +# @named trigsys = ODESystem(eqs, t; continuous_events = [evt1, evt2]) +# trigsys_ss = structural_simplify(trigsys) +# prob = ODEProblem(trigsys_ss, [], (0.0, 2π)) +# sol = solve(prob, Tsit5(); dtmax = 0.01) +# @test maximum(abs.(c1_pc .- first.(cr1p))) < 1e-5 +# @test maximum(abs.(c2_pc .- first.(cr2p))) < 1e-5 +# @test sign.(cos.(c1_pc .- 1e-6)) == sign.(last.(cr1p)) +# @test sign.(cos.(3 * (c2_pc .- 1e-6))) == sign.(last.(cr2p)) +# +# #mixed +# cr1p = [] +# cr2p = [] +# cr1n = [] +# cr2n = [] +# evt1 = ModelingToolkit.SymbolicContinuousCallback( +# [c1 ~ 0], (record_crossings, [c1 => :v], [], [], cr1p); affect_neg = nothing) +# evt2 = ModelingToolkit.SymbolicContinuousCallback( +# [c2 ~ 0], (record_crossings, [c2 => :v], [], [], cr2p); +# affect_neg = (record_crossings, [c2 => :v], [], [], cr2n)) +# @named trigsys = ODESystem(eqs, t; continuous_events = [evt1, evt2]) +# trigsys_ss = structural_simplify(trigsys) +# prob = ODEProblem(trigsys_ss, [], (0.0, 2π)) +# sol = solve(prob, Tsit5(); dtmax = 0.01) +# c1_pc = filter((<=)(0) ∘ sin, required_crossings_c1) +# c2_pc = filter(c -> -sin(3c) > 0, required_crossings_c2) +# c2_nc = filter(c -> -sin(3c) < 0, required_crossings_c2) +# @test maximum(abs.(c1_pc .- first.(cr1p))) < 1e-5 +# @test maximum(abs.(c2_pc .- first.(cr2p))) < 1e-5 +# @test maximum(abs.(c2_nc .- first.(cr2n))) < 1e-5 +# @test sign.(cos.(c1_pc .- 1e-6)) == sign.(last.(cr1p)) +# @test sign.(cos.(3 * (c2_pc .- 1e-6))) == sign.(last.(cr2p)) +# @test sign.(cos.(3 * (c2_nc .- 1e-6))) == sign.(last.(cr2n)) +# +# # baseline affect w/ right rootfind (pos + neg + right root find) +# @variables c1(t)=1.0 c2(t)=1.0 # c1 = cos(t), c2 = cos(3t) +# cr1 = [] +# cr2 = [] +# evt1 = ModelingToolkit.SymbolicContinuousCallback( +# [c1 ~ 0], (record_crossings, [c1 => :v], [], [], cr1); +# rootfind = SciMLBase.RightRootFind) +# evt2 = ModelingToolkit.SymbolicContinuousCallback( +# [c2 ~ 0], (record_crossings, [c2 => :v], [], [], cr2); +# rootfind = SciMLBase.RightRootFind) +# @named trigsys = ODESystem(eqs, t; continuous_events = [evt1, evt2]) +# trigsys_ss = structural_simplify(trigsys) +# prob = ODEProblem(trigsys_ss, [], (0.0, 2π)) +# sol = solve(prob, Tsit5(); dtmax = 0.01) +# required_crossings_c1 = [π / 2, 3 * π / 2] +# required_crossings_c2 = [π / 6, π / 2, 5 * π / 6, 7 * π / 6, 3 * π / 2, 11 * π / 6] +# @test maximum(abs.(first.(cr1) .- required_crossings_c1)) < 1e-4 +# @test maximum(abs.(first.(cr2) .- required_crossings_c2)) < 1e-4 +# @test sign.(cos.(required_crossings_c1 .+ 1e-6)) == sign.(last.(cr1)) +# @test sign.(cos.(3 * (required_crossings_c2 .+ 1e-6))) == sign.(last.(cr2)) +# +# # baseline affect w/ mixed rootfind (pos + neg + right root find) +# cr1 = [] +# cr2 = [] +# evt1 = ModelingToolkit.SymbolicContinuousCallback( +# [c1 ~ 0], (record_crossings, [c1 => :v], [], [], cr1); +# rootfind = SciMLBase.LeftRootFind) +# evt2 = ModelingToolkit.SymbolicContinuousCallback( +# [c2 ~ 0], (record_crossings, [c2 => :v], [], [], cr2); +# rootfind = SciMLBase.RightRootFind) +# @named trigsys = ODESystem(eqs, t; continuous_events = [evt1, evt2]) +# trigsys_ss = structural_simplify(trigsys) +# prob = ODEProblem(trigsys_ss, [], (0.0, 2π)) +# sol = solve(prob, Tsit5()) +# @test maximum(abs.(first.(cr1) .- required_crossings_c1)) < 1e-4 +# @test maximum(abs.(first.(cr2) .- required_crossings_c2)) < 1e-4 +# @test sign.(cos.(required_crossings_c1 .- 1e-6)) == sign.(last.(cr1)) +# @test sign.(cos.(3 * (required_crossings_c2 .+ 1e-6))) == sign.(last.(cr2)) +# +# #flip order and ensure results are okay +# cr1 = [] +# cr2 = [] +# evt1 = ModelingToolkit.SymbolicContinuousCallback( +# [c1 ~ 0], (record_crossings, [c1 => :v], [], [], cr1); +# rootfind = SciMLBase.LeftRootFind) +# evt2 = ModelingToolkit.SymbolicContinuousCallback( +# [c2 ~ 0], (record_crossings, [c2 => :v], [], [], cr2); +# rootfind = SciMLBase.RightRootFind) +# @named trigsys = ODESystem(eqs, t; continuous_events = [evt2, evt1]) +# trigsys_ss = structural_simplify(trigsys) +# prob = ODEProblem(trigsys_ss, [], (0.0, 2π)) +# sol = solve(prob, Tsit5()) +# @test maximum(abs.(first.(cr1) .- required_crossings_c1)) < 1e-4 +# @test maximum(abs.(first.(cr2) .- required_crossings_c2)) < 1e-4 +# @test sign.(cos.(required_crossings_c1 .- 1e-6)) == sign.(last.(cr1)) +# @test sign.(cos.(3 * (required_crossings_c2 .+ 1e-6))) == sign.(last.(cr2)) +#end +# +#@testset "Discrete event reinitialization (#3142)" begin +# @connector LiquidPort begin +# p(t)::Float64, [description = "Set pressure in bar", +# guess = 1.01325] +# Vdot(t)::Float64, +# [description = "Volume flow rate in L/min", +# guess = 0.0, +# connect = Flow] +# end +# +# @mtkmodel PressureSource begin +# @components begin +# port = LiquidPort() +# end +# @parameters begin +# p_set::Float64 = 1.01325, [description = "Set pressure in bar"] +# end +# @equations begin +# port.p ~ p_set +# end +# end +# +# @mtkmodel BinaryValve begin +# @constants begin +# p_ref::Float64 = 1.0, [description = "Reference pressure drop in bar"] +# ρ_ref::Float64 = 1000.0, [description = "Reference density in kg/m^3"] +# end +# @components begin +# port_in = LiquidPort() +# port_out = LiquidPort() +# end +# @parameters begin +# k_V::Float64 = 1.0, [description = "Valve coefficient in L/min/bar"] +# k_leakage::Float64 = 1e-08, [description = "Leakage coefficient in L/min/bar"] +# ρ::Float64 = 1000.0, [description = "Density in kg/m^3"] +# end +# @variables begin +# S(t)::Float64, [description = "Valve state", guess = 1.0, irreducible = true] +# Δp(t)::Float64, [description = "Pressure difference in bar", guess = 1.0] +# Vdot(t)::Float64, [description = "Volume flow rate in L/min", guess = 1.0] +# end +# @equations begin +# # Port handling +# port_in.Vdot ~ -Vdot +# port_out.Vdot ~ Vdot +# Δp ~ port_in.p - port_out.p +# # System behavior +# D(S) ~ 0.0 +# Vdot ~ S * k_V * sign(Δp) * sqrt(abs(Δp) / p_ref * ρ_ref / ρ) + k_leakage * Δp # softplus alpha function to avoid negative values under the sqrt +# end +# end +# +# # Test System +# @mtkmodel TestSystem begin +# @components begin +# pressure_source_1 = PressureSource(p_set = 2.0) +# binary_valve_1 = BinaryValve(S = 1.0, k_leakage = 0.0) +# binary_valve_2 = BinaryValve(S = 1.0, k_leakage = 0.0) +# pressure_source_2 = PressureSource(p_set = 1.0) +# end +# @equations begin +# connect(pressure_source_1.port, binary_valve_1.port_in) +# connect(binary_valve_1.port_out, binary_valve_2.port_in) +# connect(binary_valve_2.port_out, pressure_source_2.port) +# end +# @discrete_events begin +# [30] => [binary_valve_1.S ~ 0.0, binary_valve_2.Δp ~ 0.0] +# [60] => [ +# binary_valve_1.S ~ 1.0, binary_valve_2.S ~ 0.0, binary_valve_2.Δp ~ 1.0] +# [120] => [binary_valve_1.S ~ 0.0, binary_valve_2.Δp ~ 0.0] +# end +# end +# +# # Test Simulation +# @mtkbuild sys = TestSystem() +# +# # Test Simulation +# prob = ODEProblem(sys, [], (0.0, 150.0)) +# sol = solve(prob) +# @test sol[end] == [0.0, 0.0, 0.0] +#end +# +#@testset "Discrete variable timeseries" begin +# @variables x(t) +# @parameters a(t) b(t) c(t) +# cb1 = [x ~ 1.0] => [a ~ -Pre(a)] +# function save_affect!(integ, u, p, ctx) +# integ.ps[p.b] = 5.0 +# end +# cb2 = [x ~ 0.5] => (save_affect!, [], [b], [b], nothing) +# cb3 = 1.0 => [c ~ t] +# +# @mtkbuild sys = ODESystem(D(x) ~ cos(t), t, [x], [a, b, c]; +# continuous_events = [cb1, cb2], discrete_events = [cb3]) +# prob = ODEProblem(sys, [x => 1.0], (0.0, 2pi), [a => 1.0, b => 2.0, c => 0.0]) +# @test sort(canonicalize(Discrete(), prob.p)[1]) == [0.0, 1.0, 2.0] +# sol = solve(prob, Tsit5()) +# +# @test sol[a] == [1.0, -1.0] +# @test sol[b] == [2.0, 5.0, 5.0] +# @test sol[c] == [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0] +#end +# +#@testset "Heater" begin +# @variables temp(t) +# params = @parameters furnace_on_threshold=0.5 furnace_off_threshold=0.7 furnace_power=1.0 leakage=0.1 furnace_on::Bool=false +# eqs = [ +# D(temp) ~ furnace_on * furnace_power - temp^2 * leakage +# ] +# +# furnace_off = ModelingToolkit.SymbolicContinuousCallback( +# [temp ~ furnace_off_threshold], +# ModelingToolkit.ImperativeAffect(modified = (; furnace_on)) do x, o, i, c +# @set! x.furnace_on = false +# end) +# furnace_enable = ModelingToolkit.SymbolicContinuousCallback( +# [temp ~ furnace_on_threshold], +# ModelingToolkit.ImperativeAffect(modified = (; furnace_on)) do x, o, i, c +# @set! x.furnace_on = true +# end) +# @named sys = ODESystem( +# eqs, t, [temp], params; continuous_events = [furnace_off, furnace_enable]) +# ss = structural_simplify(sys) +# prob = ODEProblem(ss, [temp => 0.0, furnace_on => true], (0.0, 100.0)) +# sol = solve(prob, Tsit5(); dtmax = 0.01) +# @test all(sol[temp][sol.t .> 1.0] .<= 0.79) && all(sol[temp][sol.t .> 1.0] .>= 0.49) +# +# furnace_off = ModelingToolkit.SymbolicContinuousCallback( +# [temp ~ furnace_off_threshold], +# ModelingToolkit.ImperativeAffect(modified = (; furnace_on)) do x, o, c, i +# @set! x.furnace_on = false +# end; initialize = ModelingToolkit.ImperativeAffect(modified = (; +# temp)) do x, o, c, i +# @set! x.temp = 0.2 +# end) +# furnace_enable = ModelingToolkit.SymbolicContinuousCallback( +# [temp ~ furnace_on_threshold], +# ModelingToolkit.ImperativeAffect(modified = (; furnace_on)) do x, o, c, i +# @set! x.furnace_on = true +# end) +# @named sys = ODESystem( +# eqs, t, [temp], params; continuous_events = [furnace_off, furnace_enable]) +# ss = structural_simplify(sys) +# prob = ODEProblem(ss, [temp => 0.0, furnace_on => true], (0.0, 100.0)) +# sol = solve(prob, Tsit5(); dtmax = 0.01) +# @test all(sol[temp][sol.t .> 1.0] .<= 0.79) && all(sol[temp][sol.t .> 1.0] .>= 0.49) +# @test all(sol[temp][sol.t .!= 0.0] .<= 0.79) && all(sol[temp][sol.t .!= 0.0] .>= 0.2) +#end +# +#@testset "ImperativeAffect errors and warnings" begin +# @variables temp(t) +# params = @parameters furnace_on_threshold=0.5 furnace_off_threshold=0.7 furnace_power=1.0 leakage=0.1 furnace_on::Bool=false +# eqs = [ +# D(temp) ~ furnace_on * furnace_power - temp^2 * leakage +# ] +# +# furnace_off = ModelingToolkit.SymbolicContinuousCallback( +# [temp ~ furnace_off_threshold], +# ModelingToolkit.ImperativeAffect( +# modified = (; furnace_on), observed = (; furnace_on)) do x, o, c, i +# @set! x.furnace_on = false +# end) +# @named sys = ODESystem(eqs, t, [temp], params; continuous_events = [furnace_off]) +# ss = structural_simplify(sys) +# @test_logs (:warn, +# "The symbols Any[:furnace_on] are declared as both observed and modified; this is a code smell because it becomes easy to confuse them and assign/not assign a value.") prob=ODEProblem( +# ss, [temp => 0.0, furnace_on => true], (0.0, 100.0)) +# +# @variables tempsq(t) # trivially eliminated +# eqs = [tempsq ~ temp^2 +# D(temp) ~ furnace_on * furnace_power - temp^2 * leakage] +# +# furnace_off = ModelingToolkit.SymbolicContinuousCallback( +# [temp ~ furnace_off_threshold], +# ModelingToolkit.ImperativeAffect( +# modified = (; furnace_on, tempsq), observed = (; furnace_on)) do x, o, c, i +# @set! x.furnace_on = false +# end) +# @named sys = ODESystem( +# eqs, t, [temp, tempsq], params; continuous_events = [furnace_off]) +# ss = structural_simplify(sys) +# @test_throws "refers to missing variable(s)" prob=ODEProblem( +# ss, [temp => 0.0, furnace_on => true], (0.0, 100.0)) +# +# @parameters not_actually_here +# furnace_off = ModelingToolkit.SymbolicContinuousCallback( +# [temp ~ furnace_off_threshold], +# ModelingToolkit.ImperativeAffect(modified = (; furnace_on), +# observed = (; furnace_on, not_actually_here)) do x, o, c, i +# @set! x.furnace_on = false +# end) +# @named sys = ODESystem( +# eqs, t, [temp, tempsq], params; continuous_events = [furnace_off]) +# ss = structural_simplify(sys) +# @test_throws "refers to missing variable(s)" prob=ODEProblem( +# ss, [temp => 0.0, furnace_on => true], (0.0, 100.0)) +# +# furnace_off = ModelingToolkit.SymbolicContinuousCallback( +# [temp ~ furnace_off_threshold], +# ModelingToolkit.ImperativeAffect(modified = (; furnace_on), +# observed = (; furnace_on)) do x, o, c, i +# return (; fictional2 = false) +# end) +# @named sys = ODESystem( +# eqs, t, [temp, tempsq], params; continuous_events = [furnace_off]) +# ss = structural_simplify(sys) +# prob = ODEProblem( +# ss, [temp => 0.0, furnace_on => true], (0.0, 100.0)) +# @test_throws "Tried to write back to" solve(prob, Tsit5()) +#end +# +#@testset "Quadrature" begin +# @variables theta(t) omega(t) +# params = @parameters qA=0 qB=0 hA=0 hB=0 cnt::Int=0 +# eqs = [D(theta) ~ omega +# omega ~ 1.0] +# function decoder(oldA, oldB, newA, newB) +# state = (oldA, oldB, newA, newB) +# if state == (0, 0, 1, 0) || state == (1, 0, 1, 1) || state == (1, 1, 0, 1) || +# state == (0, 1, 0, 0) +# return 1 +# elseif state == (0, 0, 0, 1) || state == (0, 1, 1, 1) || state == (1, 1, 1, 0) || +# state == (1, 0, 0, 0) +# return -1 +# elseif state == (0, 0, 0, 0) || state == (0, 1, 0, 1) || state == (1, 0, 1, 0) || +# state == (1, 1, 1, 1) +# return 0 +# else +# return 0 # err is interpreted as no movement +# end +# end +# qAevt = ModelingToolkit.SymbolicContinuousCallback([cos(100 * theta) ~ 0], +# ModelingToolkit.ImperativeAffect((; qA, hA, hB, cnt), (; qB)) do x, o, c, i +# @set! x.hA = x.qA +# @set! x.hB = o.qB +# @set! x.qA = 1 +# @set! x.cnt += decoder(x.hA, x.hB, x.qA, o.qB) +# x +# end, +# affect_neg = ModelingToolkit.ImperativeAffect( +# (; qA, hA, hB, cnt), (; qB)) do x, o, c, i +# @set! x.hA = x.qA +# @set! x.hB = o.qB +# @set! x.qA = 0 +# @set! x.cnt += decoder(x.hA, x.hB, x.qA, o.qB) +# x +# end; rootfind = SciMLBase.RightRootFind) +# qBevt = ModelingToolkit.SymbolicContinuousCallback([cos(100 * theta - π / 2) ~ 0], +# ModelingToolkit.ImperativeAffect((; qB, hA, hB, cnt), (; qA)) do x, o, c, i +# @set! x.hA = o.qA +# @set! x.hB = x.qB +# @set! x.qB = 1 +# @set! x.cnt += decoder(x.hA, x.hB, o.qA, x.qB) +# x +# end, +# affect_neg = ModelingToolkit.ImperativeAffect( +# (; qB, hA, hB, cnt), (; qA)) do x, o, c, i +# @set! x.hA = o.qA +# @set! x.hB = x.qB +# @set! x.qB = 0 +# @set! x.cnt += decoder(x.hA, x.hB, o.qA, x.qB) +# x +# end; rootfind = SciMLBase.RightRootFind) +# @named sys = ODESystem( +# eqs, t, [theta, omega], params; continuous_events = [qAevt, qBevt]) +# ss = structural_simplify(sys) +# prob = ODEProblem(ss, [theta => 1e-5], (0.0, pi)) +# sol = solve(prob, Tsit5(); dtmax = 0.01) +# @test getp(sol, cnt)(sol) == 198 # we get 2 pulses per phase cycle (cos 0 crossing) and we go to 100 cycles; we miss a few due to the initial state +#end +# +#@testset "Initialization" begin +# @variables x(t) +# seen = false +# f = ModelingToolkit.FunctionalAffect( +# f = (i, u, p, c) -> seen = true, sts = [], pars = [], discretes = []) +# cb1 = ModelingToolkit.SymbolicContinuousCallback( +# [x ~ 0], nothing, initialize = [x ~ 1.5], finalize = f) +# @mtkbuild sys = ODESystem(D(x) ~ -1, t, [x], []; continuous_events = [cb1]) +# prob = ODEProblem(sys, [x => 1.0], (0.0, 2), []) +# sol = solve(prob, Tsit5(); dtmax = 0.01) +# @test sol[x][1] ≈ 1.0 +# @test sol[x][2] ≈ 1.5 # the initialize affect has been applied +# @test seen == true +# +# @variables x(t) +# seen = false +# f = ModelingToolkit.FunctionalAffect( +# f = (i, u, p, c) -> seen = true, sts = [], pars = [], discretes = []) +# 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 = []) +# cb2 = ModelingToolkit.SymbolicContinuousCallback( +# [x ~ 0.1], nothing, initialize = a, finalize = b) +# @mtkbuild sys = ODESystem(D(x) ~ -1, t, [x], []; continuous_events = [cb1, cb2]) +# prob = ODEProblem(sys, [x => 1.0], (0.0, 2), []) +# sol = solve(prob, Tsit5()) +# @test sol[x][1] ≈ 1.0 +# @test sol[x][2] ≈ 1.5 # the initialize affect has been applied +# @test seen == true +# @test inited == true +# @test finaled == true +# +# #periodic +# inited = false +# finaled = false +# cb3 = ModelingToolkit.SymbolicDiscreteCallback( +# 1.0, [x ~ 2], initialize = a, finalize = b) +# @mtkbuild sys = ODESystem(D(x) ~ -1, t, [x], []; discrete_events = [cb3]) +# prob = ODEProblem(sys, [x => 1.0], (0.0, 2), []) +# sol = solve(prob, Tsit5()) +# @test inited == true +# @test finaled == true +# @test isapprox(sol[x][3], 0.0, atol = 1e-9) +# @test sol[x][4] ≈ 2.0 +# @test sol[x][5] ≈ 1.0 +# +# seen = false +# inited = false +# finaled = false +# cb3 = ModelingToolkit.SymbolicDiscreteCallback(1.0, f, initialize = a, finalize = b) +# @mtkbuild sys = ODESystem(D(x) ~ -1, t, [x], []; discrete_events = [cb3]) +# prob = ODEProblem(sys, [x => 1.0], (0.0, 2), []) +# sol = solve(prob, Tsit5()) +# @test seen == true +# @test inited == true +# +# #preset +# seen = false +# inited = false +# finaled = false +# cb3 = ModelingToolkit.SymbolicDiscreteCallback([1.0], f, initialize = a, finalize = b) +# @mtkbuild sys = ODESystem(D(x) ~ -1, t, [x], []; discrete_events = [cb3]) +# prob = ODEProblem(sys, [x => 1.0], (0.0, 2), []) +# sol = solve(prob, Tsit5()) +# @test seen == true +# @test inited == true +# @test finaled == true +# +# #equational +# seen = false +# inited = false +# finaled = false +# cb3 = ModelingToolkit.SymbolicDiscreteCallback( +# t == 1.0, f, initialize = a, finalize = b) +# @mtkbuild sys = ODESystem(D(x) ~ -1, t, [x], []; discrete_events = [cb3]) +# prob = ODEProblem(sys, [x => 1.0], (0.0, 2), []) +# sol = solve(prob, Tsit5(); tstops = 1.0) +# @test seen == true +# @test inited == true +# @test finaled == true +#end @testset "Bump" begin @variables x(t) [irreducible = true] y(t) [irreducible = true] @@ -1325,9 +1325,62 @@ end @test 100.0 ∈ sol2[sys2.wd2.θ] end +@testset "Implicit affects with Pre" begin + @parameters g + @variables x(t) y(t) λ(t) + eqs = [D(D(x)) ~ λ * x + D(D(y)) ~ λ * y - g + x^2 + y^2 ~ 1] + c_evt = [t ~ 0.5] => [x ~ Pre(x) + 0.1] + @mtkbuild pend = ODESystem(eqs, t, continuous_events = c_evt) + prob = ODEProblem(pend, [x => 1, y => 0], (0., 1.), [g => 1], guesses = [λ => 1]) + sol = solve(prob, Rodas5()) + @test sol(0.5000001)[1] - sol(0.4999999)[1] ≈ 0.1 + @test sol(0.5000001)[1]^2 + sol(0.5000001)[2]^2 ≈ 1 + + # Implicit affect with Pre + c_evt = [t ~ 0.5] => [x ~ Pre(x) + y^2] + @mtkbuild pend = ODESystem(eqs, t, continuous_events = c_evt) + prob = ODEProblem(pend, [x => 1, y => 0], (0., 1.), [g => 1], guesses = [λ => 1]) + sol = solve(prob, Rodas5()) + @test sol(0.5000001)[2]^2 - sol(0.4999999)[1] ≈ sol(0.5000001)[1] + @test sol(0.5000001)[1]^2 + sol(0.5000001)[2]^2 ≈ 1 + + # Impossible affect errors + c_evt = [t ~ 0.5] => [x ~ Pre(x) + 1] + @mtkbuild pend = ODESystem(eqs, t, continuous_events = c_evt) + prob = ODEProblem(pend, [x => 1, y => 0], (0., 1.), [g => 1], guesses = [λ => 1]) + @test_throws Exception sol = solve(prob, Rodas5()) + + # Changing both variables and parameters in the same affect. + c_evt = [t ~ 0.5] => [x ~ Pre(x) + 1, g ~ Pre(g) + 1] + @mtkbuild pend = ODESystem(eqs, t, continuous_events = c_evt) + prob = ODEProblem(pend, [x => 1, y => 0], (0., 1.), [g => 1], guesses = [λ => 1]) + sol = solve(prob, Rodas5()) + @test sol.ps[g] ≈ 2 + @test sol(0.5000001, idxs = x) - sol(0.4999999, idxs = x) ≈ 1 + + # Proper re-initialization after parameter change + eqs = [x ~ g^2 - y, D(x) ~ x] + c_evt = [t ~ 0.5] => [x ~ Pre(x) + 1, g ~ Pre(g) + 1] + @mtkbuild sys = ODESystem(eqs, t, continuous_events = c_evt) + prob = ODEProblem(sys, [x => 0.5], (0., 1.), [g => 2], guesses = [y => 0]) + sol = solve(prob, Rodas5()) + @test sol.ps[g] ≈ 3 + @test ≈(sol(0.5000001)[1] - sol(0.4999999)[1], 1; atol = 1e-6) + @test sol(0.5000001, idxs = y) ≈ 9 - sol(0.5000001, idxs = x) + + # Parameters that don't appear in affects should not be mutated. + c_evt = [t ~ 0.5] => [x ~ Pre(x) + 1] + @mtkbuild sys = ODESystem(eqs, t, continuous_events = c_evt) + prob = ODEProblem(sys, [x => 0.5], (0., 1.), [g => 2], guesses = [y => 0]) + sol = solve(prob, Rodas5()) + @test prob.ps[g] == sol.ps[g] +end + + # TODO: test: # - Functional affects reinitialize correctly # - explicit equation of t in a functional affect -# - modifying both u and p in an affect # - affects that have Pre but are also algebraic in nature # - reinitialization after affects From b9669c113de3e59d89916e2163d11a4b8742e993 Mon Sep 17 00:00:00 2001 From: vyudu Date: Wed, 26 Mar 2025 14:03:22 -0400 Subject: [PATCH 054/122] up --- src/systems/callbacks.jl | 76 +- .../implicit_discrete_system.jl | 1 - src/systems/index_cache.jl | 1 + test/symbolic_events.jl | 802 ++++++++---------- 4 files changed, 410 insertions(+), 470 deletions(-) diff --git a/src/systems/callbacks.jl b/src/systems/callbacks.jl index 5d34644297..5a97472c6d 100644 --- a/src/systems/callbacks.jl +++ b/src/systems/callbacks.jl @@ -62,7 +62,6 @@ struct AffectSystem discretes::Vector """Maps the symbols of unknowns/observed in the ImplicitDiscreteSystem to its corresponding unknown/parameter in the parent system.""" aff_to_sys::Dict - explicit::Bool end system(a::AffectSystem) = a.system @@ -71,11 +70,11 @@ unknowns(a::AffectSystem) = a.unknowns parameters(a::AffectSystem) = a.parameters aff_to_sys(a::AffectSystem) = a.aff_to_sys previous_vals(a::AffectSystem) = parameters(system(a)) -is_explicit(a::AffectSystem) = a.explicit +all_equations(a::AffectSystem) = vcat(equations(system(a)), observed(system(a))) function Base.show(iio::IO, aff::AffectSystem) println(iio, "Affect system defined by equations:") - eqs = vcat(equations(system(aff)), observed(system(aff))) + eqs = all_equations(aff) show(iio, eqs) end @@ -84,8 +83,7 @@ function Base.:(==)(a1::AffectSystem, a2::AffectSystem) isequal(discretes(a1), discretes(a2)) && isequal(unknowns(a1), unknowns(a2)) && isequal(parameters(a1), parameters(a2)) && - isequal(aff_to_sys(a1), aff_to_sys(a2)) && - isequal(is_explicit(a1), is_explicit(a2)) + isequal(aff_to_sys(a1), aff_to_sys(a2)) end function Base.hash(a::AffectSystem, s::UInt) @@ -93,8 +91,7 @@ function Base.hash(a::AffectSystem, s::UInt) s = hash(unknowns(a), s) s = hash(parameters(a), s) s = hash(discretes(a), s) - s = hash(aff_to_sys(a), s) - hash(is_explicit(a), s) + hash(aff_to_sys(a), s) end function vars!(vars, aff::Union{FunctionalAffect, AffectSystem}; op = Differential) @@ -241,51 +238,44 @@ make_affect(affect::Tuple; kwargs...) = FunctionalAffect(affect...) make_affect(affect::NamedTuple; kwargs...) = FunctionalAffect(; affect...) make_affect(affect::Affect; kwargs...) = affect -function make_affect(affect::Vector{Equation}; discrete_parameters = Any[], iv = nothing, algeeqs::Vector{Equation} = Equation[]) +function make_affect(affect::Vector{Equation}; discrete_parameters::AbstractVector = Any[], iv = nothing, algeeqs::Vector{Equation} = Equation[]) isempty(affect) && return nothing isempty(algeeqs) && @warn "No algebraic equations were found for the callback defined by $(join(affect, ", ")). If the system has no algebraic equations, this can be disregarded. Otherwise pass in `algeeqs` to the SymbolicContinuousCallback constructor." + isnothing(iv) && error("Must specify iv.") - for p in discretes - # Check if p is time-dependent - false && error("Non-time dependent parameter $p passed in as a discrete. Must be declared as $p(t).") + for p in discrete_parameters + occursin(unwrap(iv), unwrap(p)) || error("Non-time dependent parameter $p passed in as a discrete. Must be declared as @parameters $p(t).") end - explicit = true dvs = OrderedSet() params = OrderedSet() for eq in affect - if !haspre(eq) && !(symbolic_type(eq.rhs) === NotSymbolic()) + if !haspre(eq) && !(symbolic_type(eq.rhs) === NotSymbolic() || symbolic_type(eq.lhs) === NotSymbolic()) @warn "Affect equation $eq has no `Pre` operator. As such it will be interpreted as an algebraic equation to be satisfied after the callback. If you intended to use the value of a variable x before the affect, use Pre(x)." - explicit = false end collect_vars!(dvs, params, eq, iv; op = Pre) end for eq in algeeqs collect_vars!(dvs, params, eq, iv) - explicit = false - end - any(isirreducible, dvs) && (explicit = false) - - if isnothing(iv) - iv = isempty(dvs) ? iv : only(arguments(dvs[1])) - isnothing(iv) && @warn "No independent variable specified and could not be inferred. If the iv appears in an affect equation explicitly, like x ~ t + 1, then it must be specified as an argument to the SymbolicContinuousCallback or SymbolicDiscreteCallback constructor. Otherwise this warning can be disregarded." end pre_params = filter(haspre ∘ value, params) - sys_params = setdiff(params, union(discrete_parameters, pre_params)) + sys_params = collect(setdiff(params, union(discrete_parameters, pre_params))) discretes = map(tovar, discrete_parameters) aff_map = Dict(zip(discretes, discrete_parameters)) - @named affectsys = ImplicitDiscreteSystem(vcat(affect, algeeqs), iv, collect(union(dvs, discretes)), collect(union(pre_params, sys_params))) - affectsys = complete(affectsys) + rev_map = Dict(zip(discrete_parameters, discretes)) + affect = Symbolics.fast_substitute(affect, rev_map) + algeeqs = Symbolics.fast_substitute(algeeqs, rev_map) + @mtkbuild affectsys = ImplicitDiscreteSystem(vcat(affect, algeeqs), iv, collect(union(dvs, discretes)), collect(union(pre_params, sys_params))) # get accessed parameters p from Pre(p) in the callback parameters - accessed_params = filter(isparameter, map(x -> unPre(x), cb_params)) + accessed_params = filter(isparameter, map(unPre, collect(pre_params))) union!(accessed_params, sys_params) # add unknowns to the map for u in dvs aff_map[u] = u end - AffectSystem(affectsys, collect(dvs), collect(accessed_params), collect(discrete_parameters), aff_map, explicit) + AffectSystem(affectsys, collect(dvs), collect(accessed_params), collect(discrete_parameters), aff_map) end function make_affect(affect; kwargs...) @@ -295,7 +285,7 @@ end """ Generate continuous callbacks. """ -function SymbolicContinuousCallbacks(events; algeeqs::Vector{Equation} = Equation[], iv = nothing) +function SymbolicContinuousCallbacks(events; discrete_parameters = Any[], algeeqs::Vector{Equation} = Equation[], iv = nothing) callbacks = SymbolicContinuousCallback[] isnothing(events) && return callbacks @@ -304,7 +294,7 @@ function SymbolicContinuousCallbacks(events; algeeqs::Vector{Equation} = Equatio for event in events cond, affs = event isa Pair ? (event[1], event[2]) : (event, nothing) - push!(callbacks, SymbolicContinuousCallback(cond, affs; iv, algeeqs)) + push!(callbacks, SymbolicContinuousCallback(cond, affs; iv, algeeqs, discrete_parameters)) end callbacks end @@ -412,11 +402,11 @@ struct SymbolicDiscreteCallback <: AbstractCallback function SymbolicDiscreteCallback( condition, affect = nothing; - initialize = nothing, finalize = nothing, iv = nothing, algeeqs = Equation[]) + initialize = nothing, finalize = nothing, iv = nothing, algeeqs = Equation[], discrete_parameters = Any[]) c = is_timed_condition(condition) ? condition : value(scalarize(condition)) - new(c, make_affect(affect; iv, algeeqs), make_affect(initialize; iv, algeeqs), - make_affect(finalize; iv, algeeqs)) + new(c, make_affect(affect; iv, algeeqs, discrete_parameters), make_affect(initialize; iv, algeeqs, discrete_parameters), + make_affect(finalize; iv, algeeqs, discrete_parameters)) end # Default affect to nothing end @@ -426,7 +416,7 @@ SymbolicDiscreteCallback(cb::SymbolicDiscreteCallback, args...; kwargs...) = cb """ Generate discrete callbacks. """ -function SymbolicDiscreteCallbacks(events; algeeqs::Vector{Equation} = Equation[], iv = nothing) +function SymbolicDiscreteCallbacks(events; discrete_parameters::Vector = Any[], algeeqs::Vector{Equation} = Equation[], iv = nothing) callbacks = SymbolicDiscreteCallback[] isnothing(events) && return callbacks @@ -435,7 +425,7 @@ function SymbolicDiscreteCallbacks(events; algeeqs::Vector{Equation} = Equation[ for event in events cond, affs = event isa Pair ? (event[1], event[2]) : (event, nothing) - push!(callbacks, SymbolicDiscreteCallback(cond, affs; iv, algeeqs)) + push!(callbacks, SymbolicDiscreteCallback(cond, affs; iv, algeeqs, discrete_parameters)) end callbacks end @@ -471,7 +461,7 @@ function namespace_affects(affect::AffectSystem, s) renamespace.((s,), unknowns(affect)), renamespace.((s,), parameters(affect)), renamespace.((s,), discretes(affect)), - Dict([k => renamespace(s, v) for (k, v) in aff_to_sys(affect)]), is_explicit(affect)) + Dict([k => renamespace(s, v) for (k, v) in aff_to_sys(affect)])) end namespace_affects(af::Nothing, s) = nothing @@ -837,19 +827,17 @@ function compile_equational_affect(aff::Union{AffectSystem, Vector{Equation}}, s aff_map = aff_to_sys(aff) sys_map = Dict([v => k for (k, v) in aff_map]) - if is_explicit(aff) - affsys = structural_simplify(affsys) - @assert isempty(equations(affsys)) + if isempty(equations(affsys)) update_eqs = Symbolics.fast_substitute(observed(affsys), Dict([p => unPre(p) for p in parameters(affsys)])) rhss = map(x -> x.rhs, update_eqs) lhss = map(x -> aff_map[x.lhs], update_eqs) is_p = [lhs ∈ Set(ps_to_update) for lhs in lhss] - + is_u = [lhs ∈ Set(dvs_to_update) for lhs in lhss] dvs = unknowns(sys) ps = parameters(sys) t = get_iv(sys) - u_idxs = indexin((@view lhss[.!is_p]), dvs) + u_idxs = indexin((@view lhss[is_u]), dvs) wrap_mtkparameters = has_index_cache(sys) && (get_index_cache(sys) !== nothing) p_idxs = if wrap_mtkparameters @@ -861,7 +849,7 @@ function compile_equational_affect(aff::Union{AffectSystem, Vector{Equation}}, s _ps = reorder_parameters(sys, ps) integ = gensym(:MTKIntegrator) - u_up, u_up! = build_function_wrapper(sys, (@view rhss[.!is_p]), dvs, _ps..., t; wrap_code = add_integrator_header(sys, integ, :u), expression = Val{false}, outputidxs = u_idxs, wrap_mtkparameters) + u_up, u_up! = build_function_wrapper(sys, (@view rhss[is_u]), dvs, _ps..., t; wrap_code = add_integrator_header(sys, integ, :u), expression = Val{false}, outputidxs = u_idxs, wrap_mtkparameters) p_up, p_up! = build_function_wrapper(sys, (@view rhss[is_p]), dvs, _ps..., t; wrap_code = add_integrator_header(sys, integ, :p), expression = Val{false}, outputidxs = p_idxs, wrap_mtkparameters) return function explicit_affect!(integ) @@ -870,7 +858,7 @@ function compile_equational_affect(aff::Union{AffectSystem, Vector{Equation}}, s reset_jumps && reset_aggregated_jumps!(integ) 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 + 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 function implicit_affect!(integ) pmap = Pair[] for pre_p in parameters(affsys) @@ -885,7 +873,7 @@ function compile_equational_affect(aff::Union{AffectSystem, Vector{Equation}}, s end affprob = ImplicitDiscreteProblem(affsys, u0, (integ.t, integ.t), pmap; build_initializeprob = false, check_length = false) affsol = init(affprob, IDSolve()) - check_error(affsol) && throw(UnsolvableCallbackError(equations(affsys))) + (check_error(affsol) === ReturnCode.InitialFailure) && throw(UnsolvableCallbackError(all_equations(aff))) for u in dvs_to_update integ[u] = affsol[sys_map[u]] end @@ -901,8 +889,8 @@ struct UnsolvableCallbackError eqs::Vector{Equation} end -function Base.showerror(io, err::UnsolvableCallbackError) - println(io, "The callback defined by the equations, $(join(err.eqs, "\n")), with discrete parameters is not solvable. Please check the algebraic equations, affect equations, and declared discrete parameters.") +function Base.showerror(io::IO, err::UnsolvableCallbackError) + println(io, "The callback defined by the following equations:\n\n$(join(err.eqs, "\n"))\n\nis not solvable. Please check that the algebraic equations and affect equations are correct, and that all parameters intended to be changed are passed in as `discrete_parameters`.") end merge_cb(::Nothing, ::Nothing) = nothing diff --git a/src/systems/discrete_system/implicit_discrete_system.jl b/src/systems/discrete_system/implicit_discrete_system.jl index b63235bbdc..a43595b25b 100644 --- a/src/systems/discrete_system/implicit_discrete_system.jl +++ b/src/systems/discrete_system/implicit_discrete_system.jl @@ -287,7 +287,6 @@ function generate_function( u_next = map(Shift(iv, 1), dvs) u = dvs p = (reorder_parameters(sys, unwrap.(ps))..., cachesyms...) - @show exprs build_function_wrapper( sys, exprs, u_next, u, p..., iv; p_start = 3, extra_assignments, kwargs...) end diff --git a/src/systems/index_cache.jl b/src/systems/index_cache.jl index e4087e1368..1186e70b5b 100644 --- a/src/systems/index_cache.jl +++ b/src/systems/index_cache.jl @@ -127,6 +127,7 @@ function IndexCache(sys::AbstractSystem) end for sym in discs + @show sym is_parameter(sys, sym) || error("Expected discrete variable $sym in callback to be a parameter") diff --git a/test/symbolic_events.jl b/test/symbolic_events.jl index f3f0d51199..bc58f78900 100644 --- a/test/symbolic_events.jl +++ b/test/symbolic_events.jl @@ -2,6 +2,8 @@ using ModelingToolkit, OrdinaryDiffEq, StochasticDiffEq, JumpProcesses, Test using SciMLStructures: canonicalize, Discrete using ModelingToolkit: SymbolicContinuousCallback, SymbolicContinuousCallbacks, + SymbolicDiscreteCallback, + SymbolicDiscreteCallbacks, get_callback, t_nounits as t, D_nounits as D, @@ -475,400 +477,348 @@ affect_neg = [x ~ 1] # sys = structural_simplify(model) # @test isempty(ModelingToolkit.continuous_events(sys)) #end -# -#@testset "ODESystem Discrete Callbacks" begin -# function testsol(osys, u0, p, tspan; tstops = Float64[], paramtotest = nothing, -# kwargs...) -# oprob = ODEProblem(complete(osys), u0, tspan, p; kwargs...) -# sol = solve(oprob, Tsit5(); tstops = tstops, abstol = 1e-10, reltol = 1e-10) -# @test isapprox(sol(1.0000000001)[1] - sol(0.999999999)[1], 1.0; rtol = 1e-6) -# paramtotest === nothing || (@test sol.ps[paramtotest] == 1.0) -# @test isapprox(sol(4.0)[1], 2 * exp(-2.0)) -# sol -# end -# -# @parameters k t1 t2 -# @variables A(t) B(t) -# -# cond1 = (t == t1) -# affect1 = [A ~ Pre(A) + 1] -# cb1 = cond1 => affect1 -# cond2 = (t == t2) -# affect2 = [k ~ 1.0] -# cb2 = cond2 => affect2 -# -# ∂ₜ = D -# eqs = [∂ₜ(A) ~ -k * A] -# @named osys = ODESystem(eqs, t, [A], [k, t1, t2], discrete_events = [cb1, cb2]) -# u0 = [A => 1.0] -# p = [k => 0.0, t1 => 1.0, t2 => 2.0] -# tspan = (0.0, 4.0) -# testsol(osys, u0, p, tspan; tstops = [1.0, 2.0], paramtotest = k) -# -# cond1a = (t == t1) -# affect1a = [A ~ Pre(A) + 1, B ~ A] -# cb1a = cond1a => affect1a -# @named osys1 = ODESystem(eqs, t, [A, B], [k, t1, t2], discrete_events = [cb1a, cb2]) -# u0′ = [A => 1.0, B => 0.0] -# sol = testsol( -# osys1, u0′, p, tspan; tstops = [1.0, 2.0], check_length = false, paramtotest = k) -# @test sol(1.0000001, idxs = B) == 2.0 -# -# # same as above - but with set-time event syntax -# cb1‵ = [1.0] => affect1 # needs to be a Vector for the event to happen only once -# cb2‵ = [2.0] => affect2 -# @named osys‵ = ODESystem(eqs, t, [A], [k], discrete_events = [cb1‵, cb2‵]) -# testsol(osys‵, u0, p, tspan; paramtotest = k) -# -# # mixing discrete affects -# @named osys3 = ODESystem(eqs, t, [A], [k, t1, t2], discrete_events = [cb1, cb2‵]) -# testsol(osys3, 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 -# end -# cb2‵‵ = [2.0] => (affect!, [], [k], [k], nothing) -# @named osys4 = ODESystem(eqs, t, [A], [k, t1], discrete_events = [cb1, cb2‵‵]) -# oprob4 = ODEProblem(complete(osys4), u0, tspan, p) -# testsol(osys4, u0, p, tspan; tstops = [1.0], paramtotest = k) -# -# # mixing with symbolic condition in the func affect -# cb2‵‵‵ = (t == t2) => (affect!, [], [k], [k], nothing) -# @named osys5 = ODESystem(eqs, t, [A], [k, t1, t2], discrete_events = [cb1, cb2‵‵‵]) -# testsol(osys5, u0, p, tspan; tstops = [1.0, 2.0]) -# @named osys6 = ODESystem(eqs, t, [A], [k, t1, t2], discrete_events = [cb2‵‵‵, cb1]) -# testsol(osys6, u0, p, tspan; tstops = [1.0, 2.0], paramtotest = k) -# -# # mix a continuous event too -# cond3 = A ~ 0.1 -# affect3 = [k ~ 0.0] -# cb3 = cond3 => affect3 -# @named osys7 = ODESystem(eqs, t, [A], [k, t1, t2], discrete_events = [cb1, cb2‵‵‵], -# continuous_events = [cb3]) -# sol = testsol(osys7, u0, p, (0.0, 10.0); tstops = [1.0, 2.0]) -# @test isapprox(sol(10.0)[1], 0.1; atol = 1e-10, rtol = 1e-10) -#end -# -#@testset "SDESystem Discrete Callbacks" begin -# function testsol(ssys, u0, p, tspan; tstops = Float64[], paramtotest = nothing, -# kwargs...) -# sprob = SDEProblem(complete(ssys), u0, tspan, p; kwargs...) -# sol = solve(sprob, RI5(); tstops = tstops, abstol = 1e-10, reltol = 1e-10) -# @test isapprox(sol(1.0000000001)[1] - sol(0.999999999)[1], 1.0; rtol = 1e-4) -# paramtotest === nothing || (@test sol.ps[paramtotest] == 1.0) -# @test isapprox(sol(4.0)[1], 2 * exp(-2.0), atol = 1e-4) -# sol -# end -# -# @parameters k t1 t2 -# @variables A(t) B(t) -# -# cond1 = (t == t1) -# affect1 = [A ~ Pre(A) + 1] -# cb1 = cond1 => affect1 -# cond2 = (t == t2) -# affect2 = [k ~ 1.0] -# cb2 = cond2 => affect2 -# -# ∂ₜ = D -# eqs = [∂ₜ(A) ~ -k * A] -# @named ssys = SDESystem(eqs, [0.0], t, [A], [k, t1, t2], -# discrete_events = [cb1, cb2]) -# u0 = [A => 1.0] -# p = [k => 0.0, t1 => 1.0, t2 => 2.0] -# tspan = (0.0, 4.0) -# testsol(ssys, u0, p, tspan; tstops = [1.0, 2.0], paramtotest = k) -# -# cond1a = (t == t1) -# affect1a = [A ~ Pre(A) + 1, B ~ A] -# cb1a = cond1a => affect1a -# @named ssys1 = SDESystem(eqs, [0.0], t, [A, B], [k, t1, t2], -# discrete_events = [cb1a, cb2]) -# u0′ = [A => 1.0, B => 0.0] -# sol = testsol( -# ssys1, u0′, p, tspan; tstops = [1.0, 2.0], check_length = false, paramtotest = k) -# @test sol(1.0000001, idxs = 2) == 2.0 -# -# # same as above - but with set-time event syntax -# cb1‵ = [1.0] => affect1 # needs to be a Vector for the event to happen only once -# cb2‵ = [2.0] => affect2 -# @named ssys‵ = SDESystem(eqs, [0.0], t, [A], [k], discrete_events = [cb1‵, cb2‵]) -# testsol(ssys‵, u0, p, tspan; paramtotest = k) -# -# # mixing discrete affects -# @named ssys3 = SDESystem(eqs, [0.0], t, [A], [k, t1, t2], -# discrete_events = [cb1, cb2‵]) -# testsol(ssys3, u0, p, tspan; tstops = [1.0], paramtotest = k) -# -# # mixing with a func affect -# function affect!(integrator, u, p, ctx) -# setp(integrator, p.k)(integrator, 1.0) -# nothing -# end -# cb2‵‵ = [2.0] => (affect!, [], [k], [k], nothing) -# @named ssys4 = SDESystem(eqs, [0.0], t, [A], [k, t1], -# discrete_events = [cb1, cb2‵‵]) -# testsol(ssys4, u0, p, tspan; tstops = [1.0], paramtotest = k) -# -# # mixing with symbolic condition in the func affect -# cb2‵‵‵ = (t == t2) => (affect!, [], [k], [k], nothing) -# @named ssys5 = SDESystem(eqs, [0.0], t, [A], [k, t1, t2], -# discrete_events = [cb1, cb2‵‵‵]) -# testsol(ssys5, u0, p, tspan; tstops = [1.0, 2.0], paramtotest = k) -# @named ssys6 = SDESystem(eqs, [0.0], t, [A], [k, t1, t2], -# discrete_events = [cb2‵‵‵, cb1]) -# testsol(ssys6, u0, p, tspan; tstops = [1.0, 2.0], paramtotest = k) -# -# # mix a continuous event too -# cond3 = A ~ 0.1 -# affect3 = [k ~ 0.0] -# cb3 = cond3 => affect3 -# @named ssys7 = SDESystem(eqs, [0.0], t, [A], [k, t1, t2], -# discrete_events = [cb1, cb2‵‵‵], -# continuous_events = [cb3]) -# sol = testsol(ssys7, u0, p, (0.0, 10.0); tstops = [1.0, 2.0]) -# @test isapprox(sol(10.0)[1], 0.1; atol = 1e-10, rtol = 1e-10) -#end -# -#@testset "JumpSystem Discrete Callbacks" begin -# function testsol(jsys, u0, p, tspan; tstops = Float64[], paramtotest = nothing, -# N = 40000, kwargs...) -# jsys = complete(jsys) -# dprob = DiscreteProblem(jsys, u0, tspan, p) -# jprob = JumpProblem(jsys, dprob, Direct(); kwargs...) -# sol = solve(jprob, SSAStepper(); tstops = tstops) -# @test (sol(1.000000000001)[1] - sol(0.99999999999)[1]) == 1 -# paramtotest === nothing || (@test sol.ps[paramtotest] == 1.0) -# @test sol(40.0)[1] == 0 -# sol -# end -# -# @parameters k t1 t2 -# @variables A(t) B(t) -# -# cond1 = (t == t1) -# affect1 = [A ~ Pre(A) + 1] -# cb1 = cond1 => affect1 -# cond2 = (t == t2) -# affect2 = [k ~ 1.0] -# cb2 = cond2 => affect2 -# -# eqs = [MassActionJump(k, [A => 1], [A => -1])] -# @named jsys = JumpSystem(eqs, t, [A], [k, t1, t2], discrete_events = [cb1, cb2]) -# u0 = [A => 1] -# p = [k => 0.0, t1 => 1.0, t2 => 2.0] -# tspan = (0.0, 40.0) -# testsol(jsys, u0, p, tspan; tstops = [1.0, 2.0], rng, paramtotest = k) -# -# cond1a = (t == t1) -# affect1a = [A ~ Pre(A) + 1, B ~ A] -# cb1a = cond1a => affect1a -# @named jsys1 = JumpSystem(eqs, t, [A, B], [k, t1, t2], discrete_events = [cb1a, cb2]) -# u0′ = [A => 1, B => 0] -# sol = testsol(jsys1, u0′, p, tspan; tstops = [1.0, 2.0], -# check_length = false, rng, paramtotest = k) -# @test sol(1.000000001, idxs = B) == 2 -# -# # same as above - but with set-time event syntax -# cb1‵ = [1.0] => affect1 # needs to be a Vector for the event to happen only once -# cb2‵ = [2.0] => affect2 -# @named jsys‵ = JumpSystem(eqs, t, [A], [k], discrete_events = [cb1‵, cb2‵]) -# testsol(jsys‵, u0, [p[1]], tspan; rng, paramtotest = k) -# -# # mixing discrete affects -# @named jsys3 = JumpSystem(eqs, t, [A], [k, t1, t2], discrete_events = [cb1, cb2‵]) -# 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 -# end -# cb2‵‵ = [2.0] => (affect!, [], [k], [k], nothing) -# @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) -# @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]) -# testsol(jsys6, u0, p, tspan; tstops = [1.0, 2.0], rng, paramtotest = k) -#end -# -#@testset "Namespacing" begin -# function oscillator_ce(k = 1.0; name) -# sts = @variables x(t)=1.0 v(t)=0.0 F(t) -# ps = @parameters k=k Θ=0.5 -# eqs = [D(x) ~ v, D(v) ~ -k * x + F] -# ev = [x ~ Θ] => [x ~ 1.0, v ~ 0.0] -# ODESystem(eqs, t, sts, ps, continuous_events = [ev]; name) -# end -# -# @named oscce = oscillator_ce() -# eqs = [oscce.F ~ 0] -# @named eqs_sys = ODESystem(eqs, t) -# @named oneosc_ce = compose(eqs_sys, oscce) -# oneosc_ce_simpl = structural_simplify(oneosc_ce) -# -# prob = ODEProblem(oneosc_ce_simpl, [], (0.0, 2.0), []) -# sol = solve(prob, Tsit5(), saveat = 0.1) -# -# @test typeof(oneosc_ce_simpl) == ODESystem -# @test sol[1, 6] < 1.0 # test whether x(t) decreases over time -# @test sol[1, 18] > 0.5 # test whether event happened -#end -# -#@testset "Additional SymbolicContinuousCallback options" begin -# # 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]) -# cr1 = [] -# cr2 = [] -# evt1 = ModelingToolkit.SymbolicContinuousCallback( -# [c1 ~ 0], (record_crossings, [c1 => :v], [], [], cr1)) -# evt2 = ModelingToolkit.SymbolicContinuousCallback( -# [c2 ~ 0], (record_crossings, [c2 => :v], [], [], cr2)) -# @named trigsys = ODESystem(eqs, t; continuous_events = [evt1, evt2]) -# trigsys_ss = structural_simplify(trigsys) -# prob = ODEProblem(trigsys_ss, [], (0.0, 2π)) -# sol = solve(prob, Tsit5()) -# required_crossings_c1 = [π / 2, 3 * π / 2] -# required_crossings_c2 = [π / 6, π / 2, 5 * π / 6, 7 * π / 6, 3 * π / 2, 11 * π / 6] -# @test maximum(abs.(first.(cr1) .- required_crossings_c1)) < 1e-4 -# @test maximum(abs.(first.(cr2) .- required_crossings_c2)) < 1e-4 -# @test sign.(cos.(required_crossings_c1 .- 1e-6)) == sign.(last.(cr1)) -# @test sign.(cos.(3 * (required_crossings_c2 .- 1e-6))) == sign.(last.(cr2)) -# -# # with neg affect (pos * neg + left root find) -# cr1p = [] -# cr2p = [] -# cr1n = [] -# cr2n = [] -# evt1 = ModelingToolkit.SymbolicContinuousCallback( -# [c1 ~ 0], (record_crossings, [c1 => :v], [], [], cr1p); -# affect_neg = (record_crossings, [c1 => :v], [], [], cr1n)) -# evt2 = ModelingToolkit.SymbolicContinuousCallback( -# [c2 ~ 0], (record_crossings, [c2 => :v], [], [], cr2p); -# affect_neg = (record_crossings, [c2 => :v], [], [], cr2n)) -# @named trigsys = ODESystem(eqs, t; continuous_events = [evt1, evt2]) -# trigsys_ss = structural_simplify(trigsys) -# prob = ODEProblem(trigsys_ss, [], (0.0, 2π)) -# sol = solve(prob, Tsit5(); dtmax = 0.01) -# c1_pc = filter((<=)(0) ∘ sin, required_crossings_c1) -# c1_nc = filter((>=)(0) ∘ sin, required_crossings_c1) -# c2_pc = filter(c -> -sin(3c) > 0, required_crossings_c2) -# c2_nc = filter(c -> -sin(3c) < 0, required_crossings_c2) -# @test maximum(abs.(c1_pc .- first.(cr1p))) < 1e-5 -# @test maximum(abs.(c1_nc .- first.(cr1n))) < 1e-5 -# @test maximum(abs.(c2_pc .- first.(cr2p))) < 1e-5 -# @test maximum(abs.(c2_nc .- first.(cr2n))) < 1e-5 -# @test sign.(cos.(c1_pc .- 1e-6)) == sign.(last.(cr1p)) -# @test sign.(cos.(c1_nc .- 1e-6)) == sign.(last.(cr1n)) -# @test sign.(cos.(3 * (c2_pc .- 1e-6))) == sign.(last.(cr2p)) -# @test sign.(cos.(3 * (c2_nc .- 1e-6))) == sign.(last.(cr2n)) -# -# # with nothing neg affect (pos * neg + left root find) -# cr1p = [] -# cr2p = [] -# evt1 = ModelingToolkit.SymbolicContinuousCallback( -# [c1 ~ 0], (record_crossings, [c1 => :v], [], [], cr1p); affect_neg = nothing) -# evt2 = ModelingToolkit.SymbolicContinuousCallback( -# [c2 ~ 0], (record_crossings, [c2 => :v], [], [], cr2p); affect_neg = nothing) -# @named trigsys = ODESystem(eqs, t; continuous_events = [evt1, evt2]) -# trigsys_ss = structural_simplify(trigsys) -# prob = ODEProblem(trigsys_ss, [], (0.0, 2π)) -# sol = solve(prob, Tsit5(); dtmax = 0.01) -# @test maximum(abs.(c1_pc .- first.(cr1p))) < 1e-5 -# @test maximum(abs.(c2_pc .- first.(cr2p))) < 1e-5 -# @test sign.(cos.(c1_pc .- 1e-6)) == sign.(last.(cr1p)) -# @test sign.(cos.(3 * (c2_pc .- 1e-6))) == sign.(last.(cr2p)) -# -# #mixed -# cr1p = [] -# cr2p = [] -# cr1n = [] -# cr2n = [] -# evt1 = ModelingToolkit.SymbolicContinuousCallback( -# [c1 ~ 0], (record_crossings, [c1 => :v], [], [], cr1p); affect_neg = nothing) -# evt2 = ModelingToolkit.SymbolicContinuousCallback( -# [c2 ~ 0], (record_crossings, [c2 => :v], [], [], cr2p); -# affect_neg = (record_crossings, [c2 => :v], [], [], cr2n)) -# @named trigsys = ODESystem(eqs, t; continuous_events = [evt1, evt2]) -# trigsys_ss = structural_simplify(trigsys) -# prob = ODEProblem(trigsys_ss, [], (0.0, 2π)) -# sol = solve(prob, Tsit5(); dtmax = 0.01) -# c1_pc = filter((<=)(0) ∘ sin, required_crossings_c1) -# c2_pc = filter(c -> -sin(3c) > 0, required_crossings_c2) -# c2_nc = filter(c -> -sin(3c) < 0, required_crossings_c2) -# @test maximum(abs.(c1_pc .- first.(cr1p))) < 1e-5 -# @test maximum(abs.(c2_pc .- first.(cr2p))) < 1e-5 -# @test maximum(abs.(c2_nc .- first.(cr2n))) < 1e-5 -# @test sign.(cos.(c1_pc .- 1e-6)) == sign.(last.(cr1p)) -# @test sign.(cos.(3 * (c2_pc .- 1e-6))) == sign.(last.(cr2p)) -# @test sign.(cos.(3 * (c2_nc .- 1e-6))) == sign.(last.(cr2n)) -# -# # baseline affect w/ right rootfind (pos + neg + right root find) -# @variables c1(t)=1.0 c2(t)=1.0 # c1 = cos(t), c2 = cos(3t) -# cr1 = [] -# cr2 = [] -# evt1 = ModelingToolkit.SymbolicContinuousCallback( -# [c1 ~ 0], (record_crossings, [c1 => :v], [], [], cr1); -# rootfind = SciMLBase.RightRootFind) -# evt2 = ModelingToolkit.SymbolicContinuousCallback( -# [c2 ~ 0], (record_crossings, [c2 => :v], [], [], cr2); -# rootfind = SciMLBase.RightRootFind) -# @named trigsys = ODESystem(eqs, t; continuous_events = [evt1, evt2]) -# trigsys_ss = structural_simplify(trigsys) -# prob = ODEProblem(trigsys_ss, [], (0.0, 2π)) -# sol = solve(prob, Tsit5(); dtmax = 0.01) -# required_crossings_c1 = [π / 2, 3 * π / 2] -# required_crossings_c2 = [π / 6, π / 2, 5 * π / 6, 7 * π / 6, 3 * π / 2, 11 * π / 6] -# @test maximum(abs.(first.(cr1) .- required_crossings_c1)) < 1e-4 -# @test maximum(abs.(first.(cr2) .- required_crossings_c2)) < 1e-4 -# @test sign.(cos.(required_crossings_c1 .+ 1e-6)) == sign.(last.(cr1)) -# @test sign.(cos.(3 * (required_crossings_c2 .+ 1e-6))) == sign.(last.(cr2)) -# -# # baseline affect w/ mixed rootfind (pos + neg + right root find) -# cr1 = [] -# cr2 = [] -# evt1 = ModelingToolkit.SymbolicContinuousCallback( -# [c1 ~ 0], (record_crossings, [c1 => :v], [], [], cr1); -# rootfind = SciMLBase.LeftRootFind) -# evt2 = ModelingToolkit.SymbolicContinuousCallback( -# [c2 ~ 0], (record_crossings, [c2 => :v], [], [], cr2); -# rootfind = SciMLBase.RightRootFind) -# @named trigsys = ODESystem(eqs, t; continuous_events = [evt1, evt2]) -# trigsys_ss = structural_simplify(trigsys) -# prob = ODEProblem(trigsys_ss, [], (0.0, 2π)) -# sol = solve(prob, Tsit5()) -# @test maximum(abs.(first.(cr1) .- required_crossings_c1)) < 1e-4 -# @test maximum(abs.(first.(cr2) .- required_crossings_c2)) < 1e-4 -# @test sign.(cos.(required_crossings_c1 .- 1e-6)) == sign.(last.(cr1)) -# @test sign.(cos.(3 * (required_crossings_c2 .+ 1e-6))) == sign.(last.(cr2)) -# -# #flip order and ensure results are okay -# cr1 = [] -# cr2 = [] -# evt1 = ModelingToolkit.SymbolicContinuousCallback( -# [c1 ~ 0], (record_crossings, [c1 => :v], [], [], cr1); -# rootfind = SciMLBase.LeftRootFind) -# evt2 = ModelingToolkit.SymbolicContinuousCallback( -# [c2 ~ 0], (record_crossings, [c2 => :v], [], [], cr2); -# rootfind = SciMLBase.RightRootFind) -# @named trigsys = ODESystem(eqs, t; continuous_events = [evt2, evt1]) -# trigsys_ss = structural_simplify(trigsys) -# prob = ODEProblem(trigsys_ss, [], (0.0, 2π)) -# sol = solve(prob, Tsit5()) -# @test maximum(abs.(first.(cr1) .- required_crossings_c1)) < 1e-4 -# @test maximum(abs.(first.(cr2) .- required_crossings_c2)) < 1e-4 -# @test sign.(cos.(required_crossings_c1 .- 1e-6)) == sign.(last.(cr1)) -# @test sign.(cos.(3 * (required_crossings_c2 .+ 1e-6))) == sign.(last.(cr2)) -#end + +@testset "SDE/ODESystem Discrete Callbacks" begin + function testsol(sys, probtype, solver, u0, p, tspan; tstops = Float64[], paramtotest = nothing, + kwargs...) + prob = probtype(complete(sys), u0, tspan, p; kwargs...) + sol = solve(prob, solver(); tstops = tstops, abstol = 1e-10, reltol = 1e-10) + @test isapprox(sol(1.0000000001)[1] - sol(0.999999999)[1], 1.0; rtol = 1e-6) + paramtotest === nothing || (@test sol.ps[paramtotest] == [0., 1.]) + @test isapprox(sol(4.0)[1], 2 * exp(-2.0); rtol = 1e-6) + sol + end + + @parameters k(t) t1 t2 + @variables A(t) B(t) + + cond1 = (t == t1) + affect1 = [A ~ Pre(A) + 1] + cb1 = cond1 => affect1 + cond2 = (t == t2) + affect2 = [k ~ 1.0] + cb2 = cond2 => affect2 + cb2 = SymbolicDiscreteCallback(cb2, discrete_parameters = [k], iv = t) + + ∂ₜ = D + eqs = [∂ₜ(A) ~ -k * A] + @named osys = ODESystem(eqs, t, [A], [k, t1, t2], discrete_events = [cb1, cb2]) + @named ssys = SDESystem(eqs, [0.0], t, [A], [k, t1, t2], + discrete_events = [cb1, cb2]) + u0 = [A => 1.0] + p = [k => 0.0, t1 => 1.0, t2 => 2.0] + tspan = (0.0, 4.0) + testsol(osys, ODEProblem, Tsit5, u0, p, tspan; tstops = [1.0, 2.0], paramtotest = k) + testsol(ssys, SDEProblem, RI5, u0, p, tspan; tstops = [1.0, 2.0], paramtotest = k) + + cond1a = (t == t1) + affect1a = [A ~ Pre(A) + 1, B ~ A] + cb1a = cond1a => affect1a + @named osys1 = ODESystem(eqs, t, [A, B], [k, t1, t2], discrete_events = [cb1a, cb2]) + @named ssys1 = SDESystem(eqs, [0.0], t, [A, B], [k, t1, t2], + discrete_events = [cb1a, cb2]) + u0′ = [A => 1.0, B => 0.0] + sol = testsol(osys1, ODEProblem, Tsit5, u0′, p, tspan; tstops = [1.0, 2.0], check_length = false, paramtotest = k) + @test sol(1.0000001, idxs = B) == 2.0 + + sol = testsol(ssys1, SDEProblem, RI5, u0′, p, tspan; tstops = [1.0, 2.0], check_length = false, paramtotest = k) + @test sol(1.0000001, idxs = B) == 2.0 + + # same as above - but with set-time event syntax + cb1‵ = [1.0] => affect1 # needs to be a Vector for the event to happen only once + cb2‵ = SymbolicDiscreteCallback([2.0] => affect2, discrete_parameters = [k], iv = t) + @named osys‵ = ODESystem(eqs, t, [A], [k], discrete_events = [cb1‵, cb2‵]) + @named ssys‵ = SDESystem(eqs, [0.0], t, [A], [k], discrete_events = [cb1‵, cb2‵]) + testsol(osys‵, ODEProblem, Tsit5, u0, p, tspan; paramtotest = k) + testsol(ssys‵, SDEProblem, RI5, u0, p, tspan; paramtotest = k) + + # mixing discrete affects + @named osys3 = ODESystem(eqs, t, [A], [k, t1, t2], discrete_events = [cb1, cb2‵]) + @named ssys3 = SDESystem(eqs, [0.0], t, [A], [k, t1, t2], + discrete_events = [cb1, cb2‵]) + testsol(osys3, ODEProblem, Tsit5, u0, p, tspan; tstops = [1.0], paramtotest = k) + 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 + end + cb2‵‵ = [2.0] => (affect!, [], [k], [k], nothing) + @named osys4 = ODESystem(eqs, t, [A], [k, t1], discrete_events = [cb1, cb2‵‵]) + @named ssys4 = SDESystem(eqs, [0.0], t, [A], [k, t1], + discrete_events = [cb1, cb2‵‵]) + oprob4 = ODEProblem(complete(osys4), u0, tspan, p) + testsol(osys4, ODEProblem, Tsit5, u0, p, tspan; tstops = [1.0], paramtotest = k) + 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) + @named osys5 = ODESystem(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‵‵‵]) + testsol(osys5, ODEProblem, Tsit5, u0, p, tspan; tstops = [1.0, 2.0]) + testsol(ssys5, SDEProblem, RI5, u0, p, tspan; tstops = [1.0, 2.0]) + @named osys6 = ODESystem(eqs, t, [A], [k, t1, t2], discrete_events = [cb2‵‵‵, cb1]) + @named ssys6 = SDESystem(eqs, [0.0], t, [A], [k, t1, t2], + discrete_events = [cb2‵‵‵, cb1]) + testsol(osys6, ODEProblem, Tsit5, u0, p, tspan; tstops = [1.0, 2.0], paramtotest = k) + testsol(ssys6, SDEProblem, RI5, u0, p, tspan; tstops = [1.0, 2.0], paramtotest = k) + + # mix a continuous event too + cond3 = A ~ 0.1 + affect3 = [k ~ 0.0] + cb3 = SymbolicContinuousCallback(cond3 => affect3, discrete_parameters = [k], iv = t) + @named osys7 = ODESystem(eqs, t, [A], [k, t1, t2], discrete_events = [cb1, cb2‵‵‵], + continuous_events = [cb3]) + @named ssys7 = SDESystem(eqs, [0.0], t, [A], [k, t1, t2], + discrete_events = [cb1, cb2‵‵‵], + continuous_events = [cb3]) + + sol = testsol(osys7, ODEProblem, Tsit5, u0, p, (0.0, 10.0); tstops = [1.0, 2.0]) + @test isapprox(sol(10.0)[1], 0.1; atol = 1e-10, rtol = 1e-10) + sol = testsol(ssys7, SDEProblem, RI5, u0, p, (0.0, 10.0); tstops = [1.0, 2.0]) + @test isapprox(sol(10.0)[1], 0.1; atol = 1e-10, rtol = 1e-10) +end + +@testset "JumpSystem Discrete Callbacks" begin + function testsol(jsys, u0, p, tspan; tstops = Float64[], paramtotest = nothing, + N = 40000, kwargs...) + jsys = complete(jsys) + dprob = DiscreteProblem(jsys, u0, tspan, p) + jprob = JumpProblem(jsys, dprob, Direct(); kwargs...) + sol = solve(jprob, SSAStepper(); tstops = tstops) + @test (sol(1.000000000001)[1] - sol(0.99999999999)[1]) == 1 + paramtotest === nothing || (@test sol.ps[paramtotest] == [0., 1.0]) + @test sol(40.0)[1] == 0 + sol + end + + @parameters k(t) t1 t2 + @variables A(t) B(t) + + eqs = [MassActionJump(k, [A => 1], [A => -1])] + cond1 = (t == t1) + affect1 = [A ~ Pre(A) + 1] + cb1 = cond1 => affect1 + cond2 = (t == t2) + affect2 = [k ~ 1.0] + cb2 = cond2 => affect2 + cb2 = SymbolicDiscreteCallback(cb2, discrete_parameters = [k], iv = t) + + @named jsys = JumpSystem(eqs, t, [A], [k, t1, t2], discrete_events = [cb1, cb2]) + u0 = [A => 1] + p = [k => 0.0, t1 => 1.0, t2 => 2.0] + tspan = (0.0, 40.0) + testsol(jsys, u0, p, tspan; tstops = [1.0, 2.0], rng, paramtotest = k) + + cond1a = (t == t1) + affect1a = [A ~ Pre(A) + 1, B ~ A] + cb1a = cond1a => affect1a + @named jsys1 = JumpSystem(eqs, t, [A, B], [k, t1, t2], discrete_events = [cb1a, cb2]) + u0′ = [A => 1, B => 0] + sol = testsol(jsys1, u0′, p, tspan; tstops = [1.0, 2.0], + check_length = false, rng, paramtotest = k) + @test sol(1.000000001, idxs = B) == 2 + + # same as above - but with set-time event syntax + cb1‵ = [1.0] => affect1 # needs to be a Vector for the event to happen only once + cb2‵ = SymbolicDiscreteCallback([2.0] => affect2, discrete_parameters = [k], iv = t) + @named jsys‵ = JumpSystem(eqs, t, [A], [k], discrete_events = [cb1‵, cb2‵]) + testsol(jsys‵, u0, [p[1]], tspan; rng, paramtotest = k) + + # mixing discrete affects + @named jsys3 = JumpSystem(eqs, t, [A], [k, t1, t2], discrete_events = [cb1, cb2‵]) + 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 + end + cb2‵‵ = [2.0] => (affect!, [], [k], [k], nothing) + @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) + @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]) + testsol(jsys6, u0, p, tspan; tstops = [1.0, 2.0], rng, paramtotest = k) +end + +@testset "Namespacing" begin + function oscillator_ce(k = 1.0; name) + sts = @variables x(t)=1.0 v(t)=0.0 F(t) + ps = @parameters k=k Θ=0.5 + eqs = [D(x) ~ v, D(v) ~ -k * x + F] + ev = [x ~ Θ] => [x ~ 1.0, v ~ 0.0] + ODESystem(eqs, t, sts, ps, continuous_events = [ev]; name) + end + + @named oscce = oscillator_ce() + eqs = [oscce.F ~ 0] + @named eqs_sys = ODESystem(eqs, t) + @named oneosc_ce = compose(eqs_sys, oscce) + oneosc_ce_simpl = structural_simplify(oneosc_ce) + + prob = ODEProblem(oneosc_ce_simpl, [], (0.0, 2.0), []) + sol = solve(prob, Tsit5(), saveat = 0.1) + + @test typeof(oneosc_ce_simpl) == ODESystem + @test sol[1, 6] < 1.0 # test whether x(t) decreases over time + @test sol[1, 18] > 0.5 # test whether event happened +end + +@testset "Additional SymbolicContinuousCallback options" begin + # 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]) + cr1 = [] + cr2 = [] + evt1 = ModelingToolkit.SymbolicContinuousCallback( + [c1 ~ 0], (record_crossings, [c1 => :v], [], [], cr1)) + evt2 = ModelingToolkit.SymbolicContinuousCallback( + [c2 ~ 0], (record_crossings, [c2 => :v], [], [], cr2)) + @named trigsys = ODESystem(eqs, t; continuous_events = [evt1, evt2]) + trigsys_ss = structural_simplify(trigsys) + prob = ODEProblem(trigsys_ss, [], (0.0, 2π)) + sol = solve(prob, Tsit5()) + required_crossings_c1 = [π / 2, 3 * π / 2] + required_crossings_c2 = [π / 6, π / 2, 5 * π / 6, 7 * π / 6, 3 * π / 2, 11 * π / 6] + @test maximum(abs.(first.(cr1) .- required_crossings_c1)) < 1e-4 + @test maximum(abs.(first.(cr2) .- required_crossings_c2)) < 1e-4 + @test sign.(cos.(required_crossings_c1 .- 1e-6)) == sign.(last.(cr1)) + @test sign.(cos.(3 * (required_crossings_c2 .- 1e-6))) == sign.(last.(cr2)) + + # with neg affect (pos * neg + left root find) + cr1p = [] + cr2p = [] + cr1n = [] + cr2n = [] + evt1 = ModelingToolkit.SymbolicContinuousCallback( + [c1 ~ 0], (record_crossings, [c1 => :v], [], [], cr1p); + affect_neg = (record_crossings, [c1 => :v], [], [], cr1n)) + evt2 = ModelingToolkit.SymbolicContinuousCallback( + [c2 ~ 0], (record_crossings, [c2 => :v], [], [], cr2p); + affect_neg = (record_crossings, [c2 => :v], [], [], cr2n)) + @named trigsys = ODESystem(eqs, t; continuous_events = [evt1, evt2]) + trigsys_ss = structural_simplify(trigsys) + prob = ODEProblem(trigsys_ss, [], (0.0, 2π)) + sol = solve(prob, Tsit5(); dtmax = 0.01) + c1_pc = filter((<=)(0) ∘ sin, required_crossings_c1) + c1_nc = filter((>=)(0) ∘ sin, required_crossings_c1) + c2_pc = filter(c -> -sin(3c) > 0, required_crossings_c2) + c2_nc = filter(c -> -sin(3c) < 0, required_crossings_c2) + @test maximum(abs.(c1_pc .- first.(cr1p))) < 1e-5 + @test maximum(abs.(c1_nc .- first.(cr1n))) < 1e-5 + @test maximum(abs.(c2_pc .- first.(cr2p))) < 1e-5 + @test maximum(abs.(c2_nc .- first.(cr2n))) < 1e-5 + @test sign.(cos.(c1_pc .- 1e-6)) == sign.(last.(cr1p)) + @test sign.(cos.(c1_nc .- 1e-6)) == sign.(last.(cr1n)) + @test sign.(cos.(3 * (c2_pc .- 1e-6))) == sign.(last.(cr2p)) + @test sign.(cos.(3 * (c2_nc .- 1e-6))) == sign.(last.(cr2n)) + + # with nothing neg affect (pos * neg + left root find) + cr1p = [] + cr2p = [] + evt1 = ModelingToolkit.SymbolicContinuousCallback( + [c1 ~ 0], (record_crossings, [c1 => :v], [], [], cr1p); affect_neg = nothing) + evt2 = ModelingToolkit.SymbolicContinuousCallback( + [c2 ~ 0], (record_crossings, [c2 => :v], [], [], cr2p); affect_neg = nothing) + @named trigsys = ODESystem(eqs, t; continuous_events = [evt1, evt2]) + trigsys_ss = structural_simplify(trigsys) + prob = ODEProblem(trigsys_ss, [], (0.0, 2π)) + sol = solve(prob, Tsit5(); dtmax = 0.01) + @test maximum(abs.(c1_pc .- first.(cr1p))) < 1e-5 + @test maximum(abs.(c2_pc .- first.(cr2p))) < 1e-5 + @test sign.(cos.(c1_pc .- 1e-6)) == sign.(last.(cr1p)) + @test sign.(cos.(3 * (c2_pc .- 1e-6))) == sign.(last.(cr2p)) + + #mixed + cr1p = [] + cr2p = [] + cr1n = [] + cr2n = [] + evt1 = ModelingToolkit.SymbolicContinuousCallback( + [c1 ~ 0], (record_crossings, [c1 => :v], [], [], cr1p); affect_neg = nothing) + evt2 = ModelingToolkit.SymbolicContinuousCallback( + [c2 ~ 0], (record_crossings, [c2 => :v], [], [], cr2p); + affect_neg = (record_crossings, [c2 => :v], [], [], cr2n)) + @named trigsys = ODESystem(eqs, t; continuous_events = [evt1, evt2]) + trigsys_ss = structural_simplify(trigsys) + prob = ODEProblem(trigsys_ss, [], (0.0, 2π)) + sol = solve(prob, Tsit5(); dtmax = 0.01) + c1_pc = filter((<=)(0) ∘ sin, required_crossings_c1) + c2_pc = filter(c -> -sin(3c) > 0, required_crossings_c2) + c2_nc = filter(c -> -sin(3c) < 0, required_crossings_c2) + @test maximum(abs.(c1_pc .- first.(cr1p))) < 1e-5 + @test maximum(abs.(c2_pc .- first.(cr2p))) < 1e-5 + @test maximum(abs.(c2_nc .- first.(cr2n))) < 1e-5 + @test sign.(cos.(c1_pc .- 1e-6)) == sign.(last.(cr1p)) + @test sign.(cos.(3 * (c2_pc .- 1e-6))) == sign.(last.(cr2p)) + @test sign.(cos.(3 * (c2_nc .- 1e-6))) == sign.(last.(cr2n)) + + # baseline affect w/ right rootfind (pos + neg + right root find) + @variables c1(t)=1.0 c2(t)=1.0 # c1 = cos(t), c2 = cos(3t) + cr1 = [] + cr2 = [] + evt1 = ModelingToolkit.SymbolicContinuousCallback( + [c1 ~ 0], (record_crossings, [c1 => :v], [], [], cr1); + rootfind = SciMLBase.RightRootFind) + evt2 = ModelingToolkit.SymbolicContinuousCallback( + [c2 ~ 0], (record_crossings, [c2 => :v], [], [], cr2); + rootfind = SciMLBase.RightRootFind) + @named trigsys = ODESystem(eqs, t; continuous_events = [evt1, evt2]) + trigsys_ss = structural_simplify(trigsys) + prob = ODEProblem(trigsys_ss, [], (0.0, 2π)) + sol = solve(prob, Tsit5(); dtmax = 0.01) + required_crossings_c1 = [π / 2, 3 * π / 2] + required_crossings_c2 = [π / 6, π / 2, 5 * π / 6, 7 * π / 6, 3 * π / 2, 11 * π / 6] + @test maximum(abs.(first.(cr1) .- required_crossings_c1)) < 1e-4 + @test maximum(abs.(first.(cr2) .- required_crossings_c2)) < 1e-4 + @test sign.(cos.(required_crossings_c1 .+ 1e-6)) == sign.(last.(cr1)) + @test sign.(cos.(3 * (required_crossings_c2 .+ 1e-6))) == sign.(last.(cr2)) + + # baseline affect w/ mixed rootfind (pos + neg + right root find) + cr1 = [] + cr2 = [] + evt1 = ModelingToolkit.SymbolicContinuousCallback( + [c1 ~ 0], (record_crossings, [c1 => :v], [], [], cr1); + rootfind = SciMLBase.LeftRootFind) + evt2 = ModelingToolkit.SymbolicContinuousCallback( + [c2 ~ 0], (record_crossings, [c2 => :v], [], [], cr2); + rootfind = SciMLBase.RightRootFind) + @named trigsys = ODESystem(eqs, t; continuous_events = [evt1, evt2]) + trigsys_ss = structural_simplify(trigsys) + prob = ODEProblem(trigsys_ss, [], (0.0, 2π)) + sol = solve(prob, Tsit5()) + @test maximum(abs.(first.(cr1) .- required_crossings_c1)) < 1e-4 + @test maximum(abs.(first.(cr2) .- required_crossings_c2)) < 1e-4 + @test sign.(cos.(required_crossings_c1 .- 1e-6)) == sign.(last.(cr1)) + @test sign.(cos.(3 * (required_crossings_c2 .+ 1e-6))) == sign.(last.(cr2)) + + #flip order and ensure results are okay + cr1 = [] + cr2 = [] + evt1 = ModelingToolkit.SymbolicContinuousCallback( + [c1 ~ 0], (record_crossings, [c1 => :v], [], [], cr1); + rootfind = SciMLBase.LeftRootFind) + evt2 = ModelingToolkit.SymbolicContinuousCallback( + [c2 ~ 0], (record_crossings, [c2 => :v], [], [], cr2); + rootfind = SciMLBase.RightRootFind) + @named trigsys = ODESystem(eqs, t; continuous_events = [evt2, evt1]) + trigsys_ss = structural_simplify(trigsys) + prob = ODEProblem(trigsys_ss, [], (0.0, 2π)) + sol = solve(prob, Tsit5()) + @test maximum(abs.(first.(cr1) .- required_crossings_c1)) < 1e-4 + @test maximum(abs.(first.(cr2) .- required_crossings_c2)) < 1e-4 + @test sign.(cos.(required_crossings_c1 .- 1e-6)) == sign.(last.(cr1)) + @test sign.(cos.(3 * (required_crossings_c2 .+ 1e-6))) == sign.(last.(cr2)) +end # #@testset "Discrete event reinitialization (#3142)" begin # @connector LiquidPort begin @@ -1326,61 +1276,63 @@ end end @testset "Implicit affects with Pre" begin + using ModelingToolkit: UnsolvableCallbackError @parameters g @variables x(t) y(t) λ(t) eqs = [D(D(x)) ~ λ * x D(D(y)) ~ λ * y - g x^2 + y^2 ~ 1] - c_evt = [t ~ 0.5] => [x ~ Pre(x) + 0.1] + c_evt = [t ~ 5.] => [x ~ Pre(x) + 0.1] @mtkbuild pend = ODESystem(eqs, t, continuous_events = c_evt) - prob = ODEProblem(pend, [x => 1, y => 0], (0., 1.), [g => 1], guesses = [λ => 1]) - sol = solve(prob, Rodas5()) - @test sol(0.5000001)[1] - sol(0.4999999)[1] ≈ 0.1 - @test sol(0.5000001)[1]^2 + sol(0.5000001)[2]^2 ≈ 1 + prob = ODEProblem(pend, [x => -1, y => 0], (0., 10.), [g => 1], guesses = [λ => 1]) + sol = solve(prob, FBDF()) + @test ≈(sol(5.000001, idxs = x) - sol(4.999999, idxs = x), 0.1, rtol = 1e-4) + @test ≈(sol(5.000001, idxs = x)^2 + sol(5.000001, idxs = y)^2, 1, rtol = 1e-4) # Implicit affect with Pre - c_evt = [t ~ 0.5] => [x ~ Pre(x) + y^2] + c_evt = [t ~ 5.] => [x ~ Pre(x) + y^2] @mtkbuild pend = ODESystem(eqs, t, continuous_events = c_evt) - prob = ODEProblem(pend, [x => 1, y => 0], (0., 1.), [g => 1], guesses = [λ => 1]) - sol = solve(prob, Rodas5()) - @test sol(0.5000001)[2]^2 - sol(0.4999999)[1] ≈ sol(0.5000001)[1] - @test sol(0.5000001)[1]^2 + sol(0.5000001)[2]^2 ≈ 1 + prob = ODEProblem(pend, [x => 1, y => 0], (0., 10.), [g => 1], guesses = [λ => 1]) + sol = solve(prob, FBDF()) + @test ≈(sol(5.000001, idxs = y)^2 + sol(4.999999, idxs = x), sol(5.000001, idxs = x), rtol = 1e-4) + @test ≈(sol(5.000001, idxs = x)^2 + sol(5.000001, idxs = y)^2, 1, rtol = 1e-4) # Impossible affect errors - c_evt = [t ~ 0.5] => [x ~ Pre(x) + 1] + c_evt = [t ~ 5.] => [x ~ Pre(x) + 2] @mtkbuild pend = ODESystem(eqs, t, continuous_events = c_evt) - prob = ODEProblem(pend, [x => 1, y => 0], (0., 1.), [g => 1], guesses = [λ => 1]) - @test_throws Exception sol = solve(prob, Rodas5()) + prob = ODEProblem(pend, [x => 1, y => 0], (0., 10.), [g => 1], guesses = [λ => 1]) + @test_throws UnsolvableCallbackError sol = solve(prob, FBDF()) # Changing both variables and parameters in the same affect. - c_evt = [t ~ 0.5] => [x ~ Pre(x) + 1, g ~ Pre(g) + 1] + @parameters g(t) + eqs = [D(D(x)) ~ λ * x + D(D(y)) ~ λ * y - g + x^2 + y^2 ~ 1] + c_evt = SymbolicContinuousCallback([t ~ 5.0], [x ~ Pre(x) + 0.1, g ~ Pre(g) + 1], discrete_parameters = [g], iv = t) @mtkbuild pend = ODESystem(eqs, t, continuous_events = c_evt) - prob = ODEProblem(pend, [x => 1, y => 0], (0., 1.), [g => 1], guesses = [λ => 1]) - sol = solve(prob, Rodas5()) - @test sol.ps[g] ≈ 2 - @test sol(0.5000001, idxs = x) - sol(0.4999999, idxs = x) ≈ 1 + prob = ODEProblem(pend, [x => 1, y => 0], (0., 10.), [g => 1], guesses = [λ => 1]) + sol = solve(prob, FBDF()) + @test sol.ps[g] ≈ [1, 2] + @test ≈(sol(5.0000001, idxs = x) - sol(4.999999, idxs = x), .1, rtol = 1e-4) # Proper re-initialization after parameter change - eqs = [x ~ g^2 - y, D(x) ~ x] - c_evt = [t ~ 0.5] => [x ~ Pre(x) + 1, g ~ Pre(g) + 1] + eqs = [y ~ g^2 - x, D(x) ~ x] + c_evt = SymbolicContinuousCallback([t ~ 5.0], [x ~ Pre(x) + 1, g ~ Pre(g) + 1], discrete_parameters = [g], iv = t) @mtkbuild sys = ODESystem(eqs, t, continuous_events = c_evt) - prob = ODEProblem(sys, [x => 0.5], (0., 1.), [g => 2], guesses = [y => 0]) - sol = solve(prob, Rodas5()) - @test sol.ps[g] ≈ 3 - @test ≈(sol(0.5000001)[1] - sol(0.4999999)[1], 1; atol = 1e-6) - @test sol(0.5000001, idxs = y) ≈ 9 - sol(0.5000001, idxs = x) + prob = ODEProblem(sys, [x => 1.0], (0., 10.), [g => 2], guesses = [y => 0.]) + sol = solve(prob, FBDF()) + @test sol.ps[g] ≈ [2., 3.] + @test ≈(sol(5.00000001, idxs = x) - sol(4.9999999, idxs = x), 1; rtol = 1e-4) + @test ≈(sol(5.00000001, idxs = y), 9 - sol(5.00000001, idxs = x), rtol = 1e-4) # Parameters that don't appear in affects should not be mutated. - c_evt = [t ~ 0.5] => [x ~ Pre(x) + 1] + c_evt = [t ~ 5.0] => [x ~ Pre(x) + 1] @mtkbuild sys = ODESystem(eqs, t, continuous_events = c_evt) - prob = ODEProblem(sys, [x => 0.5], (0., 1.), [g => 2], guesses = [y => 0]) - sol = solve(prob, Rodas5()) + prob = ODEProblem(sys, [x => 0.5], (0., 10.), [g => 2], guesses = [y => 0]) + sol = solve(prob, FBDF()) @test prob.ps[g] == sol.ps[g] end - - # TODO: test: # - Functional affects reinitialize correctly # - explicit equation of t in a functional affect -# - affects that have Pre but are also algebraic in nature # - reinitialization after affects From 8625744b601b963eb2216ddd7e4541e84ce93d96 Mon Sep 17 00:00:00 2001 From: vyudu Date: Wed, 26 Mar 2025 14:07:31 -0400 Subject: [PATCH 055/122] feat: add discrete_parameters --- src/systems/callbacks.jl | 5 +- test/symbolic_events.jl | 916 +++++++++++++++++++-------------------- 2 files changed, 462 insertions(+), 459 deletions(-) diff --git a/src/systems/callbacks.jl b/src/systems/callbacks.jl index 5a97472c6d..28a617678d 100644 --- a/src/systems/callbacks.jl +++ b/src/systems/callbacks.jl @@ -241,7 +241,10 @@ make_affect(affect::Affect; kwargs...) = affect function make_affect(affect::Vector{Equation}; discrete_parameters::AbstractVector = Any[], iv = nothing, algeeqs::Vector{Equation} = Equation[]) isempty(affect) && return nothing isempty(algeeqs) && @warn "No algebraic equations were found for the callback defined by $(join(affect, ", ")). If the system has no algebraic equations, this can be disregarded. Otherwise pass in `algeeqs` to the SymbolicContinuousCallback constructor." - isnothing(iv) && error("Must specify iv.") + if isnothing(iv) + iv = t_nounits + @warn "No independent variable specified. Defaulting to t_nounits." + end for p in discrete_parameters occursin(unwrap(iv), unwrap(p)) || error("Non-time dependent parameter $p passed in as a discrete. Must be declared as @parameters $p(t).") diff --git a/test/symbolic_events.jl b/test/symbolic_events.jl index bc58f78900..e6c1df4363 100644 --- a/test/symbolic_events.jl +++ b/test/symbolic_events.jl @@ -20,463 +20,463 @@ eqs = [D(x) ~ 1] affect = [x ~ 0] affect_neg = [x ~ 1] -#@testset "SymbolicContinuousCallback constructors" begin -# e = SymbolicContinuousCallback(eqs[]) -# @test e isa SymbolicContinuousCallback -# @test isequal(equations(e), eqs) -# @test e.affect == nothing -# @test e.affect_neg == nothing -# @test e.rootfind == SciMLBase.LeftRootFind -# -# e = SymbolicContinuousCallback(eqs) -# @test e isa SymbolicContinuousCallback -# @test isequal(equations(e), eqs) -# @test e.affect == nothing -# @test e.affect_neg == nothing -# @test e.rootfind == SciMLBase.LeftRootFind -# -# e = SymbolicContinuousCallback(eqs, nothing) -# @test e isa SymbolicContinuousCallback -# @test isequal(equations(e), eqs) -# @test e.affect == nothing -# @test e.affect_neg == nothing -# @test e.rootfind == SciMLBase.LeftRootFind -# -# e = SymbolicContinuousCallback(eqs[], nothing) -# @test e isa SymbolicContinuousCallback -# @test isequal(equations(e), eqs) -# @test e.affect == nothing -# @test e.affect_neg == nothing -# @test e.rootfind == SciMLBase.LeftRootFind -# -# e = SymbolicContinuousCallback(eqs => nothing) -# @test e isa SymbolicContinuousCallback -# @test isequal(equations(e), eqs) -# @test e.affect == nothing -# @test e.affect_neg == nothing -# @test e.rootfind == SciMLBase.LeftRootFind -# -# e = SymbolicContinuousCallback(eqs[] => nothing) -# @test e isa SymbolicContinuousCallback -# @test isequal(equations(e), eqs) -# @test e.affect == nothing -# @test e.affect_neg == nothing -# @test e.rootfind == SciMLBase.LeftRootFind -# -# ## With affect -# e = SymbolicContinuousCallback(eqs[], affect) -# @test e isa SymbolicContinuousCallback -# @test isequal(equations(e), eqs) -# @test e.rootfind == SciMLBase.LeftRootFind -# -# # with only positive edge affect -# e = SymbolicContinuousCallback(eqs[], affect, affect_neg = nothing) -# @test e isa SymbolicContinuousCallback -# @test isequal(equations(e), eqs) -# @test isnothing(e.affect_neg) -# @test e.rootfind == SciMLBase.LeftRootFind -# -# # with explicit edge affects -# e = SymbolicContinuousCallback(eqs[], affect, affect_neg = affect_neg) -# @test e isa SymbolicContinuousCallback -# @test isequal(equations(e), eqs) -# @test e.rootfind == SciMLBase.LeftRootFind -# -# # with different root finding ops -# e = SymbolicContinuousCallback( -# eqs[], affect, affect_neg = affect_neg, rootfind = SciMLBase.LeftRootFind) -# @test e isa SymbolicContinuousCallback -# @test isequal(equations(e), eqs) -# @test e.rootfind == SciMLBase.LeftRootFind -# -# # test plural constructor -# e = SymbolicContinuousCallbacks(eqs[]) -# @test e isa Vector{SymbolicContinuousCallback} -# @test isequal(equations(e[]), eqs) -# @test e[].affect == nothing -# -# e = SymbolicContinuousCallbacks(eqs) -# @test e isa Vector{SymbolicContinuousCallback} -# @test isequal(equations(e[]), eqs) -# @test e[].affect == nothing -# -# e = SymbolicContinuousCallbacks(eqs[] => affect) -# @test e isa Vector{SymbolicContinuousCallback} -# @test isequal(equations(e[]), eqs) -# @test e[].affect isa AffectSystem -# -# e = SymbolicContinuousCallbacks(eqs => affect) -# @test e isa Vector{SymbolicContinuousCallback} -# @test isequal(equations(e[]), eqs) -# @test e[].affect isa AffectSystem -# -# e = SymbolicContinuousCallbacks([eqs[] => affect]) -# @test e isa Vector{SymbolicContinuousCallback} -# @test isequal(equations(e[]), eqs) -# @test e[].affect isa AffectSystem -# -# e = SymbolicContinuousCallbacks([eqs => affect]) -# @test e isa Vector{SymbolicContinuousCallback} -# @test isequal(equations(e[]), eqs) -# @test e[].affect isa AffectSystem -#end -# -#@testset "ImperativeAffect constructors" begin -# fmfa(o, x, i, c) = nothing -# m = ModelingToolkit.ImperativeAffect(fmfa) -# @test m isa ModelingToolkit.ImperativeAffect -# @test m.f == fmfa -# @test m.obs == [] -# @test m.obs_syms == [] -# @test m.modified == [] -# @test m.mod_syms == [] -# @test m.ctx === nothing -# -# m = ModelingToolkit.ImperativeAffect(fmfa, (;)) -# @test m isa ModelingToolkit.ImperativeAffect -# @test m.f == fmfa -# @test m.obs == [] -# @test m.obs_syms == [] -# @test m.modified == [] -# @test m.mod_syms == [] -# @test m.ctx === nothing -# -# m = ModelingToolkit.ImperativeAffect(fmfa, (; x)) -# @test m isa ModelingToolkit.ImperativeAffect -# @test m.f == fmfa -# @test isequal(m.obs, []) -# @test m.obs_syms == [] -# @test isequal(m.modified, [x]) -# @test m.mod_syms == [:x] -# @test m.ctx === nothing -# -# m = ModelingToolkit.ImperativeAffect(fmfa, (; y = x)) -# @test m isa ModelingToolkit.ImperativeAffect -# @test m.f == fmfa -# @test isequal(m.obs, []) -# @test m.obs_syms == [] -# @test isequal(m.modified, [x]) -# @test m.mod_syms == [:y] -# @test m.ctx === nothing -# -# m = ModelingToolkit.ImperativeAffect(fmfa; observed = (; y = x)) -# @test m isa ModelingToolkit.ImperativeAffect -# @test m.f == fmfa -# @test isequal(m.obs, [x]) -# @test m.obs_syms == [:y] -# @test m.modified == [] -# @test m.mod_syms == [] -# @test m.ctx === nothing -# -# m = ModelingToolkit.ImperativeAffect(fmfa; modified = (; x)) -# @test m isa ModelingToolkit.ImperativeAffect -# @test m.f == fmfa -# @test isequal(m.obs, []) -# @test m.obs_syms == [] -# @test isequal(m.modified, [x]) -# @test m.mod_syms == [:x] -# @test m.ctx === nothing -# -# m = ModelingToolkit.ImperativeAffect(fmfa; modified = (; y = x)) -# @test m isa ModelingToolkit.ImperativeAffect -# @test m.f == fmfa -# @test isequal(m.obs, []) -# @test m.obs_syms == [] -# @test isequal(m.modified, [x]) -# @test m.mod_syms == [:y] -# @test m.ctx === nothing -# -# m = ModelingToolkit.ImperativeAffect(fmfa, (; x), (; x)) -# @test m isa ModelingToolkit.ImperativeAffect -# @test m.f == fmfa -# @test isequal(m.obs, [x]) -# @test m.obs_syms == [:x] -# @test isequal(m.modified, [x]) -# @test m.mod_syms == [:x] -# @test m.ctx === nothing -# -# m = ModelingToolkit.ImperativeAffect(fmfa, (; y = x), (; y = x)) -# @test m isa ModelingToolkit.ImperativeAffect -# @test m.f == fmfa -# @test isequal(m.obs, [x]) -# @test m.obs_syms == [:y] -# @test isequal(m.modified, [x]) -# @test m.mod_syms == [:y] -# @test m.ctx === nothing -# -# m = ModelingToolkit.ImperativeAffect( -# fmfa; modified = (; y = x), observed = (; y = x)) -# @test m isa ModelingToolkit.ImperativeAffect -# @test m.f == fmfa -# @test isequal(m.obs, [x]) -# @test m.obs_syms == [:y] -# @test isequal(m.modified, [x]) -# @test m.mod_syms == [:y] -# @test m.ctx === nothing -# -# m = ModelingToolkit.ImperativeAffect( -# fmfa; modified = (; y = x), observed = (; y = x), ctx = 3) -# @test m isa ModelingToolkit.ImperativeAffect -# @test m.f == fmfa -# @test isequal(m.obs, [x]) -# @test m.obs_syms == [:y] -# @test isequal(m.modified, [x]) -# @test m.mod_syms == [:y] -# @test m.ctx === 3 -# -# m = ModelingToolkit.ImperativeAffect(fmfa, (; x), (; x), 3) -# @test m isa ModelingToolkit.ImperativeAffect -# @test m.f == fmfa -# @test isequal(m.obs, [x]) -# @test m.obs_syms == [:x] -# @test isequal(m.modified, [x]) -# @test m.mod_syms == [:x] -# @test m.ctx === 3 -#end -# -#@testset "Condition Compilation" begin -# @named sys = ODESystem(eqs, t, continuous_events = [x ~ 1]) -# @test getfield(sys, :continuous_events)[] == -# SymbolicContinuousCallback(Equation[x ~ 1], nothing) -# @test isequal(equations(getfield(sys, :continuous_events))[], x ~ 1) -# fsys = flatten(sys) -# @test isequal(equations(getfield(fsys, :continuous_events))[], x ~ 1) -# -# @named sys2 = ODESystem([D(x) ~ 1], t, continuous_events = [x ~ 2], systems = [sys]) -# @test getfield(sys2, :continuous_events)[] == -# SymbolicContinuousCallback(Equation[x ~ 2], nothing) -# @test all(ModelingToolkit.continuous_events(sys2) .== [ -# SymbolicContinuousCallback(Equation[x ~ 2], nothing), -# SymbolicContinuousCallback(Equation[sys.x ~ 1], nothing) -# ]) -# -# @test isequal(equations(getfield(sys2, :continuous_events))[1], x ~ 2) -# @test length(ModelingToolkit.continuous_events(sys2)) == 2 -# @test isequal(equations(ModelingToolkit.continuous_events(sys2)[1])[], x ~ 2) -# @test isequal(equations(ModelingToolkit.continuous_events(sys2)[2])[], sys.x ~ 1) -# -# sys = complete(sys) -# sys_nosplit = complete(sys; split = false) -# sys2 = complete(sys2) -# -# # Test proper rootfinding -# prob = ODEProblem(sys, Pair[], (0.0, 2.0)) -# p0 = 0 -# t0 = 0 -# @test get_callback(prob) isa ModelingToolkit.DiffEqCallbacks.ContinuousCallback -# cb = ModelingToolkit.generate_continuous_callbacks(sys) -# cond = cb.condition -# out = [0.0] -# cond.f_iip(out, [0], p0, t0) -# @test out[] ≈ -1 # signature is u,p,t -# cond.f_iip(out, [1], p0, t0) -# @test out[] ≈ 0 # signature is u,p,t -# cond.f_iip(out, [2], p0, t0) -# @test out[] ≈ 1 # signature is u,p,t -# -# prob = ODEProblem(sys, Pair[], (0.0, 2.0)) -# prob_nosplit = ODEProblem(sys_nosplit, Pair[], (0.0, 2.0)) -# sol = solve(prob, Tsit5()) -# sol_nosplit = solve(prob_nosplit, Tsit5()) -# @test minimum(t -> abs(t - 1), sol.t) < 1e-10 # test that the solver stepped at the root -# @test minimum(t -> abs(t - 1), sol_nosplit.t) < 1e-10 # test that the solver stepped at the root -# -# # Test user-provided callback is respected -# test_callback = DiscreteCallback(x -> x, x -> x) -# prob = ODEProblem(sys, Pair[], (0.0, 2.0), callback = test_callback) -# prob_nosplit = ODEProblem(sys_nosplit, Pair[], (0.0, 2.0), callback = test_callback) -# cbs = get_callback(prob) -# cbs_nosplit = get_callback(prob_nosplit) -# @test cbs isa CallbackSet -# @test cbs.discrete_callbacks[1] == test_callback -# @test cbs_nosplit isa CallbackSet -# @test cbs_nosplit.discrete_callbacks[1] == test_callback -# -# prob = ODEProblem(sys2, Pair[], (0.0, 3.0)) -# cb = get_callback(prob) -# @test cb isa ModelingToolkit.DiffEqCallbacks.VectorContinuousCallback -# -# cond = cb.condition -# out = [0.0, 0.0] -# # the root to find is 2 -# cond.f_iip(out, [0, 0], p0, t0) -# @test out[1] ≈ -2 # signature is u,p,t -# cond.f_iip(out, [1, 0], p0, t0) -# @test out[1] ≈ -1 # signature is u,p,t -# cond.f_iip(out, [2, 0], p0, t0) # this should return 0 -# @test out[1] ≈ 0 # signature is u,p,t -# -# # the root to find is 1 -# out = [0.0, 0.0] -# cond.f_iip(out, [0, 0], p0, t0) -# @test out[2] ≈ -1 # signature is u,p,t -# cond.f_iip(out, [0, 1], p0, t0) # this should return 0 -# @test out[2] ≈ 0 # signature is u,p,t -# cond.f_iip(out, [0, 2], p0, t0) -# @test out[2] ≈ 1 # signature is u,p,t -# -# sol = solve(prob, Tsit5()) -# @test minimum(t -> abs(t - 1), sol.t) < 1e-10 # test that the solver stepped at the first root -# @test minimum(t -> abs(t - 2), sol.t) < 1e-10 # test that the solver stepped at the second root -# -# @named sys = ODESystem(eqs, t, continuous_events = [x ~ 1, x ~ 2]) # two root eqs using the same unknown -# sys = complete(sys) -# prob = ODEProblem(sys, Pair[], (0.0, 3.0)) -# @test get_callback(prob) isa ModelingToolkit.DiffEqCallbacks.VectorContinuousCallback -# sol = solve(prob, Tsit5()) -# @test minimum(t -> abs(t - 1), sol.t) < 1e-10 # test that the solver stepped at the first root -# @test minimum(t -> abs(t - 2), sol.t) < 1e-10 # test that the solver stepped at the second root -#end -# -#@testset "Bouncing Ball" begin -# ###### 1D Bounce -# @variables x(t)=1 v(t)=0 -# -# root_eqs = [x ~ 0] -# affect = [v ~ -Pre(v)] -# -# @named ball = ODESystem( -# [D(x) ~ v -# D(v) ~ -9.8], t, continuous_events = root_eqs => affect) -# -# @test only(continuous_events(ball)) == -# SymbolicContinuousCallback(Equation[x ~ 0], Equation[v ~ -Pre(v)]) -# ball = structural_simplify(ball) -# -# @test length(ModelingToolkit.continuous_events(ball)) == 1 -# -# tspan = (0.0, 5.0) -# prob = ODEProblem(ball, Pair[], tspan) -# sol = solve(prob, Tsit5()) -# @test 0 <= minimum(sol[x]) <= 1e-10 # the ball never went through the floor but got very close -# -# ###### 2D bouncing ball -# @variables x(t)=1 y(t)=0 vx(t)=0 vy(t)=1 -# -# events = [[x ~ 0] => [vx ~ -Pre(vx)] -# [y ~ -1.5, y ~ 1.5] => [vy ~ -Pre(vy)]] -# -# @named ball = ODESystem( -# [D(x) ~ vx -# D(y) ~ vy -# D(vx) ~ -9.8 -# D(vy) ~ -0.01vy], t; continuous_events = events) -# -# _ball = ball -# ball = structural_simplify(_ball) -# ball_nosplit = structural_simplify(_ball; split = false) -# -# tspan = (0.0, 5.0) -# prob = ODEProblem(ball, Pair[], tspan) -# prob_nosplit = ODEProblem(ball_nosplit, Pair[], tspan) -# -# cb = get_callback(prob) -# @test cb isa ModelingToolkit.DiffEqCallbacks.VectorContinuousCallback -# @test getfield(ball, :continuous_events)[1] == -# SymbolicContinuousCallback(Equation[x ~ 0], Equation[vx ~ -Pre(vx)]) -# @test getfield(ball, :continuous_events)[2] == -# SymbolicContinuousCallback(Equation[y ~ -1.5, y ~ 1.5], Equation[vy ~ -Pre(vy)]) -# cond = cb.condition -# out = [0.0, 0.0, 0.0] -# p0 = 0. -# t0 = 0. -# cond.f_iip(out, [0, 0, 0, 0], p0, t0) -# @test out ≈ [0, 1.5, -1.5] -# -# sol = solve(prob, Tsit5()) -# sol_nosplit = solve(prob_nosplit, Tsit5()) -# @test 0 <= minimum(sol[x]) <= 1e-10 # the ball never went through the floor but got very close -# @test minimum(sol[y]) ≈ -1.5 # check wall conditions -# @test maximum(sol[y]) ≈ 1.5 # check wall conditions -# @test 0 <= minimum(sol_nosplit[x]) <= 1e-10 # the ball never went through the floor but got very close -# @test minimum(sol_nosplit[y]) ≈ -1.5 # check wall conditions -# @test maximum(sol_nosplit[y]) ≈ 1.5 # check wall conditions -# -# ## Test multi-variable affect -# # in this test, there are two variables affected by a single event. -# events = [[x ~ 0] => [vx ~ -Pre(vx), vy ~ -Pre(vy)]] -# -# @named ball = ODESystem([D(x) ~ vx -# D(y) ~ vy -# D(vx) ~ -1 -# D(vy) ~ 0], t; continuous_events = events) -# -# ball_nosplit = structural_simplify(ball) -# ball = structural_simplify(ball) -# -# tspan = (0.0, 5.0) -# prob = ODEProblem(ball, Pair[], tspan) -# prob_nosplit = ODEProblem(ball_nosplit, Pair[], tspan) -# sol = solve(prob, Tsit5()) -# sol_nosplit = solve(prob_nosplit, Tsit5()) -# @test 0 <= minimum(sol[x]) <= 1e-10 # the ball never went through the floor but got very close -# @test -minimum(sol[y]) ≈ maximum(sol[y]) ≈ sqrt(2) # the ball will never go further than √2 in either direction (gravity was changed to 1 to get this particular number) -# @test 0 <= minimum(sol_nosplit[x]) <= 1e-10 # the ball never went through the floor but got very close -# @test -minimum(sol_nosplit[y]) ≈ maximum(sol_nosplit[y]) ≈ sqrt(2) # the ball will never go further than √2 in either direction (gravity was changed to 1 to get this particular number) -#end -# -## issue https://github.com/SciML/ModelingToolkit.jl/issues/1386 -## tests that it works for ODAESystem -#@testset "ODAESystem" begin -# @variables vs(t) v(t) vmeasured(t) -# eq = [vs ~ sin(2pi * t) -# D(v) ~ vs - v -# D(vmeasured) ~ 0.0] -# ev = [sin(20pi * t) ~ 0.0] => [vmeasured ~ Pre(v)] -# @named sys = ODESystem(eq, t, continuous_events = ev) -# sys = structural_simplify(sys) -# prob = ODEProblem(sys, zeros(2), (0.0, 5.1)) -# sol = solve(prob, Tsit5()) -# @test all(minimum((0:0.1:5) .- sol.t', dims = 2) .< 0.0001) # test that the solver stepped every 0.1s as dictated by event -# @test sol([0.25])[vmeasured][] == sol([0.23])[vmeasured][] # test the hold property -#end -# -### https://github.com/SciML/ModelingToolkit.jl/issues/1528 -#@testset "Handle Empty Events" begin -# Dₜ = D -# -# @parameters u(t) [input = true] # Indicate that this is a controlled input -# @parameters y(t) [output = true] # Indicate that this is a measured output -# -# function Mass(; name, m = 1.0, p = 0, v = 0) -# ps = @parameters m = m -# sts = @variables pos(t)=p vel(t)=v -# eqs = Dₜ(pos) ~ vel -# ODESystem(eqs, t, [pos, vel], ps; name) -# end -# function Spring(; name, k = 1e4) -# ps = @parameters k = k -# @variables x(t) = 0 # Spring deflection -# ODESystem(Equation[], t, [x], ps; name) -# end -# function Damper(; name, c = 10) -# ps = @parameters c = c -# @variables vel(t) = 0 -# ODESystem(Equation[], t, [vel], ps; name) -# end -# function SpringDamper(; name, k = false, c = false) -# spring = Spring(; name = :spring, k) -# damper = Damper(; name = :damper, c) -# compose(ODESystem(Equation[], t; name), -# spring, damper) -# end -# connect_sd(sd, m1, m2) = [ -# sd.spring.x ~ m1.pos - m2.pos, sd.damper.vel ~ m1.vel - m2.vel] -# sd_force(sd) = -sd.spring.k * sd.spring.x - sd.damper.c * sd.damper.vel -# @named mass1 = Mass(; m = 1) -# @named mass2 = Mass(; m = 1) -# @named sd = SpringDamper(; k = 1000, c = 10) -# function Model(u, d = 0) -# eqs = [connect_sd(sd, mass1, mass2) -# Dₜ(mass1.vel) ~ (sd_force(sd) + u) / mass1.m -# Dₜ(mass2.vel) ~ (-sd_force(sd) + d) / mass2.m] -# @named _model = ODESystem(eqs, t; observed = [y ~ mass2.pos]) -# @named model = compose(_model, mass1, mass2, sd) -# end -# model = Model(sin(30t)) -# sys = structural_simplify(model) -# @test isempty(ModelingToolkit.continuous_events(sys)) -#end +@testset "SymbolicContinuousCallback constructors" begin + e = SymbolicContinuousCallback(eqs[]) + @test e isa SymbolicContinuousCallback + @test isequal(equations(e), eqs) + @test e.affect == nothing + @test e.affect_neg == nothing + @test e.rootfind == SciMLBase.LeftRootFind + + e = SymbolicContinuousCallback(eqs) + @test e isa SymbolicContinuousCallback + @test isequal(equations(e), eqs) + @test e.affect == nothing + @test e.affect_neg == nothing + @test e.rootfind == SciMLBase.LeftRootFind + + e = SymbolicContinuousCallback(eqs, nothing) + @test e isa SymbolicContinuousCallback + @test isequal(equations(e), eqs) + @test e.affect == nothing + @test e.affect_neg == nothing + @test e.rootfind == SciMLBase.LeftRootFind + + e = SymbolicContinuousCallback(eqs[], nothing) + @test e isa SymbolicContinuousCallback + @test isequal(equations(e), eqs) + @test e.affect == nothing + @test e.affect_neg == nothing + @test e.rootfind == SciMLBase.LeftRootFind + + e = SymbolicContinuousCallback(eqs => nothing) + @test e isa SymbolicContinuousCallback + @test isequal(equations(e), eqs) + @test e.affect == nothing + @test e.affect_neg == nothing + @test e.rootfind == SciMLBase.LeftRootFind + + e = SymbolicContinuousCallback(eqs[] => nothing) + @test e isa SymbolicContinuousCallback + @test isequal(equations(e), eqs) + @test e.affect == nothing + @test e.affect_neg == nothing + @test e.rootfind == SciMLBase.LeftRootFind + + ## With affect + e = SymbolicContinuousCallback(eqs[], affect) + @test e isa SymbolicContinuousCallback + @test isequal(equations(e), eqs) + @test e.rootfind == SciMLBase.LeftRootFind + + # with only positive edge affect + e = SymbolicContinuousCallback(eqs[], affect, affect_neg = nothing) + @test e isa SymbolicContinuousCallback + @test isequal(equations(e), eqs) + @test isnothing(e.affect_neg) + @test e.rootfind == SciMLBase.LeftRootFind + + # with explicit edge affects + e = SymbolicContinuousCallback(eqs[], affect, affect_neg = affect_neg) + @test e isa SymbolicContinuousCallback + @test isequal(equations(e), eqs) + @test e.rootfind == SciMLBase.LeftRootFind + + # with different root finding ops + e = SymbolicContinuousCallback( + eqs[], affect, affect_neg = affect_neg, rootfind = SciMLBase.LeftRootFind) + @test e isa SymbolicContinuousCallback + @test isequal(equations(e), eqs) + @test e.rootfind == SciMLBase.LeftRootFind + + # test plural constructor + e = SymbolicContinuousCallbacks(eqs[]) + @test e isa Vector{SymbolicContinuousCallback} + @test isequal(equations(e[]), eqs) + @test e[].affect == nothing + + e = SymbolicContinuousCallbacks(eqs) + @test e isa Vector{SymbolicContinuousCallback} + @test isequal(equations(e[]), eqs) + @test e[].affect == nothing + + e = SymbolicContinuousCallbacks(eqs[] => affect) + @test e isa Vector{SymbolicContinuousCallback} + @test isequal(equations(e[]), eqs) + @test e[].affect isa AffectSystem + + e = SymbolicContinuousCallbacks(eqs => affect) + @test e isa Vector{SymbolicContinuousCallback} + @test isequal(equations(e[]), eqs) + @test e[].affect isa AffectSystem + + e = SymbolicContinuousCallbacks([eqs[] => affect]) + @test e isa Vector{SymbolicContinuousCallback} + @test isequal(equations(e[]), eqs) + @test e[].affect isa AffectSystem + + e = SymbolicContinuousCallbacks([eqs => affect]) + @test e isa Vector{SymbolicContinuousCallback} + @test isequal(equations(e[]), eqs) + @test e[].affect isa AffectSystem +end + +@testset "ImperativeAffect constructors" begin + fmfa(o, x, i, c) = nothing + m = ModelingToolkit.ImperativeAffect(fmfa) + @test m isa ModelingToolkit.ImperativeAffect + @test m.f == fmfa + @test m.obs == [] + @test m.obs_syms == [] + @test m.modified == [] + @test m.mod_syms == [] + @test m.ctx === nothing + + m = ModelingToolkit.ImperativeAffect(fmfa, (;)) + @test m isa ModelingToolkit.ImperativeAffect + @test m.f == fmfa + @test m.obs == [] + @test m.obs_syms == [] + @test m.modified == [] + @test m.mod_syms == [] + @test m.ctx === nothing + + m = ModelingToolkit.ImperativeAffect(fmfa, (; x)) + @test m isa ModelingToolkit.ImperativeAffect + @test m.f == fmfa + @test isequal(m.obs, []) + @test m.obs_syms == [] + @test isequal(m.modified, [x]) + @test m.mod_syms == [:x] + @test m.ctx === nothing + + m = ModelingToolkit.ImperativeAffect(fmfa, (; y = x)) + @test m isa ModelingToolkit.ImperativeAffect + @test m.f == fmfa + @test isequal(m.obs, []) + @test m.obs_syms == [] + @test isequal(m.modified, [x]) + @test m.mod_syms == [:y] + @test m.ctx === nothing + + m = ModelingToolkit.ImperativeAffect(fmfa; observed = (; y = x)) + @test m isa ModelingToolkit.ImperativeAffect + @test m.f == fmfa + @test isequal(m.obs, [x]) + @test m.obs_syms == [:y] + @test m.modified == [] + @test m.mod_syms == [] + @test m.ctx === nothing + + m = ModelingToolkit.ImperativeAffect(fmfa; modified = (; x)) + @test m isa ModelingToolkit.ImperativeAffect + @test m.f == fmfa + @test isequal(m.obs, []) + @test m.obs_syms == [] + @test isequal(m.modified, [x]) + @test m.mod_syms == [:x] + @test m.ctx === nothing + + m = ModelingToolkit.ImperativeAffect(fmfa; modified = (; y = x)) + @test m isa ModelingToolkit.ImperativeAffect + @test m.f == fmfa + @test isequal(m.obs, []) + @test m.obs_syms == [] + @test isequal(m.modified, [x]) + @test m.mod_syms == [:y] + @test m.ctx === nothing + + m = ModelingToolkit.ImperativeAffect(fmfa, (; x), (; x)) + @test m isa ModelingToolkit.ImperativeAffect + @test m.f == fmfa + @test isequal(m.obs, [x]) + @test m.obs_syms == [:x] + @test isequal(m.modified, [x]) + @test m.mod_syms == [:x] + @test m.ctx === nothing + + m = ModelingToolkit.ImperativeAffect(fmfa, (; y = x), (; y = x)) + @test m isa ModelingToolkit.ImperativeAffect + @test m.f == fmfa + @test isequal(m.obs, [x]) + @test m.obs_syms == [:y] + @test isequal(m.modified, [x]) + @test m.mod_syms == [:y] + @test m.ctx === nothing + + m = ModelingToolkit.ImperativeAffect( + fmfa; modified = (; y = x), observed = (; y = x)) + @test m isa ModelingToolkit.ImperativeAffect + @test m.f == fmfa + @test isequal(m.obs, [x]) + @test m.obs_syms == [:y] + @test isequal(m.modified, [x]) + @test m.mod_syms == [:y] + @test m.ctx === nothing + + m = ModelingToolkit.ImperativeAffect( + fmfa; modified = (; y = x), observed = (; y = x), ctx = 3) + @test m isa ModelingToolkit.ImperativeAffect + @test m.f == fmfa + @test isequal(m.obs, [x]) + @test m.obs_syms == [:y] + @test isequal(m.modified, [x]) + @test m.mod_syms == [:y] + @test m.ctx === 3 + + m = ModelingToolkit.ImperativeAffect(fmfa, (; x), (; x), 3) + @test m isa ModelingToolkit.ImperativeAffect + @test m.f == fmfa + @test isequal(m.obs, [x]) + @test m.obs_syms == [:x] + @test isequal(m.modified, [x]) + @test m.mod_syms == [:x] + @test m.ctx === 3 +end + +@testset "Condition Compilation" begin + @named sys = ODESystem(eqs, t, continuous_events = [x ~ 1]) + @test getfield(sys, :continuous_events)[] == + SymbolicContinuousCallback(Equation[x ~ 1], nothing) + @test isequal(equations(getfield(sys, :continuous_events))[], x ~ 1) + fsys = flatten(sys) + @test isequal(equations(getfield(fsys, :continuous_events))[], x ~ 1) + + @named sys2 = ODESystem([D(x) ~ 1], t, continuous_events = [x ~ 2], systems = [sys]) + @test getfield(sys2, :continuous_events)[] == + SymbolicContinuousCallback(Equation[x ~ 2], nothing) + @test all(ModelingToolkit.continuous_events(sys2) .== [ + SymbolicContinuousCallback(Equation[x ~ 2], nothing), + SymbolicContinuousCallback(Equation[sys.x ~ 1], nothing) + ]) + + @test isequal(equations(getfield(sys2, :continuous_events))[1], x ~ 2) + @test length(ModelingToolkit.continuous_events(sys2)) == 2 + @test isequal(equations(ModelingToolkit.continuous_events(sys2)[1])[], x ~ 2) + @test isequal(equations(ModelingToolkit.continuous_events(sys2)[2])[], sys.x ~ 1) + + sys = complete(sys) + sys_nosplit = complete(sys; split = false) + sys2 = complete(sys2) + + # Test proper rootfinding + prob = ODEProblem(sys, Pair[], (0.0, 2.0)) + p0 = 0 + t0 = 0 + @test get_callback(prob) isa ModelingToolkit.DiffEqCallbacks.ContinuousCallback + cb = ModelingToolkit.generate_continuous_callbacks(sys) + cond = cb.condition + out = [0.0] + cond.f_iip(out, [0], p0, t0) + @test out[] ≈ -1 # signature is u,p,t + cond.f_iip(out, [1], p0, t0) + @test out[] ≈ 0 # signature is u,p,t + cond.f_iip(out, [2], p0, t0) + @test out[] ≈ 1 # signature is u,p,t + + prob = ODEProblem(sys, Pair[], (0.0, 2.0)) + prob_nosplit = ODEProblem(sys_nosplit, Pair[], (0.0, 2.0)) + sol = solve(prob, Tsit5()) + sol_nosplit = solve(prob_nosplit, Tsit5()) + @test minimum(t -> abs(t - 1), sol.t) < 1e-10 # test that the solver stepped at the root + @test minimum(t -> abs(t - 1), sol_nosplit.t) < 1e-10 # test that the solver stepped at the root + + # Test user-provided callback is respected + test_callback = DiscreteCallback(x -> x, x -> x) + prob = ODEProblem(sys, Pair[], (0.0, 2.0), callback = test_callback) + prob_nosplit = ODEProblem(sys_nosplit, Pair[], (0.0, 2.0), callback = test_callback) + cbs = get_callback(prob) + cbs_nosplit = get_callback(prob_nosplit) + @test cbs isa CallbackSet + @test cbs.discrete_callbacks[1] == test_callback + @test cbs_nosplit isa CallbackSet + @test cbs_nosplit.discrete_callbacks[1] == test_callback + + prob = ODEProblem(sys2, Pair[], (0.0, 3.0)) + cb = get_callback(prob) + @test cb isa ModelingToolkit.DiffEqCallbacks.VectorContinuousCallback + + cond = cb.condition + out = [0.0, 0.0] + # the root to find is 2 + cond.f_iip(out, [0, 0], p0, t0) + @test out[1] ≈ -2 # signature is u,p,t + cond.f_iip(out, [1, 0], p0, t0) + @test out[1] ≈ -1 # signature is u,p,t + cond.f_iip(out, [2, 0], p0, t0) # this should return 0 + @test out[1] ≈ 0 # signature is u,p,t + + # the root to find is 1 + out = [0.0, 0.0] + cond.f_iip(out, [0, 0], p0, t0) + @test out[2] ≈ -1 # signature is u,p,t + cond.f_iip(out, [0, 1], p0, t0) # this should return 0 + @test out[2] ≈ 0 # signature is u,p,t + cond.f_iip(out, [0, 2], p0, t0) + @test out[2] ≈ 1 # signature is u,p,t + + sol = solve(prob, Tsit5()) + @test minimum(t -> abs(t - 1), sol.t) < 1e-10 # test that the solver stepped at the first root + @test minimum(t -> abs(t - 2), sol.t) < 1e-10 # test that the solver stepped at the second root + + @named sys = ODESystem(eqs, t, continuous_events = [x ~ 1, x ~ 2]) # two root eqs using the same unknown + sys = complete(sys) + prob = ODEProblem(sys, Pair[], (0.0, 3.0)) + @test get_callback(prob) isa ModelingToolkit.DiffEqCallbacks.VectorContinuousCallback + sol = solve(prob, Tsit5()) + @test minimum(t -> abs(t - 1), sol.t) < 1e-10 # test that the solver stepped at the first root + @test minimum(t -> abs(t - 2), sol.t) < 1e-10 # test that the solver stepped at the second root +end + +@testset "Bouncing Ball" begin + ###### 1D Bounce + @variables x(t)=1 v(t)=0 + + root_eqs = [x ~ 0] + affect = [v ~ -Pre(v)] + + @named ball = ODESystem( + [D(x) ~ v + D(v) ~ -9.8], t, continuous_events = root_eqs => affect) + + @test only(continuous_events(ball)) == + SymbolicContinuousCallback(Equation[x ~ 0], Equation[v ~ -Pre(v)]) + ball = structural_simplify(ball) + + @test length(ModelingToolkit.continuous_events(ball)) == 1 + + tspan = (0.0, 5.0) + prob = ODEProblem(ball, Pair[], tspan) + sol = solve(prob, Tsit5()) + @test 0 <= minimum(sol[x]) <= 1e-10 # the ball never went through the floor but got very close + + ###### 2D bouncing ball + @variables x(t)=1 y(t)=0 vx(t)=0 vy(t)=1 + + events = [[x ~ 0] => [vx ~ -Pre(vx)] + [y ~ -1.5, y ~ 1.5] => [vy ~ -Pre(vy)]] + + @named ball = ODESystem( + [D(x) ~ vx + D(y) ~ vy + D(vx) ~ -9.8 + D(vy) ~ -0.01vy], t; continuous_events = events) + + _ball = ball + ball = structural_simplify(_ball) + ball_nosplit = structural_simplify(_ball; split = false) + + tspan = (0.0, 5.0) + prob = ODEProblem(ball, Pair[], tspan) + prob_nosplit = ODEProblem(ball_nosplit, Pair[], tspan) + + cb = get_callback(prob) + @test cb isa ModelingToolkit.DiffEqCallbacks.VectorContinuousCallback + @test getfield(ball, :continuous_events)[1] == + SymbolicContinuousCallback(Equation[x ~ 0], Equation[vx ~ -Pre(vx)]) + @test getfield(ball, :continuous_events)[2] == + SymbolicContinuousCallback(Equation[y ~ -1.5, y ~ 1.5], Equation[vy ~ -Pre(vy)]) + cond = cb.condition + out = [0.0, 0.0, 0.0] + p0 = 0. + t0 = 0. + cond.f_iip(out, [0, 0, 0, 0], p0, t0) + @test out ≈ [0, 1.5, -1.5] + + sol = solve(prob, Tsit5()) + sol_nosplit = solve(prob_nosplit, Tsit5()) + @test 0 <= minimum(sol[x]) <= 1e-10 # the ball never went through the floor but got very close + @test minimum(sol[y]) ≈ -1.5 # check wall conditions + @test maximum(sol[y]) ≈ 1.5 # check wall conditions + @test 0 <= minimum(sol_nosplit[x]) <= 1e-10 # the ball never went through the floor but got very close + @test minimum(sol_nosplit[y]) ≈ -1.5 # check wall conditions + @test maximum(sol_nosplit[y]) ≈ 1.5 # check wall conditions + + ## Test multi-variable affect + # in this test, there are two variables affected by a single event. + events = [[x ~ 0] => [vx ~ -Pre(vx), vy ~ -Pre(vy)]] + + @named ball = ODESystem([D(x) ~ vx + D(y) ~ vy + D(vx) ~ -1 + D(vy) ~ 0], t; continuous_events = events) + + ball_nosplit = structural_simplify(ball) + ball = structural_simplify(ball) + + tspan = (0.0, 5.0) + prob = ODEProblem(ball, Pair[], tspan) + prob_nosplit = ODEProblem(ball_nosplit, Pair[], tspan) + sol = solve(prob, Tsit5()) + sol_nosplit = solve(prob_nosplit, Tsit5()) + @test 0 <= minimum(sol[x]) <= 1e-10 # the ball never went through the floor but got very close + @test -minimum(sol[y]) ≈ maximum(sol[y]) ≈ sqrt(2) # the ball will never go further than √2 in either direction (gravity was changed to 1 to get this particular number) + @test 0 <= minimum(sol_nosplit[x]) <= 1e-10 # the ball never went through the floor but got very close + @test -minimum(sol_nosplit[y]) ≈ maximum(sol_nosplit[y]) ≈ sqrt(2) # the ball will never go further than √2 in either direction (gravity was changed to 1 to get this particular number) +end + +# issue https://github.com/SciML/ModelingToolkit.jl/issues/1386 +# tests that it works for ODAESystem +@testset "ODAESystem" begin + @variables vs(t) v(t) vmeasured(t) + eq = [vs ~ sin(2pi * t) + D(v) ~ vs - v + D(vmeasured) ~ 0.0] + ev = [sin(20pi * t) ~ 0.0] => [vmeasured ~ Pre(v)] + @named sys = ODESystem(eq, t, continuous_events = ev) + sys = structural_simplify(sys) + prob = ODEProblem(sys, zeros(2), (0.0, 5.1)) + sol = solve(prob, Tsit5()) + @test all(minimum((0:0.1:5) .- sol.t', dims = 2) .< 0.0001) # test that the solver stepped every 0.1s as dictated by event + @test sol([0.25])[vmeasured][] == sol([0.23])[vmeasured][] # test the hold property +end + +## https://github.com/SciML/ModelingToolkit.jl/issues/1528 +@testset "Handle Empty Events" begin + Dₜ = D + + @parameters u(t) [input = true] # Indicate that this is a controlled input + @parameters y(t) [output = true] # Indicate that this is a measured output + + function Mass(; name, m = 1.0, p = 0, v = 0) + ps = @parameters m = m + sts = @variables pos(t)=p vel(t)=v + eqs = Dₜ(pos) ~ vel + ODESystem(eqs, t, [pos, vel], ps; name) + end + function Spring(; name, k = 1e4) + ps = @parameters k = k + @variables x(t) = 0 # Spring deflection + ODESystem(Equation[], t, [x], ps; name) + end + function Damper(; name, c = 10) + ps = @parameters c = c + @variables vel(t) = 0 + ODESystem(Equation[], t, [vel], ps; name) + end + function SpringDamper(; name, k = false, c = false) + spring = Spring(; name = :spring, k) + damper = Damper(; name = :damper, c) + compose(ODESystem(Equation[], t; name), + spring, damper) + end + connect_sd(sd, m1, m2) = [ + sd.spring.x ~ m1.pos - m2.pos, sd.damper.vel ~ m1.vel - m2.vel] + sd_force(sd) = -sd.spring.k * sd.spring.x - sd.damper.c * sd.damper.vel + @named mass1 = Mass(; m = 1) + @named mass2 = Mass(; m = 1) + @named sd = SpringDamper(; k = 1000, c = 10) + function Model(u, d = 0) + eqs = [connect_sd(sd, mass1, mass2) + Dₜ(mass1.vel) ~ (sd_force(sd) + u) / mass1.m + Dₜ(mass2.vel) ~ (-sd_force(sd) + d) / mass2.m] + @named _model = ODESystem(eqs, t; observed = [y ~ mass2.pos]) + @named model = compose(_model, mass1, mass2, sd) + end + model = Model(sin(30t)) + sys = structural_simplify(model) + @test isempty(ModelingToolkit.continuous_events(sys)) +end @testset "SDE/ODESystem Discrete Callbacks" begin function testsol(sys, probtype, solver, u0, p, tspan; tstops = Float64[], paramtotest = nothing, @@ -1319,7 +1319,7 @@ end eqs = [y ~ g^2 - x, D(x) ~ x] c_evt = SymbolicContinuousCallback([t ~ 5.0], [x ~ Pre(x) + 1, g ~ Pre(g) + 1], discrete_parameters = [g], iv = t) @mtkbuild sys = ODESystem(eqs, t, continuous_events = c_evt) - prob = ODEProblem(sys, [x => 1.0], (0., 10.), [g => 2], guesses = [y => 0.]) + prob = ODEProblem(sys, [x => 1.0], (0., 10.), [g => 2]) sol = solve(prob, FBDF()) @test sol.ps[g] ≈ [2., 3.] @test ≈(sol(5.00000001, idxs = x) - sol(4.9999999, idxs = x), 1; rtol = 1e-4) From 990eb7b73fd37a6cd6c7c2168255829779152da0 Mon Sep 17 00:00:00 2001 From: vyudu Date: Wed, 26 Mar 2025 15:45:28 -0400 Subject: [PATCH 056/122] fix: use is_diff_equation with flatten_equations --- src/systems/diffeqs/odesystem.jl | 2 +- src/systems/diffeqs/sdesystem.jl | 2 +- src/systems/discrete_system/implicit_discrete_system.jl | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/systems/diffeqs/odesystem.jl b/src/systems/diffeqs/odesystem.jl index 9c984b87cd..554ee85a2d 100644 --- a/src/systems/diffeqs/odesystem.jl +++ b/src/systems/diffeqs/odesystem.jl @@ -336,7 +336,7 @@ function ODESystem(deqs::AbstractVector{<:Equation}, iv, dvs, ps; throw(ArgumentError("System names must be unique.")) end - algeeqs = filter(eq -> eq.lhs isa Union{Symbolic, Number} && !is_diff_equation(eq), deqs) + algeeqs = filter(eq -> eq.lhs isa Union{Symbolic, Number} && !is_diff_equation(eq), flatten_equations(deqs)) cont_callbacks = SymbolicContinuousCallbacks(continuous_events; algeeqs, iv) disc_callbacks = SymbolicDiscreteCallbacks(discrete_events; algeeqs, iv) diff --git a/src/systems/diffeqs/sdesystem.jl b/src/systems/diffeqs/sdesystem.jl index 554acd43fc..b2c8a31aa9 100644 --- a/src/systems/diffeqs/sdesystem.jl +++ b/src/systems/diffeqs/sdesystem.jl @@ -270,7 +270,7 @@ function SDESystem(deqs::AbstractVector{<:Equation}, neqs::AbstractArray, iv, dv Wfact = RefValue(EMPTY_JAC) Wfact_t = RefValue(EMPTY_JAC) - algeeqs = filter(eq -> eq.lhs isa Union{Symbolic, Number} && !is_diff_equation(eq), deqs) + algeeqs = filter(eq -> eq.lhs isa Union{Symbolic, Number} && !is_diff_equation(eq), flatten_equations(deqs)) cont_callbacks = SymbolicContinuousCallbacks(continuous_events; algeeqs, iv) disc_callbacks = SymbolicDiscreteCallbacks(discrete_events; algeeqs, iv) if is_dde === nothing diff --git a/src/systems/discrete_system/implicit_discrete_system.jl b/src/systems/discrete_system/implicit_discrete_system.jl index a43595b25b..ef64f92336 100644 --- a/src/systems/discrete_system/implicit_discrete_system.jl +++ b/src/systems/discrete_system/implicit_discrete_system.jl @@ -298,11 +298,11 @@ function shift_u0map_forward(sys::ImplicitDiscreteSystem, u0map, defs) v = u0map[k] if !((op = operation(k)) isa Shift) isnothing(getunshifted(k)) && - @warn "Initial condition given in term of current state of the unknown. If `build_initializeprob = false, this may be overriden by the implicit discrete solver." + @warn "Initial condition given in term of current state of the unknown. If `build_initializeprob = false`, this may be overriden by the implicit discrete solver." updated[k] = v elseif op.steps > 0 - error("Initial conditions must be for the past state of the unknowns. Instead of providing the condition for $k, provide the condition for $(Shift(iv, -1)(only(arguments(k)))).") + error("Initial conditions must be for the current or past state of the unknowns. Instead of providing the condition for $k, provide the condition for $(Shift(iv, -1)(only(arguments(k)))).") else updated[k] = v end From 479e147f722159f4405c5f649882842f260eab26 Mon Sep 17 00:00:00 2001 From: vyudu Date: Wed, 26 Mar 2025 15:50:27 -0400 Subject: [PATCH 057/122] remove show --- src/systems/callbacks.jl | 2 +- src/systems/index_cache.jl | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/systems/callbacks.jl b/src/systems/callbacks.jl index 28a617678d..71f2960817 100644 --- a/src/systems/callbacks.jl +++ b/src/systems/callbacks.jl @@ -116,7 +116,7 @@ Base.show(io::IO, x::Pre) = print(io, "Pre") input_timedomain(::Pre, _ = nothing) = ContinuousClock() output_timedomain(::Pre, _ = nothing) = ContinuousClock() unPre(x::Num) = unPre(unwrap(x)) -unPre(x::BasicSymbolic) = operation(x) isa Pre ? only(arguments(x)) : x +unPre(x::BasicSymbolic) = (iscall(x) && operation(x) isa Pre) ? only(arguments(x)) : x function (p::Pre)(x) iw = Symbolics.iswrapped(x) diff --git a/src/systems/index_cache.jl b/src/systems/index_cache.jl index 1186e70b5b..e4087e1368 100644 --- a/src/systems/index_cache.jl +++ b/src/systems/index_cache.jl @@ -127,7 +127,6 @@ function IndexCache(sys::AbstractSystem) end for sym in discs - @show sym is_parameter(sys, sym) || error("Expected discrete variable $sym in callback to be a parameter") From 60a6a53ad5023cbbece845c75a95c5b183f941df Mon Sep 17 00:00:00 2001 From: vyudu Date: Wed, 26 Mar 2025 19:26:20 -0400 Subject: [PATCH 058/122] format --- src/systems/callbacks.jl | 167 +++++++++++------- src/systems/diffeqs/odesystem.jl | 3 +- src/systems/diffeqs/sdesystem.jl | 3 +- .../discrete_system/discrete_system.jl | 2 +- .../implicit_discrete_system.jl | 5 +- src/systems/index_cache.jl | 3 +- src/systems/model_parsing.jl | 3 + src/systems/systemstructure.jl | 1 - test/accessor_functions.jl | 8 +- test/symbolic_events.jl | 72 ++++---- 10 files changed, 164 insertions(+), 103 deletions(-) diff --git a/src/systems/callbacks.jl b/src/systems/callbacks.jl index 71f2960817..97141b3405 100644 --- a/src/systems/callbacks.jl +++ b/src/systems/callbacks.jl @@ -72,7 +72,7 @@ aff_to_sys(a::AffectSystem) = a.aff_to_sys previous_vals(a::AffectSystem) = parameters(system(a)) all_equations(a::AffectSystem) = vcat(equations(system(a)), observed(system(a))) -function Base.show(iio::IO, aff::AffectSystem) +function Base.show(iio::IO, aff::AffectSystem) println(iio, "Affect system defined by equations:") eqs = all_equations(aff) show(iio, eqs) @@ -81,8 +81,8 @@ end function Base.:(==)(a1::AffectSystem, a2::AffectSystem) isequal(system(a1), system(a2)) && isequal(discretes(a1), discretes(a2)) && - isequal(unknowns(a1), unknowns(a2)) && - isequal(parameters(a1), parameters(a2)) && + isequal(unknowns(a1), unknowns(a2)) && + isequal(parameters(a1), parameters(a2)) && isequal(aff_to_sys(a1), aff_to_sys(a2)) end @@ -94,7 +94,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::Union{FunctionalAffect, AffectSystem}; op = Differential) for var in Iterators.flatten((unknowns(aff), parameters(aff), discretes(aff))) vars!(vars, var) end @@ -202,7 +202,7 @@ Affects (i.e. `affect` and `affect_neg`) can be specified as either: DAEs will automatically be reinitialized. -Initial and final affects can also be specified with SCC, which are specified identically to positive and negative edge affects. Initialization affects +Initial and final affects can also be specified identically to positive and negative edge affects. Initialization affects will run as soon as the solver starts, while finalization affects will be executed after termination. """ struct SymbolicContinuousCallback <: AbstractCallback @@ -220,17 +220,20 @@ struct SymbolicContinuousCallback <: AbstractCallback affect_neg = affect, initialize = nothing, finalize = nothing, - rootfind = SciMLBase.LeftRootFind, + rootfind = SciMLBase.LeftRootFind, iv = nothing, algeeqs = Equation[]) - conditions = (conditions isa AbstractVector) ? conditions : [conditions] - new(conditions, make_affect(affect; iv, algeeqs, discrete_parameters), make_affect(affect_neg; iv, algeeqs, discrete_parameters), - make_affect(initialize; iv, algeeqs, discrete_parameters), make_affect(finalize; iv, algeeqs, discrete_parameters), rootfind) + new(conditions, make_affect(affect; iv, algeeqs, discrete_parameters), + make_affect(affect_neg; iv, algeeqs, discrete_parameters), + make_affect(initialize; iv, algeeqs, discrete_parameters), make_affect( + finalize; iv, algeeqs, discrete_parameters), rootfind) end # Default affect to nothing end -SymbolicContinuousCallback(p::Pair, args...; kwargs...) = SymbolicContinuousCallback(p[1], p[2], args...; kwargs...) +function SymbolicContinuousCallback(p::Pair, args...; kwargs...) + SymbolicContinuousCallback(p[1], p[2], args...; kwargs...) +end SymbolicContinuousCallback(cb::SymbolicContinuousCallback, args...; kwargs...) = cb make_affect(affect::Nothing; kwargs...) = nothing @@ -238,27 +241,31 @@ make_affect(affect::Tuple; kwargs...) = FunctionalAffect(affect...) make_affect(affect::NamedTuple; kwargs...) = FunctionalAffect(; affect...) make_affect(affect::Affect; kwargs...) = affect -function make_affect(affect::Vector{Equation}; discrete_parameters::AbstractVector = Any[], iv = nothing, algeeqs::Vector{Equation} = Equation[]) +function make_affect(affect::Vector{Equation}; discrete_parameters::AbstractVector = Any[], + iv = nothing, algeeqs::Vector{Equation} = Equation[]) isempty(affect) && return nothing - isempty(algeeqs) && @warn "No algebraic equations were found for the callback defined by $(join(affect, ", ")). If the system has no algebraic equations, this can be disregarded. Otherwise pass in `algeeqs` to the SymbolicContinuousCallback constructor." + isempty(algeeqs) && + @warn "No algebraic equations were found for the callback defined by $(join(affect, ", ")). If the system has no algebraic equations, this can be disregarded. Otherwise pass in `algeeqs` to the SymbolicContinuousCallback constructor." if isnothing(iv) iv = t_nounits @warn "No independent variable specified. Defaulting to t_nounits." end for p in discrete_parameters - occursin(unwrap(iv), unwrap(p)) || error("Non-time dependent parameter $p passed in as a discrete. Must be declared as @parameters $p(t).") + occursin(unwrap(iv), unwrap(p)) || + error("Non-time dependent parameter $p passed in as a discrete. Must be declared as @parameters $p(t).") end dvs = OrderedSet() params = OrderedSet() for eq in affect - if !haspre(eq) && !(symbolic_type(eq.rhs) === NotSymbolic() || symbolic_type(eq.lhs) === NotSymbolic()) + if !haspre(eq) && !(symbolic_type(eq.rhs) === NotSymbolic() || + symbolic_type(eq.lhs) === NotSymbolic()) @warn "Affect equation $eq has no `Pre` operator. As such it will be interpreted as an algebraic equation to be satisfied after the callback. If you intended to use the value of a variable x before the affect, use Pre(x)." end collect_vars!(dvs, params, eq, iv; op = Pre) end - for eq in algeeqs + for eq in algeeqs collect_vars!(dvs, params, eq, iv) end @@ -269,7 +276,10 @@ function make_affect(affect::Vector{Equation}; discrete_parameters::AbstractVect rev_map = Dict(zip(discrete_parameters, discretes)) affect = Symbolics.fast_substitute(affect, rev_map) algeeqs = Symbolics.fast_substitute(algeeqs, rev_map) - @mtkbuild affectsys = ImplicitDiscreteSystem(vcat(affect, algeeqs), iv, collect(union(dvs, discretes)), collect(union(pre_params, sys_params))) + @named affectsys = ImplicitDiscreteSystem( + vcat(affect, algeeqs), iv, collect(union(dvs, discretes)), + collect(union(pre_params, sys_params))) + affectsys = structural_simplify(affect_sys; fully_determined = false) # get accessed parameters p from Pre(p) in the callback parameters accessed_params = filter(isparameter, map(unPre, collect(pre_params))) union!(accessed_params, sys_params) @@ -278,7 +288,8 @@ function make_affect(affect::Vector{Equation}; discrete_parameters::AbstractVect aff_map[u] = u end - AffectSystem(affectsys, collect(dvs), collect(accessed_params), collect(discrete_parameters), aff_map) + AffectSystem(affectsys, collect(dvs), collect(accessed_params), + collect(discrete_parameters), aff_map) end function make_affect(affect; kwargs...) @@ -288,7 +299,8 @@ end """ Generate continuous callbacks. """ -function SymbolicContinuousCallbacks(events; discrete_parameters = Any[], algeeqs::Vector{Equation} = Equation[], iv = nothing) +function SymbolicContinuousCallbacks(events; discrete_parameters = Any[], + algeeqs::Vector{Equation} = Equation[], iv = nothing) callbacks = SymbolicContinuousCallback[] isnothing(events) && return callbacks @@ -297,7 +309,8 @@ function SymbolicContinuousCallbacks(events; discrete_parameters = Any[], algeeq for event in events cond, affs = event isa Pair ? (event[1], event[2]) : (event, nothing) - push!(callbacks, SymbolicContinuousCallback(cond, affs; iv, algeeqs, discrete_parameters)) + push!(callbacks, + SymbolicContinuousCallback(cond, affs; iv, algeeqs, discrete_parameters)) end callbacks end @@ -305,7 +318,8 @@ end function Base.show(io::IO, cb::AbstractCallback) indent = get(io, :indent, 0) iio = IOContext(io, :indent => indent + 1) - is_discrete(cb) ? print(io, "SymbolicDiscreteCallback(") : print(io, "SymbolicContinuousCallback(") + is_discrete(cb) ? print(io, "SymbolicDiscreteCallback(") : + print(io, "SymbolicContinuousCallback(") print(iio, "Conditions:") show(iio, equations(cb)) print(iio, "; ") @@ -334,7 +348,8 @@ end function Base.show(io::IO, mime::MIME"text/plain", cb::AbstractCallback) indent = get(io, :indent, 0) iio = IOContext(io, :indent => indent + 1) - is_discrete(cb) ? println(io, "SymbolicDiscreteCallback:") : println(io, "SymbolicContinuousCallback:") + is_discrete(cb) ? println(io, "SymbolicDiscreteCallback:") : + println(io, "SymbolicContinuousCallback:") println(iio, "Conditions:") show(iio, mime, equations(cb)) print(iio, "\n") @@ -405,21 +420,26 @@ struct SymbolicDiscreteCallback <: AbstractCallback function SymbolicDiscreteCallback( condition, affect = nothing; - initialize = nothing, finalize = nothing, iv = nothing, algeeqs = Equation[], discrete_parameters = Any[]) + initialize = nothing, finalize = nothing, iv = nothing, + algeeqs = Equation[], discrete_parameters = Any[]) c = is_timed_condition(condition) ? condition : value(scalarize(condition)) - new(c, make_affect(affect; iv, algeeqs, discrete_parameters), make_affect(initialize; iv, algeeqs, discrete_parameters), + new(c, make_affect(affect; iv, algeeqs, discrete_parameters), + make_affect(initialize; iv, algeeqs, discrete_parameters), make_affect(finalize; iv, algeeqs, discrete_parameters)) end # Default affect to nothing end -SymbolicDiscreteCallback(p::Pair, args...; kwargs...) = SymbolicDiscreteCallback(p[1], p[2], args...; kwargs...) +function SymbolicDiscreteCallback(p::Pair, args...; kwargs...) + SymbolicDiscreteCallback(p[1], p[2], args...; kwargs...) +end SymbolicDiscreteCallback(cb::SymbolicDiscreteCallback, args...; kwargs...) = cb """ Generate discrete callbacks. """ -function SymbolicDiscreteCallbacks(events; discrete_parameters::Vector = Any[], algeeqs::Vector{Equation} = Equation[], iv = nothing) +function SymbolicDiscreteCallbacks(events; discrete_parameters::Vector = Any[], + algeeqs::Vector{Equation} = Equation[], iv = nothing) callbacks = SymbolicDiscreteCallback[] isnothing(events) && return callbacks @@ -428,7 +448,8 @@ function SymbolicDiscreteCallbacks(events; discrete_parameters::Vector = Any[], for event in events cond, affs = event isa Pair ? (event[1], event[2]) : (event, nothing) - push!(callbacks, SymbolicDiscreteCallback(cond, affs; iv, algeeqs, discrete_parameters)) + push!(callbacks, + SymbolicDiscreteCallback(cond, affs; iv, algeeqs, discrete_parameters)) end callbacks end @@ -459,7 +480,7 @@ function namespace_affects(affect::FunctionalAffect, s) context(affect)) end -function namespace_affects(affect::AffectSystem, s) +function namespace_affects(affect::AffectSystem, s) AffectSystem(renamespace(s, system(affect)), renamespace.((s,), unknowns(affect)), renamespace.((s,), parameters(affect)), @@ -491,7 +512,8 @@ function namespace_callback(cb::SymbolicDiscreteCallback, s)::SymbolicDiscreteCa end function Base.hash(cb::AbstractCallback, s::UInt) - s = conditions(cb) isa AbstractVector ? foldr(hash, conditions(cb), init = s) : hash(conditions(cb), s) + s = conditions(cb) isa AbstractVector ? foldr(hash, conditions(cb), init = s) : + hash(conditions(cb), s) s = hash(affects(cb), s) !is_discrete(cb) && (s = hash(affect_negs(cb), s)) s = hash(initialize_affects(cb), s) @@ -533,8 +555,10 @@ end function Base.:(==)(e1::AbstractCallback, e2::AbstractCallback) (is_discrete(e1) === is_discrete(e2)) || return false (isequal(e1.conditions, e2.conditions) && isequal(e1.affect, e2.affect) && - isequal(e1.initialize, e2.initialize) && isequal(e1.finalize, e2.finalize)) || return false - is_discrete(e1) || (isequal(e1.affect_neg, e2.affect_neg) && isequal(e1.rootfind, e2.rootfind)) + isequal(e1.initialize, e2.initialize) && isequal(e1.finalize, e2.finalize)) || + return false + is_discrete(e1) || + (isequal(e1.affect_neg, e2.affect_neg) && isequal(e1.rootfind, e2.rootfind)) end Base.isempty(cb::AbstractCallback) = isempty(cb.conditions) @@ -553,7 +577,8 @@ Notes If set to `Val{false}` a `RuntimeGeneratedFunction` will be returned. - `kwargs` are passed through to `Symbolics.build_function`. """ -function compile_condition(cbs::Union{AbstractCallback, Vector{<:AbstractCallback}}, sys, dvs, ps; +function compile_condition( + cbs::Union{AbstractCallback, Vector{<:AbstractCallback}}, sys, dvs, ps; expression = Val{false}, eval_expression = false, eval_module = @__MODULE__, kwargs...) u = map(x -> time_varying_as_func(value(x), sys), dvs) p = map.(x -> time_varying_as_func(value(x), sys), reorder_parameters(sys, ps)) @@ -570,8 +595,8 @@ function compile_condition(cbs::Union{AbstractCallback, Vector{<:AbstractCallbac end fs = build_function_wrapper(sys, - condit, u, p..., t; expression, - kwargs...) + condit, u, p..., t; expression, + kwargs...) if expression == Val{true} fs = eval_or_rgf.(fs; eval_expression, eval_module) @@ -628,7 +653,8 @@ end is_discrete(cb::AbstractCallback) = cb isa SymbolicDiscreteCallback is_discrete(cb::Vector{<:AbstractCallback}) = eltype(cb) isa SymbolicDiscreteCallback -function generate_continuous_callbacks(sys::AbstractSystem, dvs = unknowns(sys), ps = parameters(sys; initial_parameters = true); kwargs...) +function generate_continuous_callbacks(sys::AbstractSystem, dvs = unknowns(sys), + ps = parameters(sys; initial_parameters = true); kwargs...) cbs = continuous_events(sys) isempty(cbs) && return nothing cb_classes = Dict{SciMLBase.RootfindOpt, Vector{SymbolicContinuousCallback}}() @@ -647,7 +673,8 @@ function generate_continuous_callbacks(sys::AbstractSystem, dvs = unknowns(sys), end end -function generate_discrete_callbacks(sys::AbstractSystem, dvs = unknowns(sys), ps = parameters(sys; initial_parameters = true); kwargs...) +function generate_discrete_callbacks(sys::AbstractSystem, dvs = unknowns(sys), + ps = parameters(sys; initial_parameters = true); kwargs...) dbs = discrete_events(sys) isempty(dbs) && return nothing [generate_callback(db, sys; kwargs...) for db in dbs] @@ -668,8 +695,9 @@ function generate_callback(cbs::Vector{SymbolicContinuousCallback}, sys; kwargs. cb_ind = findfirst(>(0), num_eqs) return generate_callback(cbs[cb_ind], sys; kwargs...) end - - trigger = compile_condition(cbs, sys, unknowns(sys), parameters(sys; initial_parameters = true); kwargs...) + + trigger = compile_condition( + cbs, sys, unknowns(sys), parameters(sys; initial_parameters = true); kwargs...) affects = [] affect_negs = [] inits = [] @@ -677,9 +705,13 @@ function generate_callback(cbs::Vector{SymbolicContinuousCallback}, sys; kwargs. for cb in cbs affect = compile_affect(cb.affect, cb, sys; default = EMPTY_AFFECT, kwargs...) push!(affects, affect) - affect_neg = (cb.affect_neg === cb.affect) ? affect : compile_affect(cb.affect_neg, cb, sys; default = EMPTY_AFFECT, kwargs...) + affect_neg = (cb.affect_neg === cb.affect) ? affect : + compile_affect( + cb.affect_neg, cb, sys; default = EMPTY_AFFECT, kwargs...) push!(affect_negs, affect_neg) - push!(inits, compile_affect(cb.initialize, cb, sys; default = nothing, is_init = true, kwargs...)) + push!(inits, + compile_affect( + cb.initialize, cb, sys; default = nothing, is_init = true, kwargs...)) push!(finals, compile_affect(cb.finalize, cb, sys; default = nothing, kwargs...)) end @@ -701,8 +733,8 @@ function generate_callback(cbs::Vector{SymbolicContinuousCallback}, sys; kwargs. finalize = wrap_vector_optional_affect(finals, SciMLBase.FINALIZE_DEFAULT) return VectorContinuousCallback( - trigger, affect, affect_neg, length(eqs); initialize, finalize, - rootfind = cbs[1].rootfind, initializealg = SciMLBase.NoInit()) + trigger, affect, affect_neg, length(eqs); initialize, finalize, + rootfind = cbs[1].rootfind, initializealg = SciMLBase.NoInit()) end function generate_callback(cb, sys; kwargs...) @@ -715,10 +747,13 @@ function generate_callback(cb, sys; kwargs...) affect_neg = if is_discrete(cb) nothing else - (cb.affect === cb.affect_neg) ? affect : compile_affect(cb.affect_neg, cb, sys; default = EMPTY_AFFECT, kwargs...) + (cb.affect === cb.affect_neg) ? affect : + compile_affect(cb.affect_neg, cb, sys; default = EMPTY_AFFECT, kwargs...) end - init = compile_affect(cb.initialize, cb, sys; default = SciMLBase.INITIALIZE_DEFAULT, is_init = true, kwargs...) - final = compile_affect(cb.finalize, cb, sys; default = SciMLBase.FINALIZE_DEFAULT, kwargs...) + init = compile_affect(cb.initialize, cb, sys; default = SciMLBase.INITIALIZE_DEFAULT, + is_init = true, kwargs...) + final = compile_affect( + cb.finalize, cb, sys; default = SciMLBase.FINALIZE_DEFAULT, kwargs...) initialize = isnothing(cb.initialize) ? init : ((c, u, t, i) -> init(i)) finalize = isnothing(cb.finalize) ? final : ((c, u, t, i) -> final(i)) @@ -726,16 +761,16 @@ function generate_callback(cb, sys; kwargs...) if is_discrete(cb) if is_timed && conditions(cb) isa AbstractVector return PresetTimeCallback(trigger, affect; initialize, - finalize, initializealg = SciMLBase.NoInit()) + finalize, initializealg = SciMLBase.NoInit()) elseif is_timed - return PeriodicCallback(affect, trigger; initialize, finalize) + return PeriodicCallback(affect, trigger; initialize, finalize, initializealg = SciMLBase.NoInit()) else return DiscreteCallback(trigger, affect; initialize, - finalize, initializealg = SciMLBase.NoInit()) + finalize, initializealg = SciMLBase.NoInit()) end else return ContinuousCallback(trigger, affect, affect_neg; initialize, finalize, - rootfind = cb.rootfind, initializealg = SciMLBase.NoInit()) + rootfind = cb.rootfind, initializealg = SciMLBase.NoInit()) end end @@ -756,7 +791,8 @@ Notes - `kwargs` are passed through to `Symbolics.build_function`. """ function compile_affect( - aff::Union{Nothing, Affect}, cb::AbstractCallback, sys::AbstractSystem; default = nothing, is_init = false, kwargs...) + aff::Union{Nothing, Affect}, cb::AbstractCallback, sys::AbstractSystem; + default = nothing, is_init = false, kwargs...) save_idxs = if !(has_index_cache(sys) && (ic = get_index_cache(sys)) !== nothing) Int[] else @@ -775,6 +811,7 @@ function compile_affect( end function wrap_save_discretes(f, save_idxs) + @show save_idxs let save_idxs = save_idxs if f === SciMLBase.INITIALIZE_DEFAULT (c, u, t, i) -> begin @@ -809,7 +846,7 @@ function wrap_vector_optional_affect(funs, default) end function add_integrator_header( - sys::AbstractSystem, integrator = gensym(:MTKIntegrator), out = :u) + sys::AbstractSystem, integrator = gensym(:MTKIntegrator), out = :u) expr -> Func([DestructuredArgs(expr.args, integrator, inds = [:u, :p, :t])], [], expr.body), expr -> Func( @@ -820,7 +857,8 @@ end """ Compile an affect defined by a set of equations. Systems with algebraic equations will solve implicit discrete problems to obtain their next state. Systems without will generate functions that perform explicit updates. """ -function compile_equational_affect(aff::Union{AffectSystem, Vector{Equation}}, sys; reset_jumps = false, kwargs...) +function compile_equational_affect( + aff::Union{AffectSystem, Vector{Equation}}, sys; reset_jumps = false, kwargs...) if aff isa AbstractVector aff = make_affect(aff; iv = get_iv(sys)) end @@ -831,7 +869,8 @@ function compile_equational_affect(aff::Union{AffectSystem, Vector{Equation}}, s sys_map = Dict([v => k for (k, v) in aff_map]) if isempty(equations(affsys)) - update_eqs = Symbolics.fast_substitute(observed(affsys), Dict([p => unPre(p) for p in parameters(affsys)])) + update_eqs = Symbolics.fast_substitute( + observed(affsys), Dict([p => unPre(p) for p in parameters(affsys)])) rhss = map(x -> x.rhs, update_eqs) lhss = map(x -> aff_map[x.lhs], update_eqs) is_p = [lhs ∈ Set(ps_to_update) for lhs in lhss] @@ -851,9 +890,13 @@ function compile_equational_affect(aff::Union{AffectSystem, Vector{Equation}}, s end _ps = reorder_parameters(sys, ps) integ = gensym(:MTKIntegrator) - - u_up, u_up! = build_function_wrapper(sys, (@view rhss[is_u]), dvs, _ps..., t; wrap_code = add_integrator_header(sys, integ, :u), expression = Val{false}, outputidxs = u_idxs, wrap_mtkparameters) - p_up, p_up! = build_function_wrapper(sys, (@view rhss[is_p]), dvs, _ps..., t; wrap_code = add_integrator_header(sys, integ, :p), expression = Val{false}, outputidxs = p_idxs, wrap_mtkparameters) + + u_up, u_up! = build_function_wrapper(sys, (@view rhss[is_u]), dvs, _ps..., t; + wrap_code = add_integrator_header(sys, integ, :u), + expression = Val{false}, outputidxs = u_idxs, wrap_mtkparameters) + p_up, p_up! = build_function_wrapper(sys, (@view rhss[is_p]), dvs, _ps..., t; + wrap_code = add_integrator_header(sys, integ, :p), + expression = Val{false}, outputidxs = p_idxs, wrap_mtkparameters) return function explicit_affect!(integ) isempty(dvs_to_update) || u_up!(integ) @@ -861,7 +904,9 @@ function compile_equational_affect(aff::Union{AffectSystem, Vector{Equation}}, s reset_jumps && reset_aggregated_jumps!(integ) 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 + 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 + function implicit_affect!(integ) pmap = Pair[] for pre_p in parameters(affsys) @@ -874,9 +919,11 @@ function compile_equational_affect(aff::Union{AffectSystem, Vector{Equation}}, s uval = isparameter(aff_map[u]) ? integ.ps[aff_map[u]] : integ[u] push!(u0, u => uval) end - affprob = ImplicitDiscreteProblem(affsys, u0, (integ.t, integ.t), pmap; build_initializeprob = false, check_length = false) + affprob = ImplicitDiscreteProblem(affsys, u0, (integ.t, integ.t), pmap; + build_initializeprob = false, check_length = false) affsol = init(affprob, IDSolve()) - (check_error(affsol) === ReturnCode.InitialFailure) && throw(UnsolvableCallbackError(all_equations(aff))) + (check_error(affsol) === ReturnCode.InitialFailure) && + throw(UnsolvableCallbackError(all_equations(aff))) for u in dvs_to_update integ[u] = affsol[sys_map[u]] end @@ -893,7 +940,8 @@ struct UnsolvableCallbackError end function Base.showerror(io::IO, err::UnsolvableCallbackError) - println(io, "The callback defined by the following equations:\n\n$(join(err.eqs, "\n"))\n\nis not solvable. Please check that the algebraic equations and affect equations are correct, and that all parameters intended to be changed are passed in as `discrete_parameters`.") + println(io, + "The callback defined by the following equations:\n\n$(join(err.eqs, "\n"))\n\nis not solvable. Please check that the algebraic equations and affect equations are correct, and that all parameters intended to be changed are passed in as `discrete_parameters`.") end merge_cb(::Nothing, ::Nothing) = nothing @@ -952,7 +1000,6 @@ function discrete_events_toplevel(sys::AbstractSystem) return get_discrete_events(sys) end - """ continuous_events(sys::AbstractSystem)::Vector{SymbolicContinuousCallback} diff --git a/src/systems/diffeqs/odesystem.jl b/src/systems/diffeqs/odesystem.jl index 554ee85a2d..4206155e4d 100644 --- a/src/systems/diffeqs/odesystem.jl +++ b/src/systems/diffeqs/odesystem.jl @@ -336,7 +336,8 @@ function ODESystem(deqs::AbstractVector{<:Equation}, iv, dvs, ps; throw(ArgumentError("System names must be unique.")) end - algeeqs = filter(eq -> eq.lhs isa Union{Symbolic, Number} && !is_diff_equation(eq), flatten_equations(deqs)) + algeeqs = filter(eq -> eq.lhs isa Union{Symbolic, Number} && !is_diff_equation(eq), + flatten_equations(deqs)) cont_callbacks = SymbolicContinuousCallbacks(continuous_events; algeeqs, iv) disc_callbacks = SymbolicDiscreteCallbacks(discrete_events; algeeqs, iv) diff --git a/src/systems/diffeqs/sdesystem.jl b/src/systems/diffeqs/sdesystem.jl index b2c8a31aa9..d1342e5650 100644 --- a/src/systems/diffeqs/sdesystem.jl +++ b/src/systems/diffeqs/sdesystem.jl @@ -270,7 +270,8 @@ function SDESystem(deqs::AbstractVector{<:Equation}, neqs::AbstractArray, iv, dv Wfact = RefValue(EMPTY_JAC) Wfact_t = RefValue(EMPTY_JAC) - algeeqs = filter(eq -> eq.lhs isa Union{Symbolic, Number} && !is_diff_equation(eq), flatten_equations(deqs)) + algeeqs = filter(eq -> eq.lhs isa Union{Symbolic, Number} && !is_diff_equation(eq), + flatten_equations(deqs)) cont_callbacks = SymbolicContinuousCallbacks(continuous_events; algeeqs, iv) disc_callbacks = SymbolicDiscreteCallbacks(discrete_events; algeeqs, iv) if is_dde === nothing diff --git a/src/systems/discrete_system/discrete_system.jl b/src/systems/discrete_system/discrete_system.jl index 773a6ccf6b..4dfed954e5 100644 --- a/src/systems/discrete_system/discrete_system.jl +++ b/src/systems/discrete_system/discrete_system.jl @@ -431,7 +431,7 @@ end function Base.:(==)(sys1::DiscreteSystem, sys2::DiscreteSystem) sys1 === sys2 && return true isequal(nameof(sys1), nameof(sys2)) && - isequal(get_iv(sys1), get_iv(sys2)) && + isequal(get_iv(sys1), get_iv(sys2)) && _eq_unordered(get_eqs(sys1), get_eqs(sys2)) && _eq_unordered(get_unknowns(sys1), get_unknowns(sys2)) && _eq_unordered(get_ps(sys1), get_ps(sys2)) && diff --git a/src/systems/discrete_system/implicit_discrete_system.jl b/src/systems/discrete_system/implicit_discrete_system.jl index ef64f92336..eb3a094ef5 100644 --- a/src/systems/discrete_system/implicit_discrete_system.jl +++ b/src/systems/discrete_system/implicit_discrete_system.jl @@ -270,7 +270,8 @@ function flatten(sys::ImplicitDiscreteSystem, noeqs = false) end function generate_function( - sys::ImplicitDiscreteSystem, dvs = unknowns(sys), ps = parameters(sys); wrap_code = identity, cachesyms::Tuple = (), kwargs...) + sys::ImplicitDiscreteSystem, dvs = unknowns(sys), ps = parameters(sys); + wrap_code = identity, cachesyms::Tuple = (), kwargs...) iv = get_iv(sys) # Algebraic equations get shifted forward 1, to match with differential equations exprs = map(equations(sys)) do eq @@ -453,7 +454,7 @@ end function Base.:(==)(sys1::ImplicitDiscreteSystem, sys2::ImplicitDiscreteSystem) sys1 === sys2 && return true isequal(nameof(sys1), nameof(sys2)) && - isequal(get_iv(sys1), get_iv(sys2)) && + isequal(get_iv(sys1), get_iv(sys2)) && _eq_unordered(get_eqs(sys1), get_eqs(sys2)) && _eq_unordered(get_unknowns(sys1), get_unknowns(sys2)) && _eq_unordered(get_ps(sys1), get_ps(sys2)) && diff --git a/src/systems/index_cache.jl b/src/systems/index_cache.jl index e4087e1368..948af4dfa5 100644 --- a/src/systems/index_cache.jl +++ b/src/systems/index_cache.jl @@ -117,7 +117,8 @@ 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 FunctionalAffect || + affect isa ImperativeAffect union!(discs, unwrap.(discretes(affect))) elseif isnothing(affect) continue diff --git a/src/systems/model_parsing.jl b/src/systems/model_parsing.jl index 9a9b95f417..413dfda17d 100644 --- a/src/systems/model_parsing.jl +++ b/src/systems/model_parsing.jl @@ -132,6 +132,9 @@ function _model_macro(mod, fullname::Union{Expr, Symbol}, expr, isconnector) @inline pop_structure_dict!.( Ref(dict), [:constants, :defaults, :kwargs, :structural_parameters]) + sys = :($ODESystem($(flatten_equations)(equations), $iv, variables, parameters; + name, description = $description, systems, gui_metadata = $gui_metadata, + defaults, continuous_events = cont_events, discrete_events = disc_events)) sys = :($type($(flatten_equations)(equations), $iv, variables, parameters; name, description = $description, systems, gui_metadata = $gui_metadata, defaults, costs = [$(costs...)], constraints = [$(cons...)], consolidate = $consolidate)) diff --git a/src/systems/systemstructure.jl b/src/systems/systemstructure.jl index e70502574a..08c95fbbb2 100644 --- a/src/systems/systemstructure.jl +++ b/src/systems/systemstructure.jl @@ -719,7 +719,6 @@ function _structural_simplify!(state::TearingState; simplify = false, inputs = Any[], outputs = Any[], disturbance_inputs = Any[], kwargs...) - if fully_determined isa Bool check_consistency &= fully_determined else diff --git a/test/accessor_functions.jl b/test/accessor_functions.jl index 24fb245fed..9272fb9146 100644 --- a/test/accessor_functions.jl +++ b/test/accessor_functions.jl @@ -151,14 +151,16 @@ let # Checks `continuous_events_toplevel` and `discrete_events_toplevel` (straightforward # as I stored the same single 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. - bot_cev = ModelingToolkit.SymbolicContinuousCallback(cevs[1], algeeqs = [O ~ (d + p_bot) * X_bot + Y]) - mid_dev = ModelingToolkit.SymbolicDiscreteCallback(devs[1], algeeqs = [O ~ (d + p_mid1) * X_mid1 + Y]) + bot_cev = ModelingToolkit.SymbolicContinuousCallback( + cevs[1], algeeqs = [O ~ (d + p_bot) * X_bot + Y]) + mid_dev = ModelingToolkit.SymbolicDiscreteCallback( + devs[1], algeeqs = [O ~ (d + p_mid1) * X_mid1 + Y]) @test all_sets_equal( continuous_events_toplevel.([sys_bot, sys_bot_comp, sys_bot_ss])..., [bot_cev]) @test all_sets_equal( discrete_events_toplevel.( - [sys_mid1, sys_mid1_comp, sys_mid1_ss])..., + [sys_mid1, sys_mid1_comp, sys_mid1_ss])..., [mid_dev]) @test all(sym_issubset( continuous_events_toplevel(sys), get_continuous_events(sys)) diff --git a/test/symbolic_events.jl b/test/symbolic_events.jl index e6c1df4363..e933843856 100644 --- a/test/symbolic_events.jl +++ b/test/symbolic_events.jl @@ -3,7 +3,7 @@ using SciMLStructures: canonicalize, Discrete using ModelingToolkit: SymbolicContinuousCallback, SymbolicContinuousCallbacks, SymbolicDiscreteCallback, - SymbolicDiscreteCallbacks, + SymbolicDiscreteCallbacks, get_callback, t_nounits as t, D_nounits as D, @@ -340,7 +340,7 @@ end D(v) ~ -9.8], t, continuous_events = root_eqs => affect) @test only(continuous_events(ball)) == - SymbolicContinuousCallback(Equation[x ~ 0], Equation[v ~ -Pre(v)]) + SymbolicContinuousCallback(Equation[x ~ 0], Equation[v ~ -Pre(v)]) ball = structural_simplify(ball) @test length(ModelingToolkit.continuous_events(ball)) == 1 @@ -373,13 +373,13 @@ end cb = get_callback(prob) @test cb isa ModelingToolkit.DiffEqCallbacks.VectorContinuousCallback @test getfield(ball, :continuous_events)[1] == - SymbolicContinuousCallback(Equation[x ~ 0], Equation[vx ~ -Pre(vx)]) + SymbolicContinuousCallback(Equation[x ~ 0], Equation[vx ~ -Pre(vx)]) @test getfield(ball, :continuous_events)[2] == - SymbolicContinuousCallback(Equation[y ~ -1.5, y ~ 1.5], Equation[vy ~ -Pre(vy)]) + SymbolicContinuousCallback(Equation[y ~ -1.5, y ~ 1.5], Equation[vy ~ -Pre(vy)]) cond = cb.condition out = [0.0, 0.0, 0.0] - p0 = 0. - t0 = 0. + p0 = 0.0 + t0 = 0.0 cond.f_iip(out, [0, 0, 0, 0], p0, t0) @test out ≈ [0, 1.5, -1.5] @@ -396,10 +396,11 @@ end # in this test, there are two variables affected by a single event. events = [[x ~ 0] => [vx ~ -Pre(vx), vy ~ -Pre(vy)]] - @named ball = ODESystem([D(x) ~ vx - D(y) ~ vy - D(vx) ~ -1 - D(vy) ~ 0], t; continuous_events = events) + @named ball = ODESystem( + [D(x) ~ vx + D(y) ~ vy + D(vx) ~ -1 + D(vy) ~ 0], t; continuous_events = events) ball_nosplit = structural_simplify(ball) ball = structural_simplify(ball) @@ -479,12 +480,13 @@ end end @testset "SDE/ODESystem Discrete Callbacks" begin - function testsol(sys, probtype, solver, u0, p, tspan; tstops = Float64[], paramtotest = nothing, + function testsol( + sys, probtype, solver, u0, p, tspan; tstops = Float64[], paramtotest = nothing, kwargs...) prob = probtype(complete(sys), u0, tspan, p; kwargs...) sol = solve(prob, solver(); tstops = tstops, abstol = 1e-10, reltol = 1e-10) @test isapprox(sol(1.0000000001)[1] - sol(0.999999999)[1], 1.0; rtol = 1e-6) - paramtotest === nothing || (@test sol.ps[paramtotest] == [0., 1.]) + paramtotest === nothing || (@test sol.ps[paramtotest] == [0.0, 1.0]) @test isapprox(sol(4.0)[1], 2 * exp(-2.0); rtol = 1e-6) sol end @@ -503,8 +505,7 @@ end ∂ₜ = D eqs = [∂ₜ(A) ~ -k * A] @named osys = ODESystem(eqs, t, [A], [k, t1, t2], discrete_events = [cb1, cb2]) - @named ssys = SDESystem(eqs, [0.0], t, [A], [k, t1, t2], - discrete_events = [cb1, cb2]) + @named ssys = SDESystem(eqs, [0.0], t, [A], [k, t1, t2], discrete_events = [cb1, cb2]) u0 = [A => 1.0] p = [k => 0.0, t1 => 1.0, t2 => 2.0] tspan = (0.0, 4.0) @@ -518,10 +519,12 @@ end @named ssys1 = SDESystem(eqs, [0.0], t, [A, B], [k, t1, t2], discrete_events = [cb1a, cb2]) u0′ = [A => 1.0, B => 0.0] - sol = testsol(osys1, ODEProblem, Tsit5, u0′, p, tspan; tstops = [1.0, 2.0], check_length = false, paramtotest = k) + sol = testsol(osys1, ODEProblem, Tsit5, u0′, p, tspan; + tstops = [1.0, 2.0], check_length = false, paramtotest = k) @test sol(1.0000001, idxs = B) == 2.0 - sol = testsol(ssys1, SDEProblem, RI5, u0′, p, tspan; tstops = [1.0, 2.0], check_length = false, paramtotest = k) + sol = testsol(ssys1, SDEProblem, RI5, u0′, p, tspan; tstops = [1.0, 2.0], + check_length = false, paramtotest = k) @test sol(1.0000001, idxs = B) == 2.0 # same as above - but with set-time event syntax @@ -589,7 +592,7 @@ end jprob = JumpProblem(jsys, dprob, Direct(); kwargs...) sol = solve(jprob, SSAStepper(); tstops = tstops) @test (sol(1.000000000001)[1] - sol(0.99999999999)[1]) == 1 - paramtotest === nothing || (@test sol.ps[paramtotest] == [0., 1.0]) + paramtotest === nothing || (@test sol.ps[paramtotest] == [0.0, 1.0]) @test sol(40.0)[1] == 0 sol end @@ -1282,53 +1285,56 @@ end eqs = [D(D(x)) ~ λ * x D(D(y)) ~ λ * y - g x^2 + y^2 ~ 1] - c_evt = [t ~ 5.] => [x ~ Pre(x) + 0.1] + c_evt = [t ~ 5.0] => [x ~ Pre(x) + 0.1] @mtkbuild pend = ODESystem(eqs, t, continuous_events = c_evt) - prob = ODEProblem(pend, [x => -1, y => 0], (0., 10.), [g => 1], guesses = [λ => 1]) + prob = ODEProblem(pend, [x => -1, y => 0], (0.0, 10.0), [g => 1], guesses = [λ => 1]) sol = solve(prob, FBDF()) @test ≈(sol(5.000001, idxs = x) - sol(4.999999, idxs = x), 0.1, rtol = 1e-4) @test ≈(sol(5.000001, idxs = x)^2 + sol(5.000001, idxs = y)^2, 1, rtol = 1e-4) # Implicit affect with Pre - c_evt = [t ~ 5.] => [x ~ Pre(x) + y^2] + c_evt = [t ~ 5.0] => [x ~ Pre(x) + y^2] @mtkbuild pend = ODESystem(eqs, t, continuous_events = c_evt) - prob = ODEProblem(pend, [x => 1, y => 0], (0., 10.), [g => 1], guesses = [λ => 1]) + prob = ODEProblem(pend, [x => 1, y => 0], (0.0, 10.0), [g => 1], guesses = [λ => 1]) sol = solve(prob, FBDF()) - @test ≈(sol(5.000001, idxs = y)^2 + sol(4.999999, idxs = x), sol(5.000001, idxs = x), rtol = 1e-4) + @test ≈(sol(5.000001, idxs = y)^2 + sol(4.999999, idxs = x), + sol(5.000001, idxs = x), rtol = 1e-4) @test ≈(sol(5.000001, idxs = x)^2 + sol(5.000001, idxs = y)^2, 1, rtol = 1e-4) # Impossible affect errors - c_evt = [t ~ 5.] => [x ~ Pre(x) + 2] + c_evt = [t ~ 5.0] => [x ~ Pre(x) + 2] @mtkbuild pend = ODESystem(eqs, t, continuous_events = c_evt) - prob = ODEProblem(pend, [x => 1, y => 0], (0., 10.), [g => 1], guesses = [λ => 1]) - @test_throws UnsolvableCallbackError sol = solve(prob, FBDF()) - + prob = ODEProblem(pend, [x => 1, y => 0], (0.0, 10.0), [g => 1], guesses = [λ => 1]) + @test_throws UnsolvableCallbackError sol=solve(prob, FBDF()) + # Changing both variables and parameters in the same affect. @parameters g(t) eqs = [D(D(x)) ~ λ * x D(D(y)) ~ λ * y - g x^2 + y^2 ~ 1] - c_evt = SymbolicContinuousCallback([t ~ 5.0], [x ~ Pre(x) + 0.1, g ~ Pre(g) + 1], discrete_parameters = [g], iv = t) + c_evt = SymbolicContinuousCallback( + [t ~ 5.0], [x ~ Pre(x) + 0.1, g ~ Pre(g) + 1], discrete_parameters = [g], iv = t) @mtkbuild pend = ODESystem(eqs, t, continuous_events = c_evt) - prob = ODEProblem(pend, [x => 1, y => 0], (0., 10.), [g => 1], guesses = [λ => 1]) + prob = ODEProblem(pend, [x => 1, y => 0], (0.0, 10.0), [g => 1], guesses = [λ => 1]) sol = solve(prob, FBDF()) @test sol.ps[g] ≈ [1, 2] - @test ≈(sol(5.0000001, idxs = x) - sol(4.999999, idxs = x), .1, rtol = 1e-4) + @test ≈(sol(5.0000001, idxs = x) - sol(4.999999, idxs = x), 0.1, rtol = 1e-4) # Proper re-initialization after parameter change eqs = [y ~ g^2 - x, D(x) ~ x] - c_evt = SymbolicContinuousCallback([t ~ 5.0], [x ~ Pre(x) + 1, g ~ Pre(g) + 1], discrete_parameters = [g], iv = t) + c_evt = SymbolicContinuousCallback( + [t ~ 5.0], [x ~ Pre(x) + 1, g ~ Pre(g) + 1], discrete_parameters = [g], iv = t) @mtkbuild sys = ODESystem(eqs, t, continuous_events = c_evt) - prob = ODEProblem(sys, [x => 1.0], (0., 10.), [g => 2]) + prob = ODEProblem(sys, [x => 1.0], (0.0, 10.0), [g => 2]) sol = solve(prob, FBDF()) - @test sol.ps[g] ≈ [2., 3.] + @test sol.ps[g] ≈ [2.0, 3.0] @test ≈(sol(5.00000001, idxs = x) - sol(4.9999999, idxs = x), 1; rtol = 1e-4) @test ≈(sol(5.00000001, idxs = y), 9 - sol(5.00000001, idxs = x), rtol = 1e-4) # Parameters that don't appear in affects should not be mutated. c_evt = [t ~ 5.0] => [x ~ Pre(x) + 1] @mtkbuild sys = ODESystem(eqs, t, continuous_events = c_evt) - prob = ODEProblem(sys, [x => 0.5], (0., 10.), [g => 2], guesses = [y => 0]) + prob = ODEProblem(sys, [x => 0.5], (0.0, 10.0), [g => 2], guesses = [y => 0]) sol = solve(prob, FBDF()) @test prob.ps[g] == sol.ps[g] end From 94d172c77ca6959b5abd962c6c5b8b5af3057344 Mon Sep 17 00:00:00 2001 From: vyudu Date: Thu, 27 Mar 2025 08:39:50 -0400 Subject: [PATCH 059/122] fix: fix typos and to_term differentials in affect equations --- src/systems/callbacks.jl | 20 +++++++++++++------- src/utils.jl | 12 ------------ 2 files changed, 13 insertions(+), 19 deletions(-) diff --git a/src/systems/callbacks.jl b/src/systems/callbacks.jl index 97141b3405..8bdc40a728 100644 --- a/src/systems/callbacks.jl +++ b/src/systems/callbacks.jl @@ -264,6 +264,8 @@ function make_affect(affect::Vector{Equation}; discrete_parameters::AbstractVect @warn "Affect equation $eq has no `Pre` operator. As such it will be interpreted as an algebraic equation to be satisfied after the callback. If you intended to use the value of a variable x before the affect, use Pre(x)." end collect_vars!(dvs, params, eq, iv; op = Pre) + diffvs = collect_applied_operators(eq, Differential) + union!(dvs, diffvs) end for eq in algeeqs collect_vars!(dvs, params, eq, iv) @@ -272,23 +274,28 @@ function make_affect(affect::Vector{Equation}; discrete_parameters::AbstractVect pre_params = filter(haspre ∘ value, params) sys_params = collect(setdiff(params, union(discrete_parameters, pre_params))) discretes = map(tovar, discrete_parameters) + dvs = collect(dvs) + _dvs = map(default_toterm, dvs) + aff_map = Dict(zip(discretes, discrete_parameters)) rev_map = Dict(zip(discrete_parameters, discretes)) - affect = Symbolics.fast_substitute(affect, rev_map) - algeeqs = Symbolics.fast_substitute(algeeqs, rev_map) + subs = merge(rev_map, Dict(zip(dvs, _dvs))) + affect = Symbolics.fast_substitute(affect, subs) + algeeqs = Symbolics.fast_substitute(algeeqs, subs) + @named affectsys = ImplicitDiscreteSystem( - vcat(affect, algeeqs), iv, collect(union(dvs, discretes)), + vcat(affect, algeeqs), iv, collect(union(_dvs, discretes)), collect(union(pre_params, sys_params))) - affectsys = structural_simplify(affect_sys; fully_determined = false) + affectsys = structural_simplify(affectsys; fully_determined = false) # get accessed parameters p from Pre(p) in the callback parameters accessed_params = filter(isparameter, map(unPre, collect(pre_params))) union!(accessed_params, sys_params) # add unknowns to the map - for u in dvs + for u in _dvs aff_map[u] = u end - AffectSystem(affectsys, collect(dvs), collect(accessed_params), + AffectSystem(affectsys, collect(_dvs), collect(accessed_params), collect(discrete_parameters), aff_map) end @@ -811,7 +818,6 @@ function compile_affect( end function wrap_save_discretes(f, save_idxs) - @show save_idxs let save_idxs = save_idxs if f === SciMLBase.INITIALIZE_DEFAULT (c, u, t, i) -> begin diff --git a/src/utils.jl b/src/utils.jl index 2b3cbedab0..1e0325cf18 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -513,18 +513,6 @@ function collect_applied_operators(x, op) end end -function find_derivatives!(vars, expr::Equation, f = identity) - (find_derivatives!(vars, expr.lhs, f); find_derivatives!(vars, expr.rhs, f); vars) -end -function find_derivatives!(vars, expr, f) - !iscall(O) && return vars - operation(O) isa Differential && push!(vars, f(O)) - for arg in arguments(O) - vars!(vars, arg) - end - return vars -end - """ $(TYPEDSIGNATURES) From 9e113811db3d47f72fcb6a23f887c4a63596fe3a Mon Sep 17 00:00:00 2001 From: vyudu Date: Thu, 27 Mar 2025 13:15:43 -0400 Subject: [PATCH 060/122] fix: add events to SDESystem after structural simplification --- src/systems/diffeqs/odesystem.jl | 2 +- src/systems/diffeqs/sdesystem.jl | 2 +- src/systems/systems.jl | 4 +--- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/systems/diffeqs/odesystem.jl b/src/systems/diffeqs/odesystem.jl index 4206155e4d..e8c6be8c3e 100644 --- a/src/systems/diffeqs/odesystem.jl +++ b/src/systems/diffeqs/odesystem.jl @@ -337,7 +337,7 @@ function ODESystem(deqs::AbstractVector{<:Equation}, iv, dvs, ps; end algeeqs = filter(eq -> eq.lhs isa Union{Symbolic, Number} && !is_diff_equation(eq), - flatten_equations(deqs)) + deqs) cont_callbacks = SymbolicContinuousCallbacks(continuous_events; algeeqs, iv) disc_callbacks = SymbolicDiscreteCallbacks(discrete_events; algeeqs, iv) diff --git a/src/systems/diffeqs/sdesystem.jl b/src/systems/diffeqs/sdesystem.jl index d1342e5650..c52f1d6559 100644 --- a/src/systems/diffeqs/sdesystem.jl +++ b/src/systems/diffeqs/sdesystem.jl @@ -271,7 +271,7 @@ function SDESystem(deqs::AbstractVector{<:Equation}, neqs::AbstractArray, iv, dv Wfact_t = RefValue(EMPTY_JAC) algeeqs = filter(eq -> eq.lhs isa Union{Symbolic, Number} && !is_diff_equation(eq), - flatten_equations(deqs)) + deqs) cont_callbacks = SymbolicContinuousCallbacks(continuous_events; algeeqs, iv) disc_callbacks = SymbolicDiscreteCallbacks(discrete_events; algeeqs, iv) if is_dde === nothing diff --git a/src/systems/systems.jl b/src/systems/systems.jl index 7e1ce3077c..1aace4de7f 100644 --- a/src/systems/systems.jl +++ b/src/systems/systems.jl @@ -161,9 +161,7 @@ function __structural_simplify(sys::AbstractSystem; simplify = false, get_iv(ode_sys), unknowns(ode_sys), parameters(ode_sys); name = nameof(ode_sys), is_scalar_noise, observed = observed(ode_sys), defaults = defaults(sys), parameter_dependencies = parameter_dependencies(sys), assertions = assertions(sys), - guesses = guesses(sys), initialization_eqs = initialization_equations(sys)) - @set! ssys.tearing_state = get_tearing_state(ode_sys) - return ssys + guesses = guesses(sys), initialization_eqs = initialization_equations(sys), continuous_events = continuous_events(sys), discrete_events = discrete_events(sys)) end end From 831a8d7527ce143c38289cd8194fc7cc44e4e31d Mon Sep 17 00:00:00 2001 From: vyudu Date: Fri, 28 Mar 2025 12:26:28 -0400 Subject: [PATCH 061/122] fix: add reinitalizealg back --- ext/MTKFMIExt.jl | 6 ++--- src/systems/callbacks.jl | 48 +++++++++++++++++++++++++----------- src/systems/model_parsing.jl | 16 +++++++++--- test/fmi/fmi.jl | 4 +-- test/symbolic_events.jl | 7 ++---- 5 files changed, 52 insertions(+), 29 deletions(-) diff --git a/ext/MTKFMIExt.jl b/ext/MTKFMIExt.jl index 912799c4f8..0baf37c34b 100644 --- a/ext/MTKFMIExt.jl +++ b/ext/MTKFMIExt.jl @@ -93,7 +93,7 @@ with the name `namespace__variable`. - `name`: The name of the system. """ function MTK.FMIComponent(::Val{Ver}; fmu = nothing, tolerance = 1e-6, - communication_step_size = nothing, type, name) where {Ver} + communication_step_size = nothing, type, name, reinitializealg = nothing) where {Ver} if Ver != 2 && Ver != 3 throw(ArgumentError("FMI Version must be `2` or `3`")) end @@ -238,7 +238,7 @@ function MTK.FMIComponent(::Val{Ver}; fmu = nothing, tolerance = 1e-6, finalize_affect = MTK.FunctionalAffect(fmiFinalize!, [], [wrapper], []) step_affect = MTK.FunctionalAffect(Returns(nothing), [], [], []) instance_management_callback = MTK.SymbolicDiscreteCallback( - (t != t - 1), step_affect; finalize = finalize_affect) + (t != t - 1), step_affect; finalize = finalize_affect, reinitializealg) push!(params, wrapper) append!(observed, der_observed) @@ -279,7 +279,7 @@ function MTK.FMIComponent(::Val{Ver}; fmu = nothing, tolerance = 1e-6, fmiCSStep!; observed = cb_observed, modified = cb_modified, ctx = _functor) instance_management_callback = MTK.SymbolicDiscreteCallback( communication_step_size, step_affect; initialize = initialize_affect, - finalize = finalize_affect) + finalize = finalize_affect, reinitializealg) # guarded in case there are no outputs/states and the variable is `[]`. symbolic_type(__mtk_internal_o) == NotSymbolic() || push!(params, __mtk_internal_o) diff --git a/src/systems/callbacks.jl b/src/systems/callbacks.jl index 8bdc40a728..6f7c7e38cc 100644 --- a/src/systems/callbacks.jl +++ b/src/systems/callbacks.jl @@ -200,7 +200,9 @@ Affects (i.e. `affect` and `affect_neg`) can be specified as either: + `ctx` is a user-defined context object passed to `f!` when invoked. This value is aliased for each problem. * A [`ImperativeAffect`](@ref); refer to its documentation for details. -DAEs will automatically be reinitialized. +`reinitializealg` is used to set how the system will be reinitialized after the callback. +- Symbolic affects have reinitialization built in. In this case the algorithm will default to SciMLBase.NoInit(), and should **not** be provided. +- Functional and imperative affects will default to SciMLBase.CheckInit(), which will error if the system is not properly reinitialized after the callback. If your system is a DAE, pass in an algorithm like SciMLBase.BrownBasicFullInit() to properly re-initialize. Initial and final affects can also be specified identically to positive and negative edge affects. Initialization affects will run as soon as the solver starts, while finalization affects will be executed after termination. @@ -212,6 +214,7 @@ struct SymbolicContinuousCallback <: AbstractCallback initialize::Union{Affect, Nothing} finalize::Union{Affect, Nothing} rootfind::Union{Nothing, SciMLBase.RootfindOpt} + reinitializealg::SciMLBase.DAEInitializationAlgorithm function SymbolicContinuousCallback( conditions::Union{Equation, Vector{Equation}}, @@ -221,13 +224,21 @@ struct SymbolicContinuousCallback <: AbstractCallback initialize = nothing, finalize = nothing, rootfind = SciMLBase.LeftRootFind, + reinitializealg = nothing, iv = nothing, algeeqs = Equation[]) conditions = (conditions isa AbstractVector) ? conditions : [conditions] + + if isnothing(reinitializealg) + any(a -> (a isa FunctionalAffect || a isa ImperativeAffect), [affect, affect_neg, initialize, finalize]) ? + reinitializealg = SciMLBase.CheckInit() : + reinitializealg = SciMLBase.NoInit() + end + new(conditions, make_affect(affect; iv, algeeqs, discrete_parameters), make_affect(affect_neg; iv, algeeqs, discrete_parameters), make_affect(initialize; iv, algeeqs, discrete_parameters), make_affect( - finalize; iv, algeeqs, discrete_parameters), rootfind) + finalize; iv, algeeqs, discrete_parameters), rootfind, reinitializealg) end # Default affect to nothing end @@ -424,16 +435,22 @@ struct SymbolicDiscreteCallback <: AbstractCallback affect::Union{Affect, Nothing} initialize::Union{Affect, Nothing} finalize::Union{Affect, Nothing} + reinitializealg::SciMLBase.DAEInitializationAlgorithm function SymbolicDiscreteCallback( condition, affect = nothing; initialize = nothing, finalize = nothing, iv = nothing, - algeeqs = Equation[], discrete_parameters = Any[]) + algeeqs = Equation[], discrete_parameters = Any[], reinitializealg = nothing) c = is_timed_condition(condition) ? condition : value(scalarize(condition)) + if isnothing(reinitializealg) + any(a -> (a isa FunctionalAffect || a isa ImperativeAffect), [affect, affect_neg, initialize, finalize]) ? + reinitializealg = SciMLBase.CheckInit() : + reinitializealg = SciMLBase.NoInit() + end new(c, make_affect(affect; iv, algeeqs, discrete_parameters), make_affect(initialize; iv, algeeqs, discrete_parameters), - make_affect(finalize; iv, algeeqs, discrete_parameters)) + make_affect(finalize; iv, algeeqs, discrete_parameters), reinitializealg) end # Default affect to nothing end @@ -525,7 +542,8 @@ function Base.hash(cb::AbstractCallback, s::UInt) !is_discrete(cb) && (s = hash(affect_negs(cb), s)) s = hash(initialize_affects(cb), s) s = hash(finalize_affects(cb), s) - !is_discrete(cb) ? hash(cb.rootfind, s) : s + !is_discrete(cb) && (s = hash(cb.rootfind, s)) + hash(cb.reinitializealg, s) end ########################### @@ -562,7 +580,7 @@ end function Base.:(==)(e1::AbstractCallback, e2::AbstractCallback) (is_discrete(e1) === is_discrete(e2)) || return false (isequal(e1.conditions, e2.conditions) && isequal(e1.affect, e2.affect) && - isequal(e1.initialize, e2.initialize) && isequal(e1.finalize, e2.finalize)) || + isequal(e1.initialize, e2.initialize) && isequal(e1.finalize, e2.finalize)) && isequal(e1.reinitializealg, e2.reinitializealg) || return false is_discrete(e1) || (isequal(e1.affect_neg, e2.affect_neg) && isequal(e1.rootfind, e2.rootfind)) @@ -664,15 +682,15 @@ function generate_continuous_callbacks(sys::AbstractSystem, dvs = unknowns(sys), ps = parameters(sys; initial_parameters = true); kwargs...) cbs = continuous_events(sys) isempty(cbs) && return nothing - cb_classes = Dict{SciMLBase.RootfindOpt, Vector{SymbolicContinuousCallback}}() + cb_classes = Dict{Tuple{SciMLBase.RootfindOpt, SciMLBase.DAEReinitializationAlg}, Vector{SymbolicContinuousCallback}}() # Sort the callbacks by their rootfinding method for cb in cbs - _cbs = get!(() -> SymbolicContinuousCallback[], cb_classes, cb.rootfind) + _cbs = get!(() -> SymbolicContinuousCallback[], cb_classes, (cb.rootfind, cb.reinitializealg)) push!(_cbs, cb) end - cb_classes = sort!(OrderedDict(cb_classes)) - compiled_callbacks = [generate_callback(cb, sys; kwargs...) for (rf, cb) in cb_classes] + sort!(OrderedDict(cb_classes), by = cb -> cb.rootfind) + compiled_callbacks = [generate_callback(cb, sys; kwargs...) for ((rf, reinit), cb) in cb_classes] if length(compiled_callbacks) == 1 return only(compiled_callbacks) else @@ -741,7 +759,7 @@ function generate_callback(cbs::Vector{SymbolicContinuousCallback}, sys; kwargs. return VectorContinuousCallback( trigger, affect, affect_neg, length(eqs); initialize, finalize, - rootfind = cbs[1].rootfind, initializealg = SciMLBase.NoInit()) + rootfind = cbs[1].rootfind, initializealg = cbs[1].reinitializealg) end function generate_callback(cb, sys; kwargs...) @@ -768,16 +786,16 @@ function generate_callback(cb, sys; kwargs...) if is_discrete(cb) if is_timed && conditions(cb) isa AbstractVector return PresetTimeCallback(trigger, affect; initialize, - finalize, initializealg = SciMLBase.NoInit()) + finalize, initializealg = cb.reinitializealg) elseif is_timed - return PeriodicCallback(affect, trigger; initialize, finalize, initializealg = SciMLBase.NoInit()) + return PeriodicCallback(affect, trigger; initialize, finalize, initializealg = cb.reinitializealg) else return DiscreteCallback(trigger, affect; initialize, - finalize, initializealg = SciMLBase.NoInit()) + finalize, initializealg = cb.reinitializealg) end else return ContinuousCallback(trigger, affect, affect_neg; initialize, finalize, - rootfind = cb.rootfind, initializealg = SciMLBase.NoInit()) + rootfind = cb.rootfind, initializealg = cb.reinitializealg) end end diff --git a/src/systems/model_parsing.jl b/src/systems/model_parsing.jl index 413dfda17d..6e1dfbbc66 100644 --- a/src/systems/model_parsing.jl +++ b/src/systems/model_parsing.jl @@ -77,8 +77,6 @@ function _model_macro(mod, fullname::Union{Expr, Symbol}, expr, isconnector) push!(exprs.args, :(systems = ModelingToolkit.AbstractSystem[])) push!(exprs.args, :(equations = Union{Equation, Vector{Equation}}[])) push!(exprs.args, :(defaults = Dict{Num, Union{Number, Symbol, Function}}())) - push!(exprs.args, :(disc_events = [])) - push!(exprs.args, :(cont_events = [])) Base.remove_linenums!(expr) for arg in expr.args @@ -120,8 +118,6 @@ function _model_macro(mod, fullname::Union{Expr, Symbol}, expr, isconnector) push!(exprs.args, :(push!(parameters, $(ps...)))) push!(exprs.args, :(push!(systems, $(comps...)))) push!(exprs.args, :(push!(variables, $(vs...)))) - push!(exprs.args, :(push!(disc_events, $(d_evts...)))) - push!(exprs.args, :(push!(cont_events, $(c_evts...)))) gui_metadata = isassigned(icon) > 0 ? GUIMetadata(GlobalRef(mod, name), icon[]) : GUIMetadata(GlobalRef(mod, name)) @@ -148,6 +144,18 @@ function _model_macro(mod, fullname::Union{Expr, Symbol}, expr, isconnector) isconnector && push!(exprs.args, :($Setfield.@set!(var"#___sys___".connector_type=$connector_type(var"#___sys___")))) + !isempty(c_evts) && push!(exprs.args, + :($Setfield.@set!(var"#___sys___".continuous_events=$SymbolicContinuousCallback.([ + $(c_evts...) + ])))) + + @show d_evts + !isempty(d_evts) && push!(exprs.args, + :($Setfield.@set!(var"#___sys___".discrete_events=$SymbolicDiscreteCallback.([ + $(d_evts...) + ])))) + + f = if length(where_types) == 0 :($(Symbol(:__, name, :__))(; name, $(kwargs...)) = $exprs) else diff --git a/test/fmi/fmi.jl b/test/fmi/fmi.jl index 0d10f3204a..e4c155270e 100644 --- a/test/fmi/fmi.jl +++ b/test/fmi/fmi.jl @@ -157,7 +157,7 @@ end @testset "v2, CS" begin fmu = loadFMU(joinpath(FMU_DIR, "SimpleAdder.fmu"); type = :CS) @named adder = MTK.FMIComponent( - Val(2); fmu, type = :CS, communication_step_size = 1e-6) + Val(2); fmu, type = :CS, communication_step_size = 1e-6, reinitializealg = BrownFullBasicInit()) @test MTK.isinput(adder.a) @test MTK.isinput(adder.b) @test MTK.isoutput(adder.out) @@ -209,7 +209,7 @@ end @testset "v3, CS" begin fmu = loadFMU(joinpath(FMU_DIR, "StateSpace.fmu"); type = :CS) @named sspace = MTK.FMIComponent( - Val(3); fmu, communication_step_size = 1e-6, type = :CS) + Val(3); fmu, communication_step_size = 1e-6, type = :CS, reinitializealg = BrownFullBasicInit()) @test MTK.isinput(sspace.u) @test MTK.isoutput(sspace.y) @test !MTK.isinput(sspace.x) && !MTK.isoutput(sspace.x) diff --git a/test/symbolic_events.jl b/test/symbolic_events.jl index e933843856..547993ec00 100644 --- a/test/symbolic_events.jl +++ b/test/symbolic_events.jl @@ -1204,7 +1204,7 @@ end @mtkmodel DECAY begin @parameters begin unrelated[1:2] = zeros(2) - k = 0.0 + k(t) = 0.0 end @variables begin x(t) = 10.0 @@ -1213,7 +1213,7 @@ end D(x) ~ -k * x end @discrete_events begin - (t == 1.0) => [k ~ 1.0] + (t == 1.0) => [k ~ 1.0], discrete_parameters = [k] end end @mtkbuild decay = DECAY() @@ -1338,7 +1338,4 @@ end sol = solve(prob, FBDF()) @test prob.ps[g] == sol.ps[g] end -# TODO: test: -# - Functional affects reinitialize correctly # - explicit equation of t in a functional affect -# - reinitialization after affects From deb4e31d2fe2a25f8e44624a407624e7787ea03e Mon Sep 17 00:00:00 2001 From: vyudu Date: Mon, 31 Mar 2025 14:47:35 -0400 Subject: [PATCH 062/122] fix: use discrete_parameters in tests --- src/systems/callbacks.jl | 6 +- src/systems/index_cache.jl | 7 +- src/systems/model_parsing.jl | 1 - test/parameter_dependencies.jl | 20 +- test/symbolic_events.jl | 726 ++++++++++++++++----------------- test/symbolic_parameters.jl | 6 +- 6 files changed, 385 insertions(+), 381 deletions(-) diff --git a/src/systems/callbacks.jl b/src/systems/callbacks.jl index 6f7c7e38cc..9be33b0800 100644 --- a/src/systems/callbacks.jl +++ b/src/systems/callbacks.jl @@ -444,7 +444,7 @@ struct SymbolicDiscreteCallback <: AbstractCallback c = is_timed_condition(condition) ? condition : value(scalarize(condition)) if isnothing(reinitializealg) - any(a -> (a isa FunctionalAffect || a isa ImperativeAffect), [affect, affect_neg, initialize, finalize]) ? + any(a -> (a isa FunctionalAffect || a isa ImperativeAffect), [affect, initialize, finalize]) ? reinitializealg = SciMLBase.CheckInit() : reinitializealg = SciMLBase.NoInit() end @@ -682,14 +682,14 @@ function generate_continuous_callbacks(sys::AbstractSystem, dvs = unknowns(sys), ps = parameters(sys; initial_parameters = true); kwargs...) cbs = continuous_events(sys) isempty(cbs) && return nothing - cb_classes = Dict{Tuple{SciMLBase.RootfindOpt, SciMLBase.DAEReinitializationAlg}, Vector{SymbolicContinuousCallback}}() + cb_classes = Dict{Tuple{SciMLBase.RootfindOpt, SciMLBase.DAEInitializationAlgorithm}, Vector{SymbolicContinuousCallback}}() # Sort the callbacks by their rootfinding method for cb in cbs _cbs = get!(() -> SymbolicContinuousCallback[], cb_classes, (cb.rootfind, cb.reinitializealg)) push!(_cbs, cb) end - sort!(OrderedDict(cb_classes), by = cb -> cb.rootfind) + sort!(OrderedDict(cb_classes), by = cb -> cb[1]) compiled_callbacks = [generate_callback(cb, sys; kwargs...) for ((rf, reinit), cb) in cb_classes] if length(compiled_callbacks) == 1 return only(compiled_callbacks) diff --git a/src/systems/index_cache.jl b/src/systems/index_cache.jl index 948af4dfa5..c689b351e0 100644 --- a/src/systems/index_cache.jl +++ b/src/systems/index_cache.jl @@ -345,8 +345,13 @@ function IndexCache(sys::AbstractSystem) vs = vars(eq.rhs; op = Nothing) timeseries = TimeseriesSetType() if is_time_dependent(sys) + unknown_set = Set(unknowns(sys)) for v in vs - if (idx = get(disc_idxs, v, nothing)) !== nothing + if in(v, unknown_set) + empty!(timeseries) + push!(timeseries, ContinuousTimeseries()) + break + elseif (idx = get(disc_idxs, v, nothing)) !== nothing push!(timeseries, idx.clock_idx) elseif iscall(v) && operation(v) === getindex && (idx = get(disc_idxs, arguments(v)[1], nothing)) !== nothing diff --git a/src/systems/model_parsing.jl b/src/systems/model_parsing.jl index 6e1dfbbc66..782b1625e4 100644 --- a/src/systems/model_parsing.jl +++ b/src/systems/model_parsing.jl @@ -149,7 +149,6 @@ function _model_macro(mod, fullname::Union{Expr, Symbol}, expr, isconnector) $(c_evts...) ])))) - @show d_evts !isempty(d_evts) && push!(exprs.args, :($Setfield.@set!(var"#___sys___".discrete_events=$SymbolicDiscreteCallback.([ $(d_evts...) diff --git a/test/parameter_dependencies.jl b/test/parameter_dependencies.jl index cc2f137392..89f0dc1e27 100644 --- a/test/parameter_dependencies.jl +++ b/test/parameter_dependencies.jl @@ -1,6 +1,6 @@ using ModelingToolkit using Test -using ModelingToolkit: t_nounits as t, D_nounits as D +using ModelingToolkit: t_nounits as t, D_nounits as D, SymbolicDiscreteCallback, SymbolicContinuousCallback using OrdinaryDiffEq using StochasticDiffEq using JumpProcesses @@ -10,14 +10,14 @@ using SymbolicIndexingInterface using NonlinearSolve @testset "ODESystem with callbacks" begin - @parameters p1=1.0 p2 + @parameters p1(t)=1.0 p2 @variables x(t) - cb1 = [x ~ 2.0] => [p1 ~ 2.0] # triggers at t=-2+√6 + 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]] end cb2 = [x ~ 4.0] => (affect1!, [], [p1, p2], [p1]) # triggers at t=-2+√7 - cb3 = [1.0] => [p1 ~ 5.0] + cb3 = SymbolicDiscreteCallback([1.0] => [p1 ~ 5.0], discrete_parameters = [p1]) @mtkbuild sys = ODESystem( [D(x) ~ p1 * t + p2], @@ -203,7 +203,7 @@ end @testset "Clock system" begin dt = 0.1 @variables x(t) y(t) u(t) yd(t) ud(t) r(t) z(t) - @parameters kp kq + @parameters kp(t) kq d = Clock(dt) k = ShiftIndex(d) @@ -225,7 +225,7 @@ end @test_nowarn solve(prob, Tsit5()) @mtkbuild sys = ODESystem(eqs, t; parameter_dependencies = [kq => 2kp], - discrete_events = [[0.5] => [kp ~ 2.0]]) + discrete_events = [SymbolicDiscreteCallback([0.5] => [kp ~ 2.0], discrete_parameters = [kp])]) prob = ODEProblem(sys, [x => 0.0, y => 0.0], (0.0, Tf), [kp => 1.0; z(k - 1) => 3.0; yd(k - 1) => 0.0; z(k - 2) => 4.0; yd(k - 2) => 2.0]) @@ -245,7 +245,7 @@ end end @testset "SDESystem" begin - @parameters σ ρ β + @parameters σ(t) ρ β @variables x(t) y(t) z(t) eqs = [D(x) ~ σ * (y - x), @@ -269,7 +269,7 @@ end @named sys = ODESystem(eqs, t) @named sdesys = SDESystem(sys, noiseeqs; parameter_dependencies = [ρ => 2σ], - discrete_events = [[10.0] => [σ ~ 15.0]]) + discrete_events = [SymbolicDiscreteCallback([10.0] => [σ ~ 15.0], discrete_parameters = [σ])]) sdesys = complete(sdesys) prob = SDEProblem( sdesys, [x => 1.0, y => 0.0, z => 0.0], (0.0, 100.0), [σ => 10.0, β => 2.33]) @@ -283,7 +283,7 @@ end @testset "JumpSystem" begin rng = StableRNG(12345) - @parameters β γ + @parameters β γ(t) @constants h = 1 @variables S(t) I(t) R(t) rate₁ = β * S * I * h @@ -308,7 +308,7 @@ end @named js2 = JumpSystem( [j₁, j₃], t, [S, I, R], [γ]; parameter_dependencies = [β => 0.01γ], - discrete_events = [[10.0] => [γ ~ 0.02]]) + discrete_events = [SymbolicDiscreteCallback([10.0] => [γ ~ 0.02], discrete_parameters = [γ])]) js2 = complete(js2) dprob = DiscreteProblem(js2, u₀map, tspan, parammap) jprob = JumpProblem(js2, dprob, Direct(), save_positions = (false, false), rng = rng) diff --git a/test/symbolic_events.jl b/test/symbolic_events.jl index 547993ec00..49c02bacc1 100644 --- a/test/symbolic_events.jl +++ b/test/symbolic_events.jl @@ -316,16 +316,16 @@ end @test out[2] ≈ 1 # signature is u,p,t sol = solve(prob, Tsit5()) - @test minimum(t -> abs(t - 1), sol.t) < 1e-10 # test that the solver stepped at the first root - @test minimum(t -> abs(t - 2), sol.t) < 1e-10 # test that the solver stepped at the second root + @test minimum(t -> abs(t - 1), sol.t) < 1e-9 # test that the solver stepped at the first root + @test minimum(t -> abs(t - 2), sol.t) < 1e-9 # test that the solver stepped at the second root @named sys = ODESystem(eqs, t, continuous_events = [x ~ 1, x ~ 2]) # two root eqs using the same unknown sys = complete(sys) prob = ODEProblem(sys, Pair[], (0.0, 3.0)) @test get_callback(prob) isa ModelingToolkit.DiffEqCallbacks.VectorContinuousCallback sol = solve(prob, Tsit5()) - @test minimum(t -> abs(t - 1), sol.t) < 1e-10 # test that the solver stepped at the first root - @test minimum(t -> abs(t - 2), sol.t) < 1e-10 # test that the solver stepped at the second root + @test minimum(t -> abs(t - 1), sol.t) < 1e-9 # test that the solver stepped at the first root + @test minimum(t -> abs(t - 2), sol.t) < 1e-9 # test that the solver stepped at the second root end @testset "Bouncing Ball" begin @@ -429,7 +429,7 @@ end prob = ODEProblem(sys, zeros(2), (0.0, 5.1)) sol = solve(prob, Tsit5()) @test all(minimum((0:0.1:5) .- sol.t', dims = 2) .< 0.0001) # test that the solver stepped every 0.1s as dictated by event - @test sol([0.25])[vmeasured][] == sol([0.23])[vmeasured][] # test the hold property + @test sol([0.25 - eps()])[vmeasured][] == sol([0.23])[vmeasured][] # test the hold property end ## https://github.com/SciML/ModelingToolkit.jl/issues/1528 @@ -823,363 +823,363 @@ end @test sign.(cos.(3 * (required_crossings_c2 .+ 1e-6))) == sign.(last.(cr2)) end # -#@testset "Discrete event reinitialization (#3142)" begin -# @connector LiquidPort begin -# p(t)::Float64, [description = "Set pressure in bar", -# guess = 1.01325] -# Vdot(t)::Float64, -# [description = "Volume flow rate in L/min", -# guess = 0.0, -# connect = Flow] -# end -# -# @mtkmodel PressureSource begin -# @components begin -# port = LiquidPort() -# end -# @parameters begin -# p_set::Float64 = 1.01325, [description = "Set pressure in bar"] -# end -# @equations begin -# port.p ~ p_set -# end -# end -# -# @mtkmodel BinaryValve begin -# @constants begin -# p_ref::Float64 = 1.0, [description = "Reference pressure drop in bar"] -# ρ_ref::Float64 = 1000.0, [description = "Reference density in kg/m^3"] -# end -# @components begin -# port_in = LiquidPort() -# port_out = LiquidPort() -# end -# @parameters begin -# k_V::Float64 = 1.0, [description = "Valve coefficient in L/min/bar"] -# k_leakage::Float64 = 1e-08, [description = "Leakage coefficient in L/min/bar"] -# ρ::Float64 = 1000.0, [description = "Density in kg/m^3"] -# end -# @variables begin -# S(t)::Float64, [description = "Valve state", guess = 1.0, irreducible = true] -# Δp(t)::Float64, [description = "Pressure difference in bar", guess = 1.0] -# Vdot(t)::Float64, [description = "Volume flow rate in L/min", guess = 1.0] -# end -# @equations begin -# # Port handling -# port_in.Vdot ~ -Vdot -# port_out.Vdot ~ Vdot -# Δp ~ port_in.p - port_out.p -# # System behavior -# D(S) ~ 0.0 -# Vdot ~ S * k_V * sign(Δp) * sqrt(abs(Δp) / p_ref * ρ_ref / ρ) + k_leakage * Δp # softplus alpha function to avoid negative values under the sqrt -# end -# end -# -# # Test System -# @mtkmodel TestSystem begin -# @components begin -# pressure_source_1 = PressureSource(p_set = 2.0) -# binary_valve_1 = BinaryValve(S = 1.0, k_leakage = 0.0) -# binary_valve_2 = BinaryValve(S = 1.0, k_leakage = 0.0) -# pressure_source_2 = PressureSource(p_set = 1.0) -# end -# @equations begin -# connect(pressure_source_1.port, binary_valve_1.port_in) -# connect(binary_valve_1.port_out, binary_valve_2.port_in) -# connect(binary_valve_2.port_out, pressure_source_2.port) -# end -# @discrete_events begin -# [30] => [binary_valve_1.S ~ 0.0, binary_valve_2.Δp ~ 0.0] -# [60] => [ -# binary_valve_1.S ~ 1.0, binary_valve_2.S ~ 0.0, binary_valve_2.Δp ~ 1.0] -# [120] => [binary_valve_1.S ~ 0.0, binary_valve_2.Δp ~ 0.0] -# end -# end -# -# # Test Simulation -# @mtkbuild sys = TestSystem() -# -# # Test Simulation -# prob = ODEProblem(sys, [], (0.0, 150.0)) -# sol = solve(prob) -# @test sol[end] == [0.0, 0.0, 0.0] -#end -# -#@testset "Discrete variable timeseries" begin -# @variables x(t) -# @parameters a(t) b(t) c(t) -# cb1 = [x ~ 1.0] => [a ~ -Pre(a)] -# function save_affect!(integ, u, p, ctx) -# integ.ps[p.b] = 5.0 -# end -# cb2 = [x ~ 0.5] => (save_affect!, [], [b], [b], nothing) -# cb3 = 1.0 => [c ~ t] -# -# @mtkbuild sys = ODESystem(D(x) ~ cos(t), t, [x], [a, b, c]; -# continuous_events = [cb1, cb2], discrete_events = [cb3]) -# prob = ODEProblem(sys, [x => 1.0], (0.0, 2pi), [a => 1.0, b => 2.0, c => 0.0]) -# @test sort(canonicalize(Discrete(), prob.p)[1]) == [0.0, 1.0, 2.0] -# sol = solve(prob, Tsit5()) -# -# @test sol[a] == [1.0, -1.0] -# @test sol[b] == [2.0, 5.0, 5.0] -# @test sol[c] == [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0] -#end -# -#@testset "Heater" begin -# @variables temp(t) -# params = @parameters furnace_on_threshold=0.5 furnace_off_threshold=0.7 furnace_power=1.0 leakage=0.1 furnace_on::Bool=false -# eqs = [ -# D(temp) ~ furnace_on * furnace_power - temp^2 * leakage -# ] -# -# furnace_off = ModelingToolkit.SymbolicContinuousCallback( -# [temp ~ furnace_off_threshold], -# ModelingToolkit.ImperativeAffect(modified = (; furnace_on)) do x, o, i, c -# @set! x.furnace_on = false -# end) -# furnace_enable = ModelingToolkit.SymbolicContinuousCallback( -# [temp ~ furnace_on_threshold], -# ModelingToolkit.ImperativeAffect(modified = (; furnace_on)) do x, o, i, c -# @set! x.furnace_on = true -# end) -# @named sys = ODESystem( -# eqs, t, [temp], params; continuous_events = [furnace_off, furnace_enable]) -# ss = structural_simplify(sys) -# prob = ODEProblem(ss, [temp => 0.0, furnace_on => true], (0.0, 100.0)) -# sol = solve(prob, Tsit5(); dtmax = 0.01) -# @test all(sol[temp][sol.t .> 1.0] .<= 0.79) && all(sol[temp][sol.t .> 1.0] .>= 0.49) -# -# furnace_off = ModelingToolkit.SymbolicContinuousCallback( -# [temp ~ furnace_off_threshold], -# ModelingToolkit.ImperativeAffect(modified = (; furnace_on)) do x, o, c, i -# @set! x.furnace_on = false -# end; initialize = ModelingToolkit.ImperativeAffect(modified = (; -# temp)) do x, o, c, i -# @set! x.temp = 0.2 -# end) -# furnace_enable = ModelingToolkit.SymbolicContinuousCallback( -# [temp ~ furnace_on_threshold], -# ModelingToolkit.ImperativeAffect(modified = (; furnace_on)) do x, o, c, i -# @set! x.furnace_on = true -# end) -# @named sys = ODESystem( -# eqs, t, [temp], params; continuous_events = [furnace_off, furnace_enable]) -# ss = structural_simplify(sys) -# prob = ODEProblem(ss, [temp => 0.0, furnace_on => true], (0.0, 100.0)) -# sol = solve(prob, Tsit5(); dtmax = 0.01) -# @test all(sol[temp][sol.t .> 1.0] .<= 0.79) && all(sol[temp][sol.t .> 1.0] .>= 0.49) -# @test all(sol[temp][sol.t .!= 0.0] .<= 0.79) && all(sol[temp][sol.t .!= 0.0] .>= 0.2) -#end -# -#@testset "ImperativeAffect errors and warnings" begin -# @variables temp(t) -# params = @parameters furnace_on_threshold=0.5 furnace_off_threshold=0.7 furnace_power=1.0 leakage=0.1 furnace_on::Bool=false -# eqs = [ -# D(temp) ~ furnace_on * furnace_power - temp^2 * leakage -# ] -# -# furnace_off = ModelingToolkit.SymbolicContinuousCallback( -# [temp ~ furnace_off_threshold], -# ModelingToolkit.ImperativeAffect( -# modified = (; furnace_on), observed = (; furnace_on)) do x, o, c, i -# @set! x.furnace_on = false -# end) -# @named sys = ODESystem(eqs, t, [temp], params; continuous_events = [furnace_off]) -# ss = structural_simplify(sys) -# @test_logs (:warn, -# "The symbols Any[:furnace_on] are declared as both observed and modified; this is a code smell because it becomes easy to confuse them and assign/not assign a value.") prob=ODEProblem( -# ss, [temp => 0.0, furnace_on => true], (0.0, 100.0)) -# -# @variables tempsq(t) # trivially eliminated -# eqs = [tempsq ~ temp^2 -# D(temp) ~ furnace_on * furnace_power - temp^2 * leakage] -# -# furnace_off = ModelingToolkit.SymbolicContinuousCallback( -# [temp ~ furnace_off_threshold], -# ModelingToolkit.ImperativeAffect( -# modified = (; furnace_on, tempsq), observed = (; furnace_on)) do x, o, c, i -# @set! x.furnace_on = false -# end) -# @named sys = ODESystem( -# eqs, t, [temp, tempsq], params; continuous_events = [furnace_off]) -# ss = structural_simplify(sys) -# @test_throws "refers to missing variable(s)" prob=ODEProblem( -# ss, [temp => 0.0, furnace_on => true], (0.0, 100.0)) -# -# @parameters not_actually_here -# furnace_off = ModelingToolkit.SymbolicContinuousCallback( -# [temp ~ furnace_off_threshold], -# ModelingToolkit.ImperativeAffect(modified = (; furnace_on), -# observed = (; furnace_on, not_actually_here)) do x, o, c, i -# @set! x.furnace_on = false -# end) -# @named sys = ODESystem( -# eqs, t, [temp, tempsq], params; continuous_events = [furnace_off]) -# ss = structural_simplify(sys) -# @test_throws "refers to missing variable(s)" prob=ODEProblem( -# ss, [temp => 0.0, furnace_on => true], (0.0, 100.0)) -# -# furnace_off = ModelingToolkit.SymbolicContinuousCallback( -# [temp ~ furnace_off_threshold], -# ModelingToolkit.ImperativeAffect(modified = (; furnace_on), -# observed = (; furnace_on)) do x, o, c, i -# return (; fictional2 = false) -# end) -# @named sys = ODESystem( -# eqs, t, [temp, tempsq], params; continuous_events = [furnace_off]) -# ss = structural_simplify(sys) -# prob = ODEProblem( -# ss, [temp => 0.0, furnace_on => true], (0.0, 100.0)) -# @test_throws "Tried to write back to" solve(prob, Tsit5()) -#end -# -#@testset "Quadrature" begin -# @variables theta(t) omega(t) -# params = @parameters qA=0 qB=0 hA=0 hB=0 cnt::Int=0 -# eqs = [D(theta) ~ omega -# omega ~ 1.0] -# function decoder(oldA, oldB, newA, newB) -# state = (oldA, oldB, newA, newB) -# if state == (0, 0, 1, 0) || state == (1, 0, 1, 1) || state == (1, 1, 0, 1) || -# state == (0, 1, 0, 0) -# return 1 -# elseif state == (0, 0, 0, 1) || state == (0, 1, 1, 1) || state == (1, 1, 1, 0) || -# state == (1, 0, 0, 0) -# return -1 -# elseif state == (0, 0, 0, 0) || state == (0, 1, 0, 1) || state == (1, 0, 1, 0) || -# state == (1, 1, 1, 1) -# return 0 -# else -# return 0 # err is interpreted as no movement -# end -# end -# qAevt = ModelingToolkit.SymbolicContinuousCallback([cos(100 * theta) ~ 0], -# ModelingToolkit.ImperativeAffect((; qA, hA, hB, cnt), (; qB)) do x, o, c, i -# @set! x.hA = x.qA -# @set! x.hB = o.qB -# @set! x.qA = 1 -# @set! x.cnt += decoder(x.hA, x.hB, x.qA, o.qB) -# x -# end, -# affect_neg = ModelingToolkit.ImperativeAffect( -# (; qA, hA, hB, cnt), (; qB)) do x, o, c, i -# @set! x.hA = x.qA -# @set! x.hB = o.qB -# @set! x.qA = 0 -# @set! x.cnt += decoder(x.hA, x.hB, x.qA, o.qB) -# x -# end; rootfind = SciMLBase.RightRootFind) -# qBevt = ModelingToolkit.SymbolicContinuousCallback([cos(100 * theta - π / 2) ~ 0], -# ModelingToolkit.ImperativeAffect((; qB, hA, hB, cnt), (; qA)) do x, o, c, i -# @set! x.hA = o.qA -# @set! x.hB = x.qB -# @set! x.qB = 1 -# @set! x.cnt += decoder(x.hA, x.hB, o.qA, x.qB) -# x -# end, -# affect_neg = ModelingToolkit.ImperativeAffect( -# (; qB, hA, hB, cnt), (; qA)) do x, o, c, i -# @set! x.hA = o.qA -# @set! x.hB = x.qB -# @set! x.qB = 0 -# @set! x.cnt += decoder(x.hA, x.hB, o.qA, x.qB) -# x -# end; rootfind = SciMLBase.RightRootFind) -# @named sys = ODESystem( -# eqs, t, [theta, omega], params; continuous_events = [qAevt, qBevt]) -# ss = structural_simplify(sys) -# prob = ODEProblem(ss, [theta => 1e-5], (0.0, pi)) -# sol = solve(prob, Tsit5(); dtmax = 0.01) -# @test getp(sol, cnt)(sol) == 198 # we get 2 pulses per phase cycle (cos 0 crossing) and we go to 100 cycles; we miss a few due to the initial state -#end -# -#@testset "Initialization" begin -# @variables x(t) -# seen = false -# f = ModelingToolkit.FunctionalAffect( -# f = (i, u, p, c) -> seen = true, sts = [], pars = [], discretes = []) -# cb1 = ModelingToolkit.SymbolicContinuousCallback( -# [x ~ 0], nothing, initialize = [x ~ 1.5], finalize = f) -# @mtkbuild sys = ODESystem(D(x) ~ -1, t, [x], []; continuous_events = [cb1]) -# prob = ODEProblem(sys, [x => 1.0], (0.0, 2), []) -# sol = solve(prob, Tsit5(); dtmax = 0.01) -# @test sol[x][1] ≈ 1.0 -# @test sol[x][2] ≈ 1.5 # the initialize affect has been applied -# @test seen == true -# -# @variables x(t) -# seen = false -# f = ModelingToolkit.FunctionalAffect( -# f = (i, u, p, c) -> seen = true, sts = [], pars = [], discretes = []) -# 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 = []) -# cb2 = ModelingToolkit.SymbolicContinuousCallback( -# [x ~ 0.1], nothing, initialize = a, finalize = b) -# @mtkbuild sys = ODESystem(D(x) ~ -1, t, [x], []; continuous_events = [cb1, cb2]) -# prob = ODEProblem(sys, [x => 1.0], (0.0, 2), []) -# sol = solve(prob, Tsit5()) -# @test sol[x][1] ≈ 1.0 -# @test sol[x][2] ≈ 1.5 # the initialize affect has been applied -# @test seen == true -# @test inited == true -# @test finaled == true -# -# #periodic -# inited = false -# finaled = false -# cb3 = ModelingToolkit.SymbolicDiscreteCallback( -# 1.0, [x ~ 2], initialize = a, finalize = b) -# @mtkbuild sys = ODESystem(D(x) ~ -1, t, [x], []; discrete_events = [cb3]) -# prob = ODEProblem(sys, [x => 1.0], (0.0, 2), []) -# sol = solve(prob, Tsit5()) -# @test inited == true -# @test finaled == true -# @test isapprox(sol[x][3], 0.0, atol = 1e-9) -# @test sol[x][4] ≈ 2.0 -# @test sol[x][5] ≈ 1.0 -# -# seen = false -# inited = false -# finaled = false -# cb3 = ModelingToolkit.SymbolicDiscreteCallback(1.0, f, initialize = a, finalize = b) -# @mtkbuild sys = ODESystem(D(x) ~ -1, t, [x], []; discrete_events = [cb3]) -# prob = ODEProblem(sys, [x => 1.0], (0.0, 2), []) -# sol = solve(prob, Tsit5()) -# @test seen == true -# @test inited == true -# -# #preset -# seen = false -# inited = false -# finaled = false -# cb3 = ModelingToolkit.SymbolicDiscreteCallback([1.0], f, initialize = a, finalize = b) -# @mtkbuild sys = ODESystem(D(x) ~ -1, t, [x], []; discrete_events = [cb3]) -# prob = ODEProblem(sys, [x => 1.0], (0.0, 2), []) -# sol = solve(prob, Tsit5()) -# @test seen == true -# @test inited == true -# @test finaled == true -# -# #equational -# seen = false -# inited = false -# finaled = false -# cb3 = ModelingToolkit.SymbolicDiscreteCallback( -# t == 1.0, f, initialize = a, finalize = b) -# @mtkbuild sys = ODESystem(D(x) ~ -1, t, [x], []; discrete_events = [cb3]) -# prob = ODEProblem(sys, [x => 1.0], (0.0, 2), []) -# sol = solve(prob, Tsit5(); tstops = 1.0) -# @test seen == true -# @test inited == true -# @test finaled == true -#end +@testset "Discrete event reinitialization (#3142)" begin + @connector LiquidPort begin + p(t)::Float64, [description = "Set pressure in bar", + guess = 1.01325] + Vdot(t)::Float64, + [description = "Volume flow rate in L/min", + guess = 0.0, + connect = Flow] + end + + @mtkmodel PressureSource begin + @components begin + port = LiquidPort() + end + @parameters begin + p_set::Float64 = 1.01325, [description = "Set pressure in bar"] + end + @equations begin + port.p ~ p_set + end + end + + @mtkmodel BinaryValve begin + @constants begin + p_ref::Float64 = 1.0, [description = "Reference pressure drop in bar"] + ρ_ref::Float64 = 1000.0, [description = "Reference density in kg/m^3"] + end + @components begin + port_in = LiquidPort() + port_out = LiquidPort() + end + @parameters begin + k_V::Float64 = 1.0, [description = "Valve coefficient in L/min/bar"] + k_leakage::Float64 = 1e-08, [description = "Leakage coefficient in L/min/bar"] + ρ::Float64 = 1000.0, [description = "Density in kg/m^3"] + end + @variables begin + S(t)::Float64, [description = "Valve state", guess = 1.0, irreducible = true] + Δp(t)::Float64, [description = "Pressure difference in bar", guess = 1.0] + Vdot(t)::Float64, [description = "Volume flow rate in L/min", guess = 1.0] + end + @equations begin + # Port handling + port_in.Vdot ~ -Vdot + port_out.Vdot ~ Vdot + Δp ~ port_in.p - port_out.p + # System behavior + D(S) ~ 0.0 + Vdot ~ S * k_V * sign(Δp) * sqrt(abs(Δp) / p_ref * ρ_ref / ρ) + k_leakage * Δp # softplus alpha function to avoid negative values under the sqrt + end + end + + # Test System + @mtkmodel TestSystem begin + @components begin + pressure_source_1 = PressureSource(p_set = 2.0) + binary_valve_1 = BinaryValve(S = 1.0, k_leakage = 0.0) + binary_valve_2 = BinaryValve(S = 1.0, k_leakage = 0.0) + pressure_source_2 = PressureSource(p_set = 1.0) + end + @equations begin + connect(pressure_source_1.port, binary_valve_1.port_in) + connect(binary_valve_1.port_out, binary_valve_2.port_in) + connect(binary_valve_2.port_out, pressure_source_2.port) + end + @discrete_events begin + [30] => [binary_valve_1.S ~ 0.0, binary_valve_2.Δp ~ 0.0] + [60] => [ + binary_valve_1.S ~ 1.0, binary_valve_2.S ~ 0.0, binary_valve_2.Δp ~ 1.0] + [120] => [binary_valve_1.S ~ 0.0, binary_valve_2.Δp ~ 0.0] + end + end + + # Test Simulation + @mtkbuild sys = TestSystem() + + # Test Simulation + prob = ODEProblem(sys, [], (0.0, 150.0)) + sol = solve(prob) + @test sol[end] == [0.0, 0.0, 0.0] +end + +@testset "Discrete variable timeseries" begin + @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 + end + cb2 = [x ~ 0.5] => (save_affect!, [], [b], [b], nothing) + cb3 = SymbolicDiscreteCallback(1.0 => [c ~ t], discrete_parameters = [c]) + + @mtkbuild sys = ODESystem(D(x) ~ cos(t), t, [x], [a, b, c]; + continuous_events = [cb1, cb2], discrete_events = [cb3]) + prob = ODEProblem(sys, [x => 1.0], (0.0, 2pi), [a => 1.0, b => 2.0, c => 0.0]) + @test sort(canonicalize(Discrete(), prob.p)[1]) == [0.0, 1.0, 2.0] + sol = solve(prob, Tsit5()) + + @test sol[a] == [1.0, -1.0] + @test sol[b] == [2.0, 5.0, 5.0] + @test sol[c] == [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0] +end + +@testset "Heater" begin + @variables temp(t) + params = @parameters furnace_on_threshold=0.5 furnace_off_threshold=0.7 furnace_power=1.0 leakage=0.1 furnace_on::Bool=false + eqs = [ + D(temp) ~ furnace_on * furnace_power - temp^2 * leakage + ] + + furnace_off = ModelingToolkit.SymbolicContinuousCallback( + [temp ~ furnace_off_threshold], + ModelingToolkit.ImperativeAffect(modified = (; furnace_on)) do x, o, i, c + @set! x.furnace_on = false + end) + furnace_enable = ModelingToolkit.SymbolicContinuousCallback( + [temp ~ furnace_on_threshold], + ModelingToolkit.ImperativeAffect(modified = (; furnace_on)) do x, o, i, c + @set! x.furnace_on = true + end) + @named sys = ODESystem( + eqs, t, [temp], params; continuous_events = [furnace_off, furnace_enable]) + ss = structural_simplify(sys) + prob = ODEProblem(ss, [temp => 0.0, furnace_on => true], (0.0, 100.0)) + sol = solve(prob, Tsit5(); dtmax = 0.01) + @test all(sol[temp][sol.t .> 1.0] .<= 0.79) && all(sol[temp][sol.t .> 1.0] .>= 0.49) + + furnace_off = ModelingToolkit.SymbolicContinuousCallback( + [temp ~ furnace_off_threshold], + ModelingToolkit.ImperativeAffect(modified = (; furnace_on)) do x, o, c, i + @set! x.furnace_on = false + end; initialize = ModelingToolkit.ImperativeAffect(modified = (; + temp)) do x, o, c, i + @set! x.temp = 0.2 + end) + furnace_enable = ModelingToolkit.SymbolicContinuousCallback( + [temp ~ furnace_on_threshold], + ModelingToolkit.ImperativeAffect(modified = (; furnace_on)) do x, o, c, i + @set! x.furnace_on = true + end) + @named sys = ODESystem( + eqs, t, [temp], params; continuous_events = [furnace_off, furnace_enable]) + ss = structural_simplify(sys) + prob = ODEProblem(ss, [temp => 0.0, furnace_on => true], (0.0, 100.0)) + sol = solve(prob, Tsit5(); dtmax = 0.01) + @test all(sol[temp][sol.t .> 1.0] .<= 0.79) && all(sol[temp][sol.t .> 1.0] .>= 0.49) + @test all(sol[temp][sol.t .!= 0.0] .<= 0.79) && all(sol[temp][sol.t .!= 0.0] .>= 0.2) +end + +@testset "ImperativeAffect errors and warnings" begin + @variables temp(t) + params = @parameters furnace_on_threshold=0.5 furnace_off_threshold=0.7 furnace_power=1.0 leakage=0.1 furnace_on::Bool=false + eqs = [ + D(temp) ~ furnace_on * furnace_power - temp^2 * leakage + ] + + furnace_off = ModelingToolkit.SymbolicContinuousCallback( + [temp ~ furnace_off_threshold], + ModelingToolkit.ImperativeAffect( + modified = (; furnace_on), observed = (; furnace_on)) do x, o, c, i + @set! x.furnace_on = false + end) + @named sys = ODESystem(eqs, t, [temp], params; continuous_events = [furnace_off]) + ss = structural_simplify(sys) + @test_logs (:warn, + "The symbols Any[:furnace_on] are declared as both observed and modified; this is a code smell because it becomes easy to confuse them and assign/not assign a value.") prob=ODEProblem( + ss, [temp => 0.0, furnace_on => true], (0.0, 100.0)) + + @variables tempsq(t) # trivially eliminated + eqs = [tempsq ~ temp^2 + D(temp) ~ furnace_on * furnace_power - temp^2 * leakage] + + furnace_off = ModelingToolkit.SymbolicContinuousCallback( + [temp ~ furnace_off_threshold], + ModelingToolkit.ImperativeAffect( + modified = (; furnace_on, tempsq), observed = (; furnace_on)) do x, o, c, i + @set! x.furnace_on = false + end) + @named sys = ODESystem( + eqs, t, [temp, tempsq], params; continuous_events = [furnace_off]) + ss = structural_simplify(sys) + @test_throws "refers to missing variable(s)" prob=ODEProblem( + ss, [temp => 0.0, furnace_on => true], (0.0, 100.0)) + + @parameters not_actually_here + furnace_off = ModelingToolkit.SymbolicContinuousCallback( + [temp ~ furnace_off_threshold], + ModelingToolkit.ImperativeAffect(modified = (; furnace_on), + observed = (; furnace_on, not_actually_here)) do x, o, c, i + @set! x.furnace_on = false + end) + @named sys = ODESystem( + eqs, t, [temp, tempsq], params; continuous_events = [furnace_off]) + ss = structural_simplify(sys) + @test_throws "refers to missing variable(s)" prob=ODEProblem( + ss, [temp => 0.0, furnace_on => true], (0.0, 100.0)) + + furnace_off = ModelingToolkit.SymbolicContinuousCallback( + [temp ~ furnace_off_threshold], + ModelingToolkit.ImperativeAffect(modified = (; furnace_on), + observed = (; furnace_on)) do x, o, c, i + return (; fictional2 = false) + end) + @named sys = ODESystem( + eqs, t, [temp, tempsq], params; continuous_events = [furnace_off]) + ss = structural_simplify(sys) + prob = ODEProblem( + ss, [temp => 0.0, furnace_on => true], (0.0, 100.0)) + @test_throws "Tried to write back to" solve(prob, Tsit5()) +end + +@testset "Quadrature" begin + @variables theta(t) omega(t) + params = @parameters qA=0 qB=0 hA=0 hB=0 cnt::Int=0 + eqs = [D(theta) ~ omega + omega ~ 1.0] + function decoder(oldA, oldB, newA, newB) + state = (oldA, oldB, newA, newB) + if state == (0, 0, 1, 0) || state == (1, 0, 1, 1) || state == (1, 1, 0, 1) || + state == (0, 1, 0, 0) + return 1 + elseif state == (0, 0, 0, 1) || state == (0, 1, 1, 1) || state == (1, 1, 1, 0) || + state == (1, 0, 0, 0) + return -1 + elseif state == (0, 0, 0, 0) || state == (0, 1, 0, 1) || state == (1, 0, 1, 0) || + state == (1, 1, 1, 1) + return 0 + else + return 0 # err is interpreted as no movement + end + end + qAevt = ModelingToolkit.SymbolicContinuousCallback([cos(100 * theta) ~ 0], + ModelingToolkit.ImperativeAffect((; qA, hA, hB, cnt), (; qB)) do x, o, c, i + @set! x.hA = x.qA + @set! x.hB = o.qB + @set! x.qA = 1 + @set! x.cnt += decoder(x.hA, x.hB, x.qA, o.qB) + x + end, + affect_neg = ModelingToolkit.ImperativeAffect( + (; qA, hA, hB, cnt), (; qB)) do x, o, c, i + @set! x.hA = x.qA + @set! x.hB = o.qB + @set! x.qA = 0 + @set! x.cnt += decoder(x.hA, x.hB, x.qA, o.qB) + x + end; rootfind = SciMLBase.RightRootFind) + qBevt = ModelingToolkit.SymbolicContinuousCallback([cos(100 * theta - π / 2) ~ 0], + ModelingToolkit.ImperativeAffect((; qB, hA, hB, cnt), (; qA)) do x, o, c, i + @set! x.hA = o.qA + @set! x.hB = x.qB + @set! x.qB = 1 + @set! x.cnt += decoder(x.hA, x.hB, o.qA, x.qB) + x + end, + affect_neg = ModelingToolkit.ImperativeAffect( + (; qB, hA, hB, cnt), (; qA)) do x, o, c, i + @set! x.hA = o.qA + @set! x.hB = x.qB + @set! x.qB = 0 + @set! x.cnt += decoder(x.hA, x.hB, o.qA, x.qB) + x + end; rootfind = SciMLBase.RightRootFind) + @named sys = ODESystem( + eqs, t, [theta, omega], params; continuous_events = [qAevt, qBevt]) + ss = structural_simplify(sys) + prob = ODEProblem(ss, [theta => 1e-5], (0.0, pi)) + sol = solve(prob, Tsit5(); dtmax = 0.01) + @test getp(sol, cnt)(sol) == 198 # we get 2 pulses per phase cycle (cos 0 crossing) and we go to 100 cycles; we miss a few due to the initial state +end + +@testset "Initialization" begin + @variables x(t) + seen = false + f = ModelingToolkit.FunctionalAffect( + f = (i, u, p, c) -> seen = true, sts = [], pars = [], discretes = []) + cb1 = ModelingToolkit.SymbolicContinuousCallback( + [x ~ 0], nothing, initialize = [x ~ 1.5], finalize = f) + @mtkbuild sys = ODESystem(D(x) ~ -1, t, [x], []; continuous_events = [cb1]) + prob = ODEProblem(sys, [x => 1.0], (0.0, 2), []) + sol = solve(prob, Tsit5(); dtmax = 0.01) + @test sol[x][1] ≈ 1.0 + @test sol[x][2] ≈ 1.5 # the initialize affect has been applied + @test seen == true + + @variables x(t) + seen = false + f = ModelingToolkit.FunctionalAffect( + f = (i, u, p, c) -> seen = true, sts = [], pars = [], discretes = []) + 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 = []) + cb2 = ModelingToolkit.SymbolicContinuousCallback( + [x ~ 0.1], nothing, initialize = a, finalize = b) + @mtkbuild sys = ODESystem(D(x) ~ -1, t, [x], []; continuous_events = [cb1, cb2]) + prob = ODEProblem(sys, [x => 1.0], (0.0, 2), []) + sol = solve(prob, Tsit5()) + @test sol[x][1] ≈ 1.0 + @test sol[x][2] ≈ 1.5 # the initialize affect has been applied + @test seen == true + @test inited == true + @test finaled == true + + #periodic + inited = false + finaled = false + cb3 = ModelingToolkit.SymbolicDiscreteCallback( + 1.0, [x ~ 2], initialize = a, finalize = b) + @mtkbuild sys = ODESystem(D(x) ~ -1, t, [x], []; discrete_events = [cb3]) + prob = ODEProblem(sys, [x => 1.0], (0.0, 2), []) + sol = solve(prob, Tsit5()) + @test inited == true + @test finaled == true + @test isapprox(sol[x][3], 0.0, atol = 1e-9) + @test sol[x][4] ≈ 2.0 + @test sol[x][5] ≈ 1.0 + + seen = false + inited = false + finaled = false + cb3 = ModelingToolkit.SymbolicDiscreteCallback(1.0, f, initialize = a, finalize = b) + @mtkbuild sys = ODESystem(D(x) ~ -1, t, [x], []; discrete_events = [cb3]) + prob = ODEProblem(sys, [x => 1.0], (0.0, 2), []) + sol = solve(prob, Tsit5()) + @test seen == true + @test inited == true + + #preset + seen = false + inited = false + finaled = false + cb3 = ModelingToolkit.SymbolicDiscreteCallback([1.0], f, initialize = a, finalize = b) + @mtkbuild sys = ODESystem(D(x) ~ -1, t, [x], []; discrete_events = [cb3]) + prob = ODEProblem(sys, [x => 1.0], (0.0, 2), []) + sol = solve(prob, Tsit5()) + @test seen == true + @test inited == true + @test finaled == true + + #equational + seen = false + inited = false + finaled = false + cb3 = ModelingToolkit.SymbolicDiscreteCallback( + t == 1.0, f, initialize = a, finalize = b) + @mtkbuild sys = ODESystem(D(x) ~ -1, t, [x], []; discrete_events = [cb3]) + prob = ODEProblem(sys, [x => 1.0], (0.0, 2), []) + sol = solve(prob, Tsit5(); tstops = 1.0) + @test seen == true + @test inited == true + @test finaled == true +end @testset "Bump" begin @variables x(t) [irreducible = true] y(t) [irreducible = true] @@ -1213,7 +1213,7 @@ end D(x) ~ -k * x end @discrete_events begin - (t == 1.0) => [k ~ 1.0], discrete_parameters = [k] + (t == 1.0) => [k ~ 1.0]#, discrete_parameters = [k] end end @mtkbuild decay = DECAY() diff --git a/test/symbolic_parameters.jl b/test/symbolic_parameters.jl index a29090912c..f4fa21e614 100644 --- a/test/symbolic_parameters.jl +++ b/test/symbolic_parameters.jl @@ -28,7 +28,7 @@ resolved = ModelingToolkit.varmap_to_vars(Dict(), parameters(ns), prob = NonlinearProblem(complete(ns), [u => 1.0], Pair[]) @test prob.u0 == [1.0, 1.1, 0.9] -@show sol = solve(prob, NewtonRaphson()) +sol = solve(prob, NewtonRaphson()) @variables a @parameters b @@ -43,12 +43,12 @@ res = ModelingToolkit.varmap_to_vars(Dict(), parameters(top), top = complete(top) prob = NonlinearProblem(top, [unknowns(ns, u) => 1.0, a => 1.0], []) @test prob.u0 == [1.0, 0.5, 1.1, 0.9] -@show sol = solve(prob, NewtonRaphson()) +sol = solve(prob, NewtonRaphson()) # test NullParameters+defaults prob = NonlinearProblem(top, [unknowns(ns, u) => 1.0, a => 1.0]) @test prob.u0 == [1.0, 0.5, 1.1, 0.9] -@show sol = solve(prob, NewtonRaphson()) +sol = solve(prob, NewtonRaphson()) # test initial conditions and parameters at the problem level pars = @parameters(begin From 9b356db7fd1dce469ebfc99baf02591a1c0ce985 Mon Sep 17 00:00:00 2001 From: vyudu Date: Mon, 31 Mar 2025 17:23:19 -0400 Subject: [PATCH 063/122] fix: fix collect_var --- src/systems/callbacks.jl | 7 +++-- src/utils.jl | 1 + test/odesystem.jl | 57 +++++++++++++++++++++------------------- 3 files changed, 36 insertions(+), 29 deletions(-) diff --git a/src/systems/callbacks.jl b/src/systems/callbacks.jl index 9be33b0800..9343ada58b 100644 --- a/src/systems/callbacks.jl +++ b/src/systems/callbacks.jl @@ -301,7 +301,9 @@ function make_affect(affect::Vector{Equation}; discrete_parameters::AbstractVect # get accessed parameters p from Pre(p) in the callback parameters accessed_params = filter(isparameter, map(unPre, collect(pre_params))) union!(accessed_params, sys_params) - # add unknowns to the map + + # add scalarized unknowns to the map. + _dvs = reduce(vcat, map(scalarize, _dvs), init = Any[]) for u in _dvs aff_map[u] = u end @@ -616,7 +618,8 @@ function compile_condition( end if !is_discrete(cbs) - condit = [cond.lhs - cond.rhs for cond in condit] + condit = reduce(vcat, flatten_equations(condit)) + condit = condit isa AbstractVector ? [c.lhs - c.rhs for c in condit] : [condit.lhs - condit.rhs] end fs = build_function_wrapper(sys, diff --git a/src/utils.jl b/src/utils.jl index 1e0325cf18..c0b9517419 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -591,6 +591,7 @@ end function collect_var!(unknowns, parameters, var, iv; depth = 0) isequal(var, iv) && return nothing + var = unwrap(var) check_scope_depth(getmetadata(var, SymScope, LocalScope()), depth) || return nothing var = setmetadata(var, SymScope, LocalScope()) if iscalledparameter(var) diff --git a/test/odesystem.jl b/test/odesystem.jl index 08c37d30bc..d63910c188 100644 --- a/test/odesystem.jl +++ b/test/odesystem.jl @@ -1017,24 +1017,26 @@ prob = ODEProblem(sys, [x => 1.0], (0.0, 10.0)) @test_nowarn solve(prob, Tsit5()) # Issue#2383 -@variables x(t)[1:3] -@parameters p[1:3, 1:3] -eqs = [ - D(x) ~ p * x -] -@mtkbuild sys = ODESystem(eqs, t; continuous_events = [[norm(x) ~ 3.0] => [x ~ ones(3)]]) -# array affect equations used to not work -prob1 = @test_nowarn ODEProblem(sys, [x => ones(3)], (0.0, 10.0), [p => ones(3, 3)]) -sol1 = @test_nowarn solve(prob1, Tsit5()) - -# array condition equations also used to not work -@mtkbuild sys = ODESystem( - eqs, t; continuous_events = [[x ~ sqrt(3) * ones(3)] => [x ~ ones(3)]]) -# array affect equations used to not work -prob2 = @test_nowarn ODEProblem(sys, [x => ones(3)], (0.0, 10.0), [p => ones(3, 3)]) -sol2 = @test_nowarn solve(prob2, Tsit5()) - -@test sol1 ≈ sol2 +@testset "Arrays in affect/condition equations" begin + @variables x(t)[1:3] + @parameters p[1:3, 1:3] + eqs = [ + D(x) ~ p * x + ] + @mtkbuild sys = ODESystem(eqs, t; continuous_events = [[norm(x) ~ 3.0] => [x ~ ones(3)]]) + # array affect equations used to not work + prob1 = @test_nowarn ODEProblem(sys, [x => ones(3)], (0.0, 10.0), [p => ones(3, 3)]) + sol1 = @test_nowarn solve(prob1, Tsit5()) + + # array condition equations also used to not work + @mtkbuild sys = ODESystem( + eqs, t; continuous_events = [[x ~ sqrt(3) * ones(3)] => [x ~ ones(3)]]) + # array affect equations used to not work + prob2 = @test_nowarn ODEProblem(sys, [x => ones(3)], (0.0, 10.0), [p => ones(3, 3)]) + sol2 = @test_nowarn solve(prob2, Tsit5()) + + @test sol1.u ≈ sol2.u[2:end] +end # Requires fix in symbolics for `linear_expansion(p * x, D(y))` @test_skip begin @@ -1181,10 +1183,12 @@ end end # Namespacing of array variables -@variables x(t)[1:2] -@named sys = ODESystem(Equation[], t) -@test getname(unknowns(sys, x)) == :sys₊x -@test size(unknowns(sys, x)) == size(x) +@testset "Namespacing of array variables" begin + @variables x(t)[1:2] + @named sys = ODESystem(Equation[], t) + @test getname(unknowns(sys, x)) == :sys₊x + @test size(unknowns(sys, x)) == size(x) +end # Issue#2667 and Issue#2953 @testset "ForwardDiff through ODEProblem constructor" begin @@ -1522,8 +1526,7 @@ end @testset "Observed variables dependent on discrete parameters" begin @variables x(t) obs(t) @parameters c(t) - @mtkbuild sys = ODESystem( - [D(x) ~ c * cos(x), obs ~ c], t, [x], [c]; discrete_events = [1.0 => [c ~ c + 1]]) + @mtkbuild sys = ODESystem([D(x) ~ c * cos(x), obs ~ c], t, [x], [c]; discrete_events = [SymbolicDiscreteCallback(1.0 => [c ~ Pre(c) + 1], discrete_parameters = [c])]) prob = ODEProblem(sys, [x => 0.0], (0.0, 2pi), [c => 1.0]) sol = solve(prob, Tsit5()) @test sol[obs] ≈ 1:7 @@ -1583,15 +1586,15 @@ end # Test `isequal` @testset "`isequal`" begin @variables X(t) - @parameters p d + @parameters p d(t) eq = D(X) ~ p - d * X osys1 = complete(ODESystem([eq], t; name = :osys)) osys2 = complete(ODESystem([eq], t; name = :osys)) @test osys1 == osys2 # true - continuous_events = [[X ~ 1.0] => [X ~ X + 5.0]] - discrete_events = [5.0 => [d ~ d / 2.0]] + continuous_events = [[X ~ 1.0] => [X ~ Pre(X) + 5.0]] + discrete_events = [SymbolicDiscreteCallback(5.0 => [d ~ d / 2.0], discrete_parameters = [d])] osys1 = complete(ODESystem([eq], t; name = :osys, continuous_events)) osys2 = complete(ODESystem([eq], t; name = :osys)) From 661e1f6d460979f1611b50844dac336a6ea9de50 Mon Sep 17 00:00:00 2001 From: vyudu Date: Mon, 31 Mar 2025 17:34:09 -0400 Subject: [PATCH 064/122] format --- src/systems/callbacks.jl | 37 +++++++++++++++++++++------------- src/systems/model_parsing.jl | 1 - src/systems/systems.jl | 4 +++- test/fmi/fmi.jl | 6 ++++-- test/odesystem.jl | 17 +++++++++++----- test/parameter_dependencies.jl | 12 +++++++---- test/symbolic_events.jl | 2 +- 7 files changed, 51 insertions(+), 28 deletions(-) diff --git a/src/systems/callbacks.jl b/src/systems/callbacks.jl index 9343ada58b..f025531c3c 100644 --- a/src/systems/callbacks.jl +++ b/src/systems/callbacks.jl @@ -230,15 +230,17 @@ struct SymbolicContinuousCallback <: AbstractCallback conditions = (conditions isa AbstractVector) ? conditions : [conditions] if isnothing(reinitializealg) - any(a -> (a isa FunctionalAffect || a isa ImperativeAffect), [affect, affect_neg, initialize, finalize]) ? - reinitializealg = SciMLBase.CheckInit() : - reinitializealg = SciMLBase.NoInit() + any(a -> (a isa FunctionalAffect || a isa ImperativeAffect), + [affect, affect_neg, initialize, finalize]) ? + reinitializealg = SciMLBase.CheckInit() : + reinitializealg = SciMLBase.NoInit() end new(conditions, make_affect(affect; iv, algeeqs, discrete_parameters), make_affect(affect_neg; iv, algeeqs, discrete_parameters), make_affect(initialize; iv, algeeqs, discrete_parameters), make_affect( - finalize; iv, algeeqs, discrete_parameters), rootfind, reinitializealg) + finalize; iv, algeeqs, discrete_parameters), + rootfind, reinitializealg) end # Default affect to nothing end @@ -286,7 +288,7 @@ function make_affect(affect::Vector{Equation}; discrete_parameters::AbstractVect sys_params = collect(setdiff(params, union(discrete_parameters, pre_params))) discretes = map(tovar, discrete_parameters) dvs = collect(dvs) - _dvs = map(default_toterm, dvs) + _dvs = map(default_toterm, dvs) aff_map = Dict(zip(discretes, discrete_parameters)) rev_map = Dict(zip(discrete_parameters, discretes)) @@ -446,9 +448,10 @@ struct SymbolicDiscreteCallback <: AbstractCallback c = is_timed_condition(condition) ? condition : value(scalarize(condition)) if isnothing(reinitializealg) - any(a -> (a isa FunctionalAffect || a isa ImperativeAffect), [affect, initialize, finalize]) ? - reinitializealg = SciMLBase.CheckInit() : - reinitializealg = SciMLBase.NoInit() + any(a -> (a isa FunctionalAffect || a isa ImperativeAffect), + [affect, initialize, finalize]) ? + reinitializealg = SciMLBase.CheckInit() : + reinitializealg = SciMLBase.NoInit() end new(c, make_affect(affect; iv, algeeqs, discrete_parameters), make_affect(initialize; iv, algeeqs, discrete_parameters), @@ -582,7 +585,8 @@ end function Base.:(==)(e1::AbstractCallback, e2::AbstractCallback) (is_discrete(e1) === is_discrete(e2)) || return false (isequal(e1.conditions, e2.conditions) && isequal(e1.affect, e2.affect) && - isequal(e1.initialize, e2.initialize) && isequal(e1.finalize, e2.finalize)) && isequal(e1.reinitializealg, e2.reinitializealg) || + isequal(e1.initialize, e2.initialize) && isequal(e1.finalize, e2.finalize)) && + isequal(e1.reinitializealg, e2.reinitializealg) || return false is_discrete(e1) || (isequal(e1.affect_neg, e2.affect_neg) && isequal(e1.rootfind, e2.rootfind)) @@ -619,7 +623,8 @@ function compile_condition( if !is_discrete(cbs) condit = reduce(vcat, flatten_equations(condit)) - condit = condit isa AbstractVector ? [c.lhs - c.rhs for c in condit] : [condit.lhs - condit.rhs] + condit = condit isa AbstractVector ? [c.lhs - c.rhs for c in condit] : + [condit.lhs - condit.rhs] end fs = build_function_wrapper(sys, @@ -685,15 +690,18 @@ function generate_continuous_callbacks(sys::AbstractSystem, dvs = unknowns(sys), ps = parameters(sys; initial_parameters = true); kwargs...) cbs = continuous_events(sys) isempty(cbs) && return nothing - cb_classes = Dict{Tuple{SciMLBase.RootfindOpt, SciMLBase.DAEInitializationAlgorithm}, Vector{SymbolicContinuousCallback}}() + cb_classes = Dict{Tuple{SciMLBase.RootfindOpt, SciMLBase.DAEInitializationAlgorithm}, + Vector{SymbolicContinuousCallback}}() # Sort the callbacks by their rootfinding method for cb in cbs - _cbs = get!(() -> SymbolicContinuousCallback[], cb_classes, (cb.rootfind, cb.reinitializealg)) + _cbs = get!(() -> SymbolicContinuousCallback[], + cb_classes, (cb.rootfind, cb.reinitializealg)) push!(_cbs, cb) end sort!(OrderedDict(cb_classes), by = cb -> cb[1]) - compiled_callbacks = [generate_callback(cb, sys; kwargs...) for ((rf, reinit), cb) in cb_classes] + compiled_callbacks = [generate_callback(cb, sys; kwargs...) + for ((rf, reinit), cb) in cb_classes] if length(compiled_callbacks) == 1 return only(compiled_callbacks) else @@ -791,7 +799,8 @@ function generate_callback(cb, sys; kwargs...) return PresetTimeCallback(trigger, affect; initialize, finalize, initializealg = cb.reinitializealg) elseif is_timed - return PeriodicCallback(affect, trigger; initialize, finalize, initializealg = cb.reinitializealg) + return PeriodicCallback( + affect, trigger; initialize, finalize, initializealg = cb.reinitializealg) else return DiscreteCallback(trigger, affect; initialize, finalize, initializealg = cb.reinitializealg) diff --git a/src/systems/model_parsing.jl b/src/systems/model_parsing.jl index 782b1625e4..bf4100063d 100644 --- a/src/systems/model_parsing.jl +++ b/src/systems/model_parsing.jl @@ -154,7 +154,6 @@ function _model_macro(mod, fullname::Union{Expr, Symbol}, expr, isconnector) $(d_evts...) ])))) - f = if length(where_types) == 0 :($(Symbol(:__, name, :__))(; name, $(kwargs...)) = $exprs) else diff --git a/src/systems/systems.jl b/src/systems/systems.jl index 1aace4de7f..bed7824d58 100644 --- a/src/systems/systems.jl +++ b/src/systems/systems.jl @@ -161,7 +161,9 @@ function __structural_simplify(sys::AbstractSystem; simplify = false, get_iv(ode_sys), unknowns(ode_sys), parameters(ode_sys); name = nameof(ode_sys), is_scalar_noise, observed = observed(ode_sys), defaults = defaults(sys), parameter_dependencies = parameter_dependencies(sys), assertions = assertions(sys), - guesses = guesses(sys), initialization_eqs = initialization_equations(sys), continuous_events = continuous_events(sys), discrete_events = discrete_events(sys)) + guesses = guesses(sys), initialization_eqs = initialization_equations(sys), + continuous_events = continuous_events(sys), + discrete_events = discrete_events(sys)) end end diff --git a/test/fmi/fmi.jl b/test/fmi/fmi.jl index e4c155270e..98c93398ff 100644 --- a/test/fmi/fmi.jl +++ b/test/fmi/fmi.jl @@ -157,7 +157,8 @@ end @testset "v2, CS" begin fmu = loadFMU(joinpath(FMU_DIR, "SimpleAdder.fmu"); type = :CS) @named adder = MTK.FMIComponent( - Val(2); fmu, type = :CS, communication_step_size = 1e-6, reinitializealg = BrownFullBasicInit()) + Val(2); fmu, type = :CS, communication_step_size = 1e-6, + reinitializealg = BrownFullBasicInit()) @test MTK.isinput(adder.a) @test MTK.isinput(adder.b) @test MTK.isoutput(adder.out) @@ -209,7 +210,8 @@ end @testset "v3, CS" begin fmu = loadFMU(joinpath(FMU_DIR, "StateSpace.fmu"); type = :CS) @named sspace = MTK.FMIComponent( - Val(3); fmu, communication_step_size = 1e-6, type = :CS, reinitializealg = BrownFullBasicInit()) + Val(3); fmu, communication_step_size = 1e-6, type = :CS, + reinitializealg = BrownFullBasicInit()) @test MTK.isinput(sspace.u) @test MTK.isoutput(sspace.y) @test !MTK.isinput(sspace.x) && !MTK.isoutput(sspace.x) diff --git a/test/odesystem.jl b/test/odesystem.jl index d63910c188..c02bb87fe5 100644 --- a/test/odesystem.jl +++ b/test/odesystem.jl @@ -1023,18 +1023,19 @@ prob = ODEProblem(sys, [x => 1.0], (0.0, 10.0)) eqs = [ D(x) ~ p * x ] - @mtkbuild sys = ODESystem(eqs, t; continuous_events = [[norm(x) ~ 3.0] => [x ~ ones(3)]]) + @mtkbuild sys = ODESystem( + eqs, t; continuous_events = [[norm(x) ~ 3.0] => [x ~ ones(3)]]) # array affect equations used to not work prob1 = @test_nowarn ODEProblem(sys, [x => ones(3)], (0.0, 10.0), [p => ones(3, 3)]) sol1 = @test_nowarn solve(prob1, Tsit5()) - + # array condition equations also used to not work @mtkbuild sys = ODESystem( eqs, t; continuous_events = [[x ~ sqrt(3) * ones(3)] => [x ~ ones(3)]]) # array affect equations used to not work prob2 = @test_nowarn ODEProblem(sys, [x => ones(3)], (0.0, 10.0), [p => ones(3, 3)]) sol2 = @test_nowarn solve(prob2, Tsit5()) - + @test sol1.u ≈ sol2.u[2:end] end @@ -1526,7 +1527,12 @@ end @testset "Observed variables dependent on discrete parameters" begin @variables x(t) obs(t) @parameters c(t) - @mtkbuild sys = ODESystem([D(x) ~ c * cos(x), obs ~ c], t, [x], [c]; discrete_events = [SymbolicDiscreteCallback(1.0 => [c ~ Pre(c) + 1], discrete_parameters = [c])]) + @mtkbuild sys = ODESystem([D(x) ~ c * cos(x), obs ~ c], + t, + [x], + [c]; + discrete_events = [SymbolicDiscreteCallback( + 1.0 => [c ~ Pre(c) + 1], discrete_parameters = [c])]) prob = ODEProblem(sys, [x => 0.0], (0.0, 2pi), [c => 1.0]) sol = solve(prob, Tsit5()) @test sol[obs] ≈ 1:7 @@ -1594,7 +1600,8 @@ end @test osys1 == osys2 # true continuous_events = [[X ~ 1.0] => [X ~ Pre(X) + 5.0]] - discrete_events = [SymbolicDiscreteCallback(5.0 => [d ~ d / 2.0], discrete_parameters = [d])] + discrete_events = [SymbolicDiscreteCallback( + 5.0 => [d ~ d / 2.0], discrete_parameters = [d])] osys1 = complete(ODESystem([eq], t; name = :osys, continuous_events)) osys2 = complete(ODESystem([eq], t; name = :osys)) diff --git a/test/parameter_dependencies.jl b/test/parameter_dependencies.jl index 89f0dc1e27..bdaa2a391b 100644 --- a/test/parameter_dependencies.jl +++ b/test/parameter_dependencies.jl @@ -1,6 +1,7 @@ using ModelingToolkit using Test -using ModelingToolkit: t_nounits as t, D_nounits as D, SymbolicDiscreteCallback, SymbolicContinuousCallback +using ModelingToolkit: t_nounits as t, D_nounits as D, SymbolicDiscreteCallback, + SymbolicContinuousCallback using OrdinaryDiffEq using StochasticDiffEq using JumpProcesses @@ -225,7 +226,8 @@ end @test_nowarn solve(prob, Tsit5()) @mtkbuild sys = ODESystem(eqs, t; parameter_dependencies = [kq => 2kp], - discrete_events = [SymbolicDiscreteCallback([0.5] => [kp ~ 2.0], discrete_parameters = [kp])]) + discrete_events = [SymbolicDiscreteCallback( + [0.5] => [kp ~ 2.0], discrete_parameters = [kp])]) prob = ODEProblem(sys, [x => 0.0, y => 0.0], (0.0, Tf), [kp => 1.0; z(k - 1) => 3.0; yd(k - 1) => 0.0; z(k - 2) => 4.0; yd(k - 2) => 2.0]) @@ -269,7 +271,8 @@ end @named sys = ODESystem(eqs, t) @named sdesys = SDESystem(sys, noiseeqs; parameter_dependencies = [ρ => 2σ], - discrete_events = [SymbolicDiscreteCallback([10.0] => [σ ~ 15.0], discrete_parameters = [σ])]) + discrete_events = [SymbolicDiscreteCallback( + [10.0] => [σ ~ 15.0], discrete_parameters = [σ])]) sdesys = complete(sdesys) prob = SDEProblem( sdesys, [x => 1.0, y => 0.0, z => 0.0], (0.0, 100.0), [σ => 10.0, β => 2.33]) @@ -308,7 +311,8 @@ end @named js2 = JumpSystem( [j₁, j₃], t, [S, I, R], [γ]; parameter_dependencies = [β => 0.01γ], - discrete_events = [SymbolicDiscreteCallback([10.0] => [γ ~ 0.02], discrete_parameters = [γ])]) + discrete_events = [SymbolicDiscreteCallback( + [10.0] => [γ ~ 0.02], discrete_parameters = [γ])]) js2 = complete(js2) dprob = DiscreteProblem(js2, u₀map, tspan, parammap) jprob = JumpProblem(js2, dprob, Direct(), save_positions = (false, false), rng = rng) diff --git a/test/symbolic_events.jl b/test/symbolic_events.jl index 49c02bacc1..c41ab497b8 100644 --- a/test/symbolic_events.jl +++ b/test/symbolic_events.jl @@ -1100,7 +1100,7 @@ end f = ModelingToolkit.FunctionalAffect( f = (i, u, p, c) -> seen = true, sts = [], pars = [], discretes = []) cb1 = ModelingToolkit.SymbolicContinuousCallback( - [x ~ 0], nothing, initialize = [x ~ 1.5], finalize = f) + [x ~ 0], nothing, initialize = [x ~ 1.5], finalize = f) @mtkbuild sys = ODESystem(D(x) ~ -1, t, [x], []; continuous_events = [cb1]) prob = ODEProblem(sys, [x => 1.0], (0.0, 2), []) sol = solve(prob, Tsit5(); dtmax = 0.01) From 4d1e8b77b09650aa15a0bd6cb24753f7a162d432 Mon Sep 17 00:00:00 2001 From: vyudu Date: Tue, 1 Apr 2025 12:08:26 -0400 Subject: [PATCH 065/122] docs: add documentation for the symbolic affect changes --- docs/src/basics/Events.md | 89 +++++++++++++++++++++++++++++++++------ test/odesystem.jl | 3 +- 2 files changed, 77 insertions(+), 15 deletions(-) diff --git a/docs/src/basics/Events.md b/docs/src/basics/Events.md index 3a76f478f1..2725938952 100644 --- a/docs/src/basics/Events.md +++ b/docs/src/basics/Events.md @@ -25,6 +25,67 @@ the event occurs). These can both be specified symbolically, but a more [general functional affect](@ref func_affects) representation is also allowed, as described below. +## Symbolic Callback Semantics (changed in V10) + +In callbacks, there is a distinction between values of the unknowns and parameters +*before* the callback, and the desired values *after* the callback. In MTK, this +is provided by the `Pre` operator. For example, if we would like to add 1 to an +unknown `x` in a callback, the equation would look like the following: + +```julia +x ~ Pre(x) + 1 +``` + +Non `Pre`-d values will be interpreted as values *after* the callback. As such, +writing + +```julia +x ~ x + 1 +``` + +will be interpreted as an algebraic equation to be satisfied after the callback. +Since this equation obviously cannot be satisfied, an error will result. + +Callbacks must maintain the consistency of DAEs, meaning that they must satisfy +all the algebraic equations of the system after their update. However, the affect +equations often do not fully specify which unknowns/parameters should be modified +to maintain consistency. To make this clear, MTK uses the following rules: + + 1. All unknowns are treated as modifiable by the callback. In order to enforce that an unknown `x` remains the same, one can add `x ~ Pre(x)` to the affect equations. + 2. All parameters are treated as un-modifiable, *unless* they are declared as `discrete_parameters` to the callback. In order to be a discrete parameter, the parameter must be time-dependent (the terminology *discretes* here means [discrete variables](@ref save_discretes)). + +For example, consider the following system. + +```julia +@variables x(t) y(t) +@parameters p(t) +@mtkbuild sys = ODESystem([x * y ~ p, D(x) ~ 0], t) +event = [t == 1] => [x ~ Pre(x) + 1] +``` + +By default what will happen is that `x` will increase by 1, `p` will remain constant, +and `y` will change in order to compensate the increase in `x`. But what if we +wanted to keep `y` constant and change `p` instead? We could use the callback +constructor as follows: + +```julia +event = SymbolicDiscreteCallback( + [t == 1] => [x ~ Pre(x) + 1, y ~ Pre(y)], discrete_parameters = [p]) +``` + +This way, we enforce that `y` will remain the same, and `p` will change. + +!!! warning + + Symbolic affects come with the guarantee that the state after the callback + will be consistent. However, when using [general functional affects](@ref func_affects) + or [imperative affects](@ref imp_affects) one must be more careful. In + particular, one can pass in `reinitializealg` as a keyword arg to the + callback constructor to re-initialize the system. This will default to + `SciMLBase.NoInit()` in the case of symbolic affects and `SciMLBase.CheckInit()` + in the case of functional affects. This keyword should *not* be provided + if the affect is purely symbolic. + ## Continuous Events The basic purely symbolic continuous event interface to encode *one* continuous @@ -91,7 +152,7 @@ like this @variables x(t)=1 v(t)=0 root_eqs = [x ~ 0] # the event happens at the ground x(t) = 0 -affect = [v ~ -v] # the effect is that the velocity changes sign +affect = [v ~ -Pre(v)] # the effect is that the velocity changes sign @mtkbuild ball = ODESystem([D(x) ~ v D(v) ~ -9.8], t; continuous_events = root_eqs => affect) # equation => affect @@ -110,8 +171,8 @@ Multiple events? No problem! This example models a bouncing ball in 2D that is e ```@example events @variables x(t)=1 y(t)=0 vx(t)=0 vy(t)=2 -continuous_events = [[x ~ 0] => [vx ~ -vx] - [y ~ -1.5, y ~ 1.5] => [vy ~ -vy]] +continuous_events = [[x ~ 0] => [vx ~ -Pre(vx)] + [y ~ -1.5, y ~ 1.5] => [vy ~ -Pre(vy)]] @mtkbuild ball = ODESystem( [ @@ -204,7 +265,7 @@ bb_sol = solve(bb_prob, Tsit5()) plot(bb_sol) ``` -## Discrete events support +## Discrete Events In addition to continuous events, discrete events are also supported. The general interface to represent a collection of discrete events is @@ -233,7 +294,7 @@ Dₜ = Differential(t) eqs = [Dₜ(N) ~ α - N] # at time tinject we inject M cells -injection = (t == tinject) => [N ~ N + M] +injection = (t == tinject) => [N ~ Pre(N) + M] u0 = [N => 0.0] tspan = (0.0, 20.0) @@ -255,7 +316,7 @@ its steady-state value (which is 100). We can encode this by modifying the event to ```@example events -injection = ((t == tinject) & (N < 50)) => [N ~ N + M] +injection = ((t == tinject) & (N < 50)) => [N ~ Pre(N) + M] @mtkbuild osys = ODESystem(eqs, t, [N], [M, tinject, α]; discrete_events = injection) oprob = ODEProblem(osys, u0, tspan, p) @@ -275,7 +336,7 @@ cells, modeled by setting `α = 0.0` @parameters tkill # we reset the first event to just occur at tinject -injection = (t == tinject) => [N ~ N + M] +injection = (t == tinject) => [N ~ Pre(N) + M] # at time tkill we turn off production of cells killing = (t == tkill) => [α ~ 0.0] @@ -298,7 +359,7 @@ A preset-time event is triggered at specific set times, which can be passed in a vector like ```julia -discrete_events = [[1.0, 4.0] => [v ~ -v]] +discrete_events = [[1.0, 4.0] => [v ~ -Pre(v)]] ``` This will change the sign of `v` *only* at `t = 1.0` and `t = 4.0`. @@ -306,7 +367,7 @@ This will change the sign of `v` *only* at `t = 1.0` and `t = 4.0`. As such, our last example with treatment and killing could instead be modeled by ```@example events -injection = [10.0] => [N ~ N + M] +injection = [10.0] => [N ~ Pre(N) + M] killing = [20.0] => [α ~ 0.0] p = [α => 100.0, M => 50] @@ -325,7 +386,7 @@ specify a periodic interval, pass the interval as the condition for the event. For example, ```julia -discrete_events = [1.0 => [v ~ -v]] +discrete_events = [1.0 => [v ~ -Pre(v)]] ``` will change the sign of `v` at `t = 1.0`, `2.0`, ... @@ -334,10 +395,10 @@ Finally, we note that to specify an event at precisely one time, say 2.0 below, one must still use a vector ```julia -discrete_events = [[2.0] => [v ~ -v]] +discrete_events = [[2.0] => [v ~ -Pre(v)]] ``` -## Saving discrete values +## [Saving discrete values](@id save_discretes) Time-dependent parameters which are updated in callbacks are termed as discrete variables. ModelingToolkit enables automatically saving the timeseries of these discrete variables, @@ -349,7 +410,7 @@ example: @parameters c(t) @mtkbuild sys = ODESystem( - D(x) ~ c * cos(x), t, [x], [c]; discrete_events = [1.0 => [c ~ c + 1]]) + D(x) ~ c * cos(x), t, [x], [c]; discrete_events = [1.0 => [c ~ Pre(c) + 1]]) prob = ODEProblem(sys, [x => 0.0], (0.0, 2pi), [c => 1.0]) sol = solve(prob, Tsit5()) @@ -370,7 +431,7 @@ this change: @parameters c @mtkbuild sys = ODESystem( - D(x) ~ c * cos(x), t, [x], [c]; discrete_events = [1.0 => [c ~ c + 1]]) + D(x) ~ c * cos(x), t, [x], [c]; discrete_events = [1.0 => [c ~ Pre(c) + 1]]) prob = ODEProblem(sys, [x => 0.0], (0.0, 2pi), [c => 1.0]) sol = solve(prob, Tsit5()) diff --git a/test/odesystem.jl b/test/odesystem.jl index c02bb87fe5..1274326fee 100644 --- a/test/odesystem.jl +++ b/test/odesystem.jl @@ -1,5 +1,6 @@ using ModelingToolkit, StaticArrays, LinearAlgebra -using ModelingToolkit: get_metadata, MTKParameters +using ModelingToolkit: get_metadata, MTKParameters, SymbolicDiscreteCallback, + SymbolicContinuousCallback using SymbolicIndexingInterface using OrdinaryDiffEq, Sundials using DiffEqBase, SparseArrays From 00ccca45cdaad94793b647dc93a2018b3763d5a1 Mon Sep 17 00:00:00 2001 From: vyudu Date: Tue, 1 Apr 2025 12:40:54 -0400 Subject: [PATCH 066/122] revert index cache --- src/systems/callbacks.jl | 51 ++++++++++++++++---------------- src/systems/diffeqs/odesystem.jl | 6 ++-- src/systems/diffeqs/sdesystem.jl | 6 ++-- src/systems/index_cache.jl | 7 +---- test/accessor_functions.jl | 4 +-- test/symbolic_events.jl | 5 ++-- 6 files changed, 37 insertions(+), 42 deletions(-) diff --git a/src/systems/callbacks.jl b/src/systems/callbacks.jl index f025531c3c..a7929f9c34 100644 --- a/src/systems/callbacks.jl +++ b/src/systems/callbacks.jl @@ -160,7 +160,7 @@ const Affect = Union{AffectSystem, FunctionalAffect, ImperativeAffect} """ SymbolicContinuousCallback(eqs::Vector{Equation}, affect = nothing, iv = nothing; - affect_neg = affect, initialize = nothing, finalize = nothing, rootfind = SciMLBase.LeftRootFind, algeeqs = Equation[]) + affect_neg = affect, initialize = nothing, finalize = nothing, rootfind = SciMLBase.LeftRootFind, alg_eqs = Equation[]) A [`ContinuousCallback`](@ref SciMLBase.ContinuousCallback) specified symbolically. Takes a vector of equations `eq` as well as the positive-edge `affect` and negative-edge `affect_neg` that apply when *any* of `eq` are satisfied. @@ -226,7 +226,7 @@ struct SymbolicContinuousCallback <: AbstractCallback rootfind = SciMLBase.LeftRootFind, reinitializealg = nothing, iv = nothing, - algeeqs = Equation[]) + alg_eqs = Equation[]) conditions = (conditions isa AbstractVector) ? conditions : [conditions] if isnothing(reinitializealg) @@ -236,18 +236,19 @@ struct SymbolicContinuousCallback <: AbstractCallback reinitializealg = SciMLBase.NoInit() end - new(conditions, make_affect(affect; iv, algeeqs, discrete_parameters), - make_affect(affect_neg; iv, algeeqs, discrete_parameters), - make_affect(initialize; iv, algeeqs, discrete_parameters), make_affect( - finalize; iv, algeeqs, discrete_parameters), + new(conditions, make_affect(affect; iv, alg_eqs, discrete_parameters), + make_affect(affect_neg; iv, alg_eqs, discrete_parameters), + make_affect(initialize; iv, alg_eqs, discrete_parameters), make_affect( + finalize; iv, alg_eqs, discrete_parameters), rootfind, reinitializealg) end # Default affect to nothing end -function SymbolicContinuousCallback(p::Pair, args...; kwargs...) - SymbolicContinuousCallback(p[1], p[2], args...; kwargs...) +SymbolicContinuousCallback(p::Pair, args...; kwargs...) = SymbolicContinuousCallback(p[1], p[2], args...; kwargs...) + +function SymbolicContinuousCallback(cb::SymbolicContinuousCallback, args...; iv = nothing, alg_eqs = Equation[], kwargs...) + cb end -SymbolicContinuousCallback(cb::SymbolicContinuousCallback, args...; kwargs...) = cb make_affect(affect::Nothing; kwargs...) = nothing make_affect(affect::Tuple; kwargs...) = FunctionalAffect(affect...) @@ -255,10 +256,10 @@ make_affect(affect::NamedTuple; kwargs...) = FunctionalAffect(; affect...) make_affect(affect::Affect; kwargs...) = affect function make_affect(affect::Vector{Equation}; discrete_parameters::AbstractVector = Any[], - iv = nothing, algeeqs::Vector{Equation} = Equation[]) + iv = nothing, alg_eqs::Vector{Equation} = Equation[]) isempty(affect) && return nothing - isempty(algeeqs) && - @warn "No algebraic equations were found for the callback defined by $(join(affect, ", ")). If the system has no algebraic equations, this can be disregarded. Otherwise pass in `algeeqs` to the SymbolicContinuousCallback constructor." + isempty(alg_eqs) && + @warn "No algebraic equations were found for the callback defined by $(join(affect, ", ")). If the system has no algebraic equations, this can be disregarded. Otherwise pass in `alg_eqs` to the SymbolicContinuousCallback constructor." if isnothing(iv) iv = t_nounits @warn "No independent variable specified. Defaulting to t_nounits." @@ -280,7 +281,7 @@ function make_affect(affect::Vector{Equation}; discrete_parameters::AbstractVect diffvs = collect_applied_operators(eq, Differential) union!(dvs, diffvs) end - for eq in algeeqs + for eq in alg_eqs collect_vars!(dvs, params, eq, iv) end @@ -294,10 +295,10 @@ function make_affect(affect::Vector{Equation}; discrete_parameters::AbstractVect rev_map = Dict(zip(discrete_parameters, discretes)) subs = merge(rev_map, Dict(zip(dvs, _dvs))) affect = Symbolics.fast_substitute(affect, subs) - algeeqs = Symbolics.fast_substitute(algeeqs, subs) + alg_eqs = Symbolics.fast_substitute(alg_eqs, subs) @named affectsys = ImplicitDiscreteSystem( - vcat(affect, algeeqs), iv, collect(union(_dvs, discretes)), + vcat(affect, alg_eqs), iv, collect(union(_dvs, discretes)), collect(union(pre_params, sys_params))) affectsys = structural_simplify(affectsys; fully_determined = false) # get accessed parameters p from Pre(p) in the callback parameters @@ -322,7 +323,7 @@ end Generate continuous callbacks. """ function SymbolicContinuousCallbacks(events; discrete_parameters = Any[], - algeeqs::Vector{Equation} = Equation[], iv = nothing) + alg_eqs::Vector{Equation} = Equation[], iv = nothing) callbacks = SymbolicContinuousCallback[] isnothing(events) && return callbacks @@ -332,7 +333,7 @@ function SymbolicContinuousCallbacks(events; discrete_parameters = Any[], for event in events cond, affs = event isa Pair ? (event[1], event[2]) : (event, nothing) push!(callbacks, - SymbolicContinuousCallback(cond, affs; iv, algeeqs, discrete_parameters)) + SymbolicContinuousCallback(cond, affs; iv, alg_eqs, discrete_parameters)) end callbacks end @@ -421,7 +422,7 @@ end # TODO: Iterative callbacks """ SymbolicDiscreteCallback(conditions::Vector{Equation}, affect = nothing, iv = nothing; - initialize = nothing, finalize = nothing, algeeqs = Equation[]) + initialize = nothing, finalize = nothing, alg_eqs = Equation[]) A callback that triggers at the first timestep that the conditions are satisfied. @@ -432,7 +433,7 @@ The condition can be one of: Arguments: - iv: The independent variable of the system. This must be specified if the independent variable appaers in one of the equations explicitly, as in x ~ t + 1. -- algeeqs: Algebraic equations of the system that must be satisfied after the callback occurs. +- alg_eqs: Algebraic equations of the system that must be satisfied after the callback occurs. """ struct SymbolicDiscreteCallback <: AbstractCallback conditions::Any @@ -444,7 +445,7 @@ struct SymbolicDiscreteCallback <: AbstractCallback function SymbolicDiscreteCallback( condition, affect = nothing; initialize = nothing, finalize = nothing, iv = nothing, - algeeqs = Equation[], discrete_parameters = Any[], reinitializealg = nothing) + alg_eqs = Equation[], discrete_parameters = Any[], reinitializealg = nothing) c = is_timed_condition(condition) ? condition : value(scalarize(condition)) if isnothing(reinitializealg) @@ -453,9 +454,9 @@ struct SymbolicDiscreteCallback <: AbstractCallback reinitializealg = SciMLBase.CheckInit() : reinitializealg = SciMLBase.NoInit() end - new(c, make_affect(affect; iv, algeeqs, discrete_parameters), - make_affect(initialize; iv, algeeqs, discrete_parameters), - make_affect(finalize; iv, algeeqs, discrete_parameters), reinitializealg) + new(c, make_affect(affect; iv, alg_eqs, discrete_parameters), + make_affect(initialize; iv, alg_eqs, discrete_parameters), + make_affect(finalize; iv, alg_eqs, discrete_parameters), reinitializealg) end # Default affect to nothing end @@ -468,7 +469,7 @@ SymbolicDiscreteCallback(cb::SymbolicDiscreteCallback, args...; kwargs...) = cb Generate discrete callbacks. """ function SymbolicDiscreteCallbacks(events; discrete_parameters::Vector = Any[], - algeeqs::Vector{Equation} = Equation[], iv = nothing) + alg_eqs::Vector{Equation} = Equation[], iv = nothing) callbacks = SymbolicDiscreteCallback[] isnothing(events) && return callbacks @@ -478,7 +479,7 @@ function SymbolicDiscreteCallbacks(events; discrete_parameters::Vector = Any[], for event in events cond, affs = event isa Pair ? (event[1], event[2]) : (event, nothing) push!(callbacks, - SymbolicDiscreteCallback(cond, affs; iv, algeeqs, discrete_parameters)) + SymbolicDiscreteCallback(cond, affs; iv, alg_eqs, discrete_parameters)) end callbacks end diff --git a/src/systems/diffeqs/odesystem.jl b/src/systems/diffeqs/odesystem.jl index e8c6be8c3e..8f211136f0 100644 --- a/src/systems/diffeqs/odesystem.jl +++ b/src/systems/diffeqs/odesystem.jl @@ -336,10 +336,10 @@ function ODESystem(deqs::AbstractVector{<:Equation}, iv, dvs, ps; throw(ArgumentError("System names must be unique.")) end - algeeqs = filter(eq -> eq.lhs isa Union{Symbolic, Number} && !is_diff_equation(eq), + alg_eqs = filter(eq -> eq.lhs isa Union{Symbolic, Number} && !is_diff_equation(eq), deqs) - cont_callbacks = SymbolicContinuousCallbacks(continuous_events; algeeqs, iv) - disc_callbacks = SymbolicDiscreteCallbacks(discrete_events; algeeqs, iv) + cont_callbacks = SymbolicContinuousCallbacks(continuous_events; alg_eqs, iv) + disc_callbacks = SymbolicDiscreteCallbacks(discrete_events; alg_eqs, iv) if is_dde === nothing is_dde = _check_if_dde(deqs, iv′, systems) diff --git a/src/systems/diffeqs/sdesystem.jl b/src/systems/diffeqs/sdesystem.jl index c52f1d6559..5e26e13d3d 100644 --- a/src/systems/diffeqs/sdesystem.jl +++ b/src/systems/diffeqs/sdesystem.jl @@ -270,10 +270,10 @@ function SDESystem(deqs::AbstractVector{<:Equation}, neqs::AbstractArray, iv, dv Wfact = RefValue(EMPTY_JAC) Wfact_t = RefValue(EMPTY_JAC) - algeeqs = filter(eq -> eq.lhs isa Union{Symbolic, Number} && !is_diff_equation(eq), + alg_eqs = filter(eq -> eq.lhs isa Union{Symbolic, Number} && !is_diff_equation(eq), deqs) - cont_callbacks = SymbolicContinuousCallbacks(continuous_events; algeeqs, iv) - disc_callbacks = SymbolicDiscreteCallbacks(discrete_events; algeeqs, iv) + cont_callbacks = SymbolicContinuousCallbacks(continuous_events; alg_eqs, iv) + disc_callbacks = SymbolicDiscreteCallbacks(discrete_events; alg_eqs, iv) if is_dde === nothing is_dde = _check_if_dde(deqs, iv′, systems) end diff --git a/src/systems/index_cache.jl b/src/systems/index_cache.jl index c689b351e0..948af4dfa5 100644 --- a/src/systems/index_cache.jl +++ b/src/systems/index_cache.jl @@ -345,13 +345,8 @@ function IndexCache(sys::AbstractSystem) vs = vars(eq.rhs; op = Nothing) timeseries = TimeseriesSetType() if is_time_dependent(sys) - unknown_set = Set(unknowns(sys)) for v in vs - if in(v, unknown_set) - empty!(timeseries) - push!(timeseries, ContinuousTimeseries()) - break - elseif (idx = get(disc_idxs, v, nothing)) !== nothing + if (idx = get(disc_idxs, v, nothing)) !== nothing push!(timeseries, idx.clock_idx) elseif iscall(v) && operation(v) === getindex && (idx = get(disc_idxs, arguments(v)[1], nothing)) !== nothing diff --git a/test/accessor_functions.jl b/test/accessor_functions.jl index 9272fb9146..4136736a8b 100644 --- a/test/accessor_functions.jl +++ b/test/accessor_functions.jl @@ -152,9 +152,9 @@ let # as I stored the same single 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. bot_cev = ModelingToolkit.SymbolicContinuousCallback( - cevs[1], algeeqs = [O ~ (d + p_bot) * X_bot + Y]) + cevs[1], alg_eqs = [O ~ (d + p_bot) * X_bot + Y]) mid_dev = ModelingToolkit.SymbolicDiscreteCallback( - devs[1], algeeqs = [O ~ (d + p_mid1) * X_mid1 + Y]) + devs[1], alg_eqs = [O ~ (d + p_mid1) * X_mid1 + Y]) @test all_sets_equal( continuous_events_toplevel.([sys_bot, sys_bot_comp, sys_bot_ss])..., [bot_cev]) diff --git a/test/symbolic_events.jl b/test/symbolic_events.jl index c41ab497b8..4c2ce45fd8 100644 --- a/test/symbolic_events.jl +++ b/test/symbolic_events.jl @@ -1321,7 +1321,7 @@ end @test ≈(sol(5.0000001, idxs = x) - sol(4.999999, idxs = x), 0.1, rtol = 1e-4) # Proper re-initialization after parameter change - eqs = [y ~ g^2 - x, D(x) ~ x] + eqs = [y ~ g^2, D(x) ~ x] c_evt = SymbolicContinuousCallback( [t ~ 5.0], [x ~ Pre(x) + 1, g ~ Pre(g) + 1], discrete_parameters = [g], iv = t) @mtkbuild sys = ODESystem(eqs, t, continuous_events = c_evt) @@ -1329,7 +1329,7 @@ end sol = solve(prob, FBDF()) @test sol.ps[g] ≈ [2.0, 3.0] @test ≈(sol(5.00000001, idxs = x) - sol(4.9999999, idxs = x), 1; rtol = 1e-4) - @test ≈(sol(5.00000001, idxs = y), 9 - sol(5.00000001, idxs = x), rtol = 1e-4) + @test ≈(sol(5.00000001, idxs = y), 9, rtol = 1e-4) # Parameters that don't appear in affects should not be mutated. c_evt = [t ~ 5.0] => [x ~ Pre(x) + 1] @@ -1338,4 +1338,3 @@ end sol = solve(prob, FBDF()) @test prob.ps[g] == sol.ps[g] end -# - explicit equation of t in a functional affect From 7b4058b6c8e98096f3f3ef459ebde96c9431b47f Mon Sep 17 00:00:00 2001 From: vyudu Date: Tue, 1 Apr 2025 13:40:07 -0400 Subject: [PATCH 067/122] fix: use discrete_parameters in SII test --- src/systems/callbacks.jl | 5 ++--- test/symbolic_indexing_interface.jl | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/systems/callbacks.jl b/src/systems/callbacks.jl index a7929f9c34..233bbb3e87 100644 --- a/src/systems/callbacks.jl +++ b/src/systems/callbacks.jl @@ -307,6 +307,7 @@ function make_affect(affect::Vector{Equation}; discrete_parameters::AbstractVect # add scalarized unknowns to the map. _dvs = reduce(vcat, map(scalarize, _dvs), init = Any[]) + @show _dvs for u in _dvs aff_map[u] = u end @@ -460,9 +461,7 @@ struct SymbolicDiscreteCallback <: AbstractCallback end # Default affect to nothing end -function SymbolicDiscreteCallback(p::Pair, args...; kwargs...) - SymbolicDiscreteCallback(p[1], p[2], args...; kwargs...) -end +SymbolicDiscreteCallback(p::Pair, args...; kwargs...) = SymbolicDiscreteCallback(p[1], p[2], args...; kwargs...) SymbolicDiscreteCallback(cb::SymbolicDiscreteCallback, args...; kwargs...) = cb """ diff --git a/test/symbolic_indexing_interface.jl b/test/symbolic_indexing_interface.jl index 8b3da5fd72..e352533fbd 100644 --- a/test/symbolic_indexing_interface.jl +++ b/test/symbolic_indexing_interface.jl @@ -230,7 +230,7 @@ end @testset "`timeseries_parameter_index` on unwrapped scalarized timeseries parameter" begin @variables x(t)[1:2] @parameters p(t)[1:2, 1:2] - ev = [x[1] ~ 2.0] => [p ~ -ones(2, 2)] + ev = SymbolicContinuousCallback([x[1] ~ 2.0] => [p ~ -ones(2, 2)], discrete_parameters = [p]) @mtkbuild sys = ODESystem(D(x) ~ p * x, t; continuous_events = [ev]) p = ModelingToolkit.unwrap(p) @test timeseries_parameter_index(sys, p) === ParameterTimeseriesIndex(1, (1, 1)) From 8d0454c43351c76c00bad4b7713e166a749323d0 Mon Sep 17 00:00:00 2001 From: vyudu Date: Tue, 1 Apr 2025 15:01:47 -0400 Subject: [PATCH 068/122] fix: fix model parsing for events --- src/systems/callbacks.jl | 2 +- src/systems/model_parsing.jl | 56 +++++++++++++++++++++++++++++------- test/symbolic_events.jl | 2 +- 3 files changed, 47 insertions(+), 13 deletions(-) diff --git a/src/systems/callbacks.jl b/src/systems/callbacks.jl index 233bbb3e87..50cd2d1ba1 100644 --- a/src/systems/callbacks.jl +++ b/src/systems/callbacks.jl @@ -255,7 +255,7 @@ make_affect(affect::Tuple; kwargs...) = FunctionalAffect(affect...) make_affect(affect::NamedTuple; kwargs...) = FunctionalAffect(; affect...) make_affect(affect::Affect; kwargs...) = affect -function make_affect(affect::Vector{Equation}; discrete_parameters::AbstractVector = Any[], +function make_affect(affect::Vector{Equation}; discrete_parameters = Any[], iv = nothing, alg_eqs::Vector{Equation} = Equation[]) isempty(affect) && return nothing isempty(alg_eqs) && diff --git a/src/systems/model_parsing.jl b/src/systems/model_parsing.jl index bf4100063d..2acc90b721 100644 --- a/src/systems/model_parsing.jl +++ b/src/systems/model_parsing.jl @@ -144,15 +144,23 @@ function _model_macro(mod, fullname::Union{Expr, Symbol}, expr, isconnector) isconnector && push!(exprs.args, :($Setfield.@set!(var"#___sys___".connector_type=$connector_type(var"#___sys___")))) - !isempty(c_evts) && push!(exprs.args, - :($Setfield.@set!(var"#___sys___".continuous_events=$SymbolicContinuousCallback.([ - $(c_evts...) - ])))) + push!(exprs.args, :(alg_eqs = $(alg_equations)(var"#___sys___"))) + d_evt_exs = map(d_evts) do evt + length(evt.args) == 2 ? + :($SymbolicDiscreteCallback($(evt.args[1]); iv = $iv, alg_eqs, $(evt.args[2]...))) : + :($SymbolicDiscreteCallback($(evt.args[1]); iv = $iv, alg_eqs)) + end !isempty(d_evts) && push!(exprs.args, - :($Setfield.@set!(var"#___sys___".discrete_events=$SymbolicDiscreteCallback.([ - $(d_evts...) - ])))) + :($Setfield.@set!(var"#___sys___".discrete_events=[$(d_evt_exs...)]))) + + c_evt_exs = map(c_evts) do evt + length(evt.args) == 2 ? + :($SymbolicContinuousCallback($(evt.args[1]); iv = $iv, alg_eqs, $(evt.args[2]...))) : + :($SymbolicContinuousCallback($(evt.args[1]); iv = $iv, alg_eqs)) + end + !isempty(c_evts) && push!(exprs.args, + :($Setfield.@set!(var"#___sys___".continuous_events=[$(c_evt_exs...)]))) f = if length(where_types) == 0 :($(Symbol(:__, name, :__))(; name, $(kwargs...)) = $exprs) @@ -1147,8 +1155,16 @@ end function parse_continuous_events!(c_evts, dict, body) dict[:continuous_events] = [] Base.remove_linenums!(body) - for arg in body.args - push!(c_evts, arg) + for line in body.args + if length(line.args) == 3 && line.args[1] == :(=>) + push!(c_evts, :(($line,))) + elseif length(line.args) == 2 + event = line.args[1] + kwargs = parse_event_kwargs(line.args[2]) + push!(c_evts, :(($event, $kwargs))) + else + error("Malformed continuous event $line.") + end push!(dict[:continuous_events], readable_code.(c_evts)...) end end @@ -1156,12 +1172,30 @@ end function parse_discrete_events!(d_evts, dict, body) dict[:discrete_events] = [] Base.remove_linenums!(body) - for arg in body.args - push!(d_evts, arg) + for line in body.args + if length(line.args) == 3 && line.args[1] == :(=>) + push!(d_evts, :(($line,))) + elseif length(line.args) == 2 + event = line.args[1] + kwargs = parse_event_kwargs(line.args[2]) + push!(d_evts, :(($event, $kwargs))) + else + error("Malformed discrete event $line.") + end push!(dict[:discrete_events], readable_code.(d_evts)...) end end +function parse_event_kwargs(disc_expr) + kwargs = :([]) + for arg in disc_expr.args + (arg.head != :(=)) && error("Malformed event kwarg $arg.") + (arg.args[1] isa Symbol) || error("Invalid keyword argument name $(arg.args[1]).") + push!(kwargs.args, arg) + end + kwargs +end + function parse_constraints!(cons, dict, body) dict[:constraints] = [] Base.remove_linenums!(body) diff --git a/test/symbolic_events.jl b/test/symbolic_events.jl index 4c2ce45fd8..115e681f5a 100644 --- a/test/symbolic_events.jl +++ b/test/symbolic_events.jl @@ -1213,7 +1213,7 @@ end D(x) ~ -k * x end @discrete_events begin - (t == 1.0) => [k ~ 1.0]#, discrete_parameters = [k] + (t == 1.0) => [k ~ 1.0], [discrete_parameters = k] end end @mtkbuild decay = DECAY() From 3c036b06cce3572a6573265cead37dd0102292a8 Mon Sep 17 00:00:00 2001 From: vyudu Date: Tue, 1 Apr 2025 15:10:38 -0400 Subject: [PATCH 069/122] docs: document the discrete_parameters --- docs/src/basics/MTKLanguage.md | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/docs/src/basics/MTKLanguage.md b/docs/src/basics/MTKLanguage.md index e91f2bcb67..ea9c9da3df 100644 --- a/docs/src/basics/MTKLanguage.md +++ b/docs/src/basics/MTKLanguage.md @@ -203,6 +203,7 @@ getdefault(model_c3.model_a.k_array[2]) - Defining continuous events as described [here](https://docs.sciml.ai/ModelingToolkit/stable/basics/Events/#Continuous-Events). - If this block is not defined in the model, no continuous events will be added. + - Discrete parameters and other keyword arguments should be specified in a vector, as seen below. ```@example mtkmodel-example using ModelingToolkit @@ -210,7 +211,7 @@ using ModelingToolkit: t @mtkmodel M begin @parameters begin - k + k(t) end @variables begin x(t) @@ -223,21 +224,24 @@ using ModelingToolkit: t @continuous_events begin [x ~ 1.5] => [x ~ 5, y ~ 5] [t ~ 4] => [x ~ 10] + [t ~ 5] => [k ~ 3], [discrete_parameters = k] end end ``` + #### `@discrete_events` begin block - Defining discrete events as described [here](https://docs.sciml.ai/ModelingToolkit/stable/basics/Events/#Discrete-events-support). - If this block is not defined in the model, no discrete events will be added. + - Discrete parameters and other keyword arguments should be specified in a vector, as seen below. ```@example mtkmodel-example using ModelingToolkit @mtkmodel M begin @parameters begin - k + k(t) end @variables begin x(t) @@ -248,7 +252,8 @@ using ModelingToolkit D(y) ~ -k end @discrete_events begin - (t == 1.5) => [x ~ x + 5, y ~ 5] + (t == 1.5) => [x ~ Pre(x) + 5, y ~ 5] + (t == 2.5) => [k ~ Pre(k) * 2], [discrete_parameters = k] end end ``` From 7295caa4ab8fa3b11b28a2081cb4db34d109f0fd Mon Sep 17 00:00:00 2001 From: vyudu Date: Tue, 1 Apr 2025 15:20:50 -0400 Subject: [PATCH 070/122] format --- docs/src/basics/MTKLanguage.md | 1 - src/systems/callbacks.jl | 11 ++++++++--- src/systems/model_parsing.jl | 15 ++++++++------- test/symbolic_indexing_interface.jl | 3 ++- 4 files changed, 18 insertions(+), 12 deletions(-) diff --git a/docs/src/basics/MTKLanguage.md b/docs/src/basics/MTKLanguage.md index ea9c9da3df..ba6d2c34b5 100644 --- a/docs/src/basics/MTKLanguage.md +++ b/docs/src/basics/MTKLanguage.md @@ -229,7 +229,6 @@ using ModelingToolkit: t end ``` - #### `@discrete_events` begin block - Defining discrete events as described [here](https://docs.sciml.ai/ModelingToolkit/stable/basics/Events/#Discrete-events-support). diff --git a/src/systems/callbacks.jl b/src/systems/callbacks.jl index 50cd2d1ba1..33163efaa8 100644 --- a/src/systems/callbacks.jl +++ b/src/systems/callbacks.jl @@ -244,9 +244,12 @@ struct SymbolicContinuousCallback <: AbstractCallback end # Default affect to nothing end -SymbolicContinuousCallback(p::Pair, args...; kwargs...) = SymbolicContinuousCallback(p[1], p[2], args...; kwargs...) +function SymbolicContinuousCallback(p::Pair, args...; kwargs...) + SymbolicContinuousCallback(p[1], p[2], args...; kwargs...) +end -function SymbolicContinuousCallback(cb::SymbolicContinuousCallback, args...; iv = nothing, alg_eqs = Equation[], kwargs...) +function SymbolicContinuousCallback(cb::SymbolicContinuousCallback, args...; + iv = nothing, alg_eqs = Equation[], kwargs...) cb end @@ -461,7 +464,9 @@ struct SymbolicDiscreteCallback <: AbstractCallback end # Default affect to nothing end -SymbolicDiscreteCallback(p::Pair, args...; kwargs...) = SymbolicDiscreteCallback(p[1], p[2], args...; kwargs...) +function SymbolicDiscreteCallback(p::Pair, args...; kwargs...) + SymbolicDiscreteCallback(p[1], p[2], args...; kwargs...) +end SymbolicDiscreteCallback(cb::SymbolicDiscreteCallback, args...; kwargs...) = cb """ diff --git a/src/systems/model_parsing.jl b/src/systems/model_parsing.jl index 2acc90b721..93b92509b6 100644 --- a/src/systems/model_parsing.jl +++ b/src/systems/model_parsing.jl @@ -146,7 +146,7 @@ function _model_macro(mod, fullname::Union{Expr, Symbol}, expr, isconnector) push!(exprs.args, :(alg_eqs = $(alg_equations)(var"#___sys___"))) d_evt_exs = map(d_evts) do evt - length(evt.args) == 2 ? + length(evt.args) == 2 ? :($SymbolicDiscreteCallback($(evt.args[1]); iv = $iv, alg_eqs, $(evt.args[2]...))) : :($SymbolicDiscreteCallback($(evt.args[1]); iv = $iv, alg_eqs)) end @@ -155,8 +155,9 @@ function _model_macro(mod, fullname::Union{Expr, Symbol}, expr, isconnector) :($Setfield.@set!(var"#___sys___".discrete_events=[$(d_evt_exs...)]))) c_evt_exs = map(c_evts) do evt - length(evt.args) == 2 ? - :($SymbolicContinuousCallback($(evt.args[1]); iv = $iv, alg_eqs, $(evt.args[2]...))) : + length(evt.args) == 2 ? + :($SymbolicContinuousCallback( + $(evt.args[1]); iv = $iv, alg_eqs, $(evt.args[2]...))) : :($SymbolicContinuousCallback($(evt.args[1]); iv = $iv, alg_eqs)) end !isempty(c_evts) && push!(exprs.args, @@ -1156,9 +1157,9 @@ function parse_continuous_events!(c_evts, dict, body) dict[:continuous_events] = [] Base.remove_linenums!(body) for line in body.args - if length(line.args) == 3 && line.args[1] == :(=>) + if length(line.args) == 3 && line.args[1] == :(=>) push!(c_evts, :(($line,))) - elseif length(line.args) == 2 + elseif length(line.args) == 2 event = line.args[1] kwargs = parse_event_kwargs(line.args[2]) push!(c_evts, :(($event, $kwargs))) @@ -1173,9 +1174,9 @@ function parse_discrete_events!(d_evts, dict, body) dict[:discrete_events] = [] Base.remove_linenums!(body) for line in body.args - if length(line.args) == 3 && line.args[1] == :(=>) + if length(line.args) == 3 && line.args[1] == :(=>) push!(d_evts, :(($line,))) - elseif length(line.args) == 2 + elseif length(line.args) == 2 event = line.args[1] kwargs = parse_event_kwargs(line.args[2]) push!(d_evts, :(($event, $kwargs))) diff --git a/test/symbolic_indexing_interface.jl b/test/symbolic_indexing_interface.jl index e352533fbd..44821987b5 100644 --- a/test/symbolic_indexing_interface.jl +++ b/test/symbolic_indexing_interface.jl @@ -230,7 +230,8 @@ end @testset "`timeseries_parameter_index` on unwrapped scalarized timeseries parameter" begin @variables x(t)[1:2] @parameters p(t)[1:2, 1:2] - ev = SymbolicContinuousCallback([x[1] ~ 2.0] => [p ~ -ones(2, 2)], discrete_parameters = [p]) + ev = SymbolicContinuousCallback( + [x[1] ~ 2.0] => [p ~ -ones(2, 2)], discrete_parameters = [p]) @mtkbuild sys = ODESystem(D(x) ~ p * x, t; continuous_events = [ev]) p = ModelingToolkit.unwrap(p) @test timeseries_parameter_index(sys, p) === ParameterTimeseriesIndex(1, (1, 1)) From 9e12c830761787e94f99f0372e69b96f7c7e5521 Mon Sep 17 00:00:00 2001 From: vyudu Date: Tue, 1 Apr 2025 16:37:51 -0400 Subject: [PATCH 071/122] fix: remove the plural constructors --- docs/src/systems/DiscreteSystem.md | 1 - docs/src/systems/ImplicitDiscreteSystem.md | 1 - src/systems/callbacks.jl | 61 ++++------------------ src/systems/diffeqs/odesystem.jl | 7 ++- src/systems/diffeqs/sdesystem.jl | 6 ++- src/systems/jumps/jumpsystem.jl | 22 +------- test/symbolic_events.jl | 33 ------------ test/symbolic_indexing_interface.jl | 3 +- 8 files changed, 22 insertions(+), 112 deletions(-) diff --git a/docs/src/systems/DiscreteSystem.md b/docs/src/systems/DiscreteSystem.md index f8a71043ab..55a02e5714 100644 --- a/docs/src/systems/DiscreteSystem.md +++ b/docs/src/systems/DiscreteSystem.md @@ -12,7 +12,6 @@ DiscreteSystem - `get_unknowns(sys)` or `unknowns(sys)`: The set of unknowns in the discrete system. - `get_ps(sys)` or `parameters(sys)`: The parameters of the discrete system. - `get_iv(sys)`: The independent variable of the discrete system - - `discrete_events(sys)`: The set of discrete events in the discrete system. ## Transformations diff --git a/docs/src/systems/ImplicitDiscreteSystem.md b/docs/src/systems/ImplicitDiscreteSystem.md index d69f88f106..d687502b49 100644 --- a/docs/src/systems/ImplicitDiscreteSystem.md +++ b/docs/src/systems/ImplicitDiscreteSystem.md @@ -12,7 +12,6 @@ ImplicitDiscreteSystem - `get_unknowns(sys)` or `unknowns(sys)`: The set of unknowns in the implicit discrete system. - `get_ps(sys)` or `parameters(sys)`: The parameters of the implicit discrete system. - `get_iv(sys)`: The independent variable of the implicit discrete system - - `discrete_events(sys)`: The set of discrete events in the implicit discrete system. ## Transformations diff --git a/src/systems/callbacks.jl b/src/systems/callbacks.jl index 33163efaa8..f469c1a1ec 100644 --- a/src/systems/callbacks.jl +++ b/src/systems/callbacks.jl @@ -69,7 +69,6 @@ discretes(a::AffectSystem) = a.discretes unknowns(a::AffectSystem) = a.unknowns parameters(a::AffectSystem) = a.parameters aff_to_sys(a::AffectSystem) = a.aff_to_sys -previous_vals(a::AffectSystem) = parameters(system(a)) all_equations(a::AffectSystem) = vcat(equations(system(a)), observed(system(a))) function Base.show(iio::IO, aff::AffectSystem) @@ -149,7 +148,6 @@ function (p::Pre)(x) end return result end - haspre(eq::Equation) = haspre(eq.lhs) || haspre(eq.rhs) haspre(O) = recursive_hasoperator(Pre, O) @@ -247,11 +245,8 @@ end function SymbolicContinuousCallback(p::Pair, args...; kwargs...) SymbolicContinuousCallback(p[1], p[2], args...; kwargs...) end - -function SymbolicContinuousCallback(cb::SymbolicContinuousCallback, args...; - iv = nothing, alg_eqs = Equation[], kwargs...) - cb -end +SymbolicContinuousCallback(cb::SymbolicContinuousCallback, args...; kwargs...) = cb +SymbolicContinuousCallback(cb::Nothing, args...; kwargs...) = nothing make_affect(affect::Nothing; kwargs...) = nothing make_affect(affect::Tuple; kwargs...) = FunctionalAffect(affect...) @@ -310,7 +305,6 @@ function make_affect(affect::Vector{Equation}; discrete_parameters = Any[], # add scalarized unknowns to the map. _dvs = reduce(vcat, map(scalarize, _dvs), init = Any[]) - @show _dvs for u in _dvs aff_map[u] = u end @@ -323,25 +317,6 @@ function make_affect(affect; kwargs...) error("Malformed affect $(affect). This should be a vector of equations or a tuple specifying a functional affect.") end -""" -Generate continuous callbacks. -""" -function SymbolicContinuousCallbacks(events; discrete_parameters = Any[], - alg_eqs::Vector{Equation} = Equation[], iv = nothing) - callbacks = SymbolicContinuousCallback[] - isnothing(events) && return callbacks - - events isa AbstractVector || (events = [events]) - isempty(events) && return callbacks - - for event in events - cond, affs = event isa Pair ? (event[1], event[2]) : (event, nothing) - push!(callbacks, - SymbolicContinuousCallback(cond, affs; iv, alg_eqs, discrete_parameters)) - end - callbacks -end - function Base.show(io::IO, cb::AbstractCallback) indent = get(io, :indent, 0) iio = IOContext(io, :indent => indent + 1) @@ -422,8 +397,6 @@ end ################################ ######## Discrete events ####### ################################ - -# TODO: Iterative callbacks """ SymbolicDiscreteCallback(conditions::Vector{Equation}, affect = nothing, iv = nothing; initialize = nothing, finalize = nothing, alg_eqs = Equation[]) @@ -468,25 +441,7 @@ function SymbolicDiscreteCallback(p::Pair, args...; kwargs...) SymbolicDiscreteCallback(p[1], p[2], args...; kwargs...) end SymbolicDiscreteCallback(cb::SymbolicDiscreteCallback, args...; kwargs...) = cb - -""" -Generate discrete callbacks. -""" -function SymbolicDiscreteCallbacks(events; discrete_parameters::Vector = Any[], - alg_eqs::Vector{Equation} = Equation[], iv = nothing) - callbacks = SymbolicDiscreteCallback[] - - isnothing(events) && return callbacks - events isa AbstractVector || (events = [events]) - isempty(events) && return callbacks - - for event in events - cond, affs = event isa Pair ? (event[1], event[2]) : (event, nothing) - push!(callbacks, - SymbolicDiscreteCallback(cond, affs; iv, alg_eqs, discrete_parameters)) - end - callbacks -end +SymbolicDiscreteCallback(cb::Nothing, args...; kwargs...) = nothing function is_timed_condition(condition::T) where {T} if T === Num @@ -500,10 +455,14 @@ function is_timed_condition(condition::T) where {T} end end +to_cb_vector(cbs::Vector{<:AbstractCallback}) = cbs +to_cb_vector(cbs::Vector) = Vector{AbstractCallback}(cbs) +to_cb_vector(cbs::Nothing) = AbstractCallback[] +to_cb_vector(cb::AbstractCallback) = [cb] + ############################################ ########## Namespacing Utilities ########### ############################################ - function namespace_affects(affect::FunctionalAffect, s) FunctionalAffect(func(affect), renamespace.((s,), unknowns(affect)), @@ -530,7 +489,7 @@ function namespace_callback(cb::SymbolicContinuousCallback, s)::SymbolicContinuo affect_neg = namespace_affects(affect_negs(cb), s), initialize = namespace_affects(initialize_affects(cb), s), finalize = namespace_affects(finalize_affects(cb), s), - rootfind = cb.rootfind) + rootfind = cb.rootfind, reinitializealg = cb.reinitializealg) end function namespace_conditions(condition, s) @@ -542,7 +501,7 @@ function namespace_callback(cb::SymbolicDiscreteCallback, s)::SymbolicDiscreteCa namespace_conditions(conditions(cb), s), namespace_affects(affects(cb), s), initialize = namespace_affects(initialize_affects(cb), s), - finalize = namespace_affects(finalize_affects(cb), s)) + finalize = namespace_affects(finalize_affects(cb), s), reinitializealg = cb.reinitializealg) end function Base.hash(cb::AbstractCallback, s::UInt) diff --git a/src/systems/diffeqs/odesystem.jl b/src/systems/diffeqs/odesystem.jl index 8f211136f0..0f2d63cf69 100644 --- a/src/systems/diffeqs/odesystem.jl +++ b/src/systems/diffeqs/odesystem.jl @@ -338,8 +338,11 @@ function ODESystem(deqs::AbstractVector{<:Equation}, iv, dvs, ps; alg_eqs = filter(eq -> eq.lhs isa Union{Symbolic, Number} && !is_diff_equation(eq), deqs) - cont_callbacks = SymbolicContinuousCallbacks(continuous_events; alg_eqs, iv) - disc_callbacks = SymbolicDiscreteCallbacks(discrete_events; alg_eqs, iv) + @show continuous_events + @show discrete_events + cont_callbacks = to_cb_vector(SymbolicContinuousCallback.( + continuous_events; alg_eqs, iv)) + disc_callbacks = to_cb_vector(SymbolicDiscreteCallback.(discrete_events; alg_eqs, iv)) if is_dde === nothing is_dde = _check_if_dde(deqs, iv′, systems) diff --git a/src/systems/diffeqs/sdesystem.jl b/src/systems/diffeqs/sdesystem.jl index 5e26e13d3d..ee0329cbe1 100644 --- a/src/systems/diffeqs/sdesystem.jl +++ b/src/systems/diffeqs/sdesystem.jl @@ -272,8 +272,10 @@ function SDESystem(deqs::AbstractVector{<:Equation}, neqs::AbstractArray, iv, dv alg_eqs = filter(eq -> eq.lhs isa Union{Symbolic, Number} && !is_diff_equation(eq), deqs) - cont_callbacks = SymbolicContinuousCallbacks(continuous_events; alg_eqs, iv) - disc_callbacks = SymbolicDiscreteCallbacks(discrete_events; alg_eqs, iv) + cont_callbacks = to_cb_vector(SymbolicContinuousCallback.( + continuous_events; alg_eqs, iv)) + disc_callbacks = to_cb_vector(SymbolicDiscreteCallback.(discrete_events; alg_eqs, iv)) + if is_dde === nothing is_dde = _check_if_dde(deqs, iv′, systems) end diff --git a/src/systems/jumps/jumpsystem.jl b/src/systems/jumps/jumpsystem.jl index ea5e0638d1..9acdd64cfa 100644 --- a/src/systems/jumps/jumpsystem.jl +++ b/src/systems/jumps/jumpsystem.jl @@ -1,19 +1,5 @@ const JumpType = Union{VariableRateJump, ConstantRateJump, MassActionJump} -# modifies the expression representing an affect function to -# call reset_aggregated_jumps!(integrator). -# assumes iip -function _reset_aggregator!(expr, integrator) - @assert Meta.isexpr(expr, :function) - body = expr.args[end] - body = quote - $body - $reset_aggregated_jumps!($integrator) - end - expr.args[end] = body - return nothing -end - """ $(TYPEDEF) @@ -90,11 +76,6 @@ struct JumpSystem{U <: ArrayPartition} <: AbstractTimeDependentSystem """ connector_type::Any """ - A `Vector{SymbolicContinuousCallback}` that model events. - The integrator will use root finding to guarantee that it steps at each zero crossing. - """ - continuous_events::Vector{SymbolicContinuousCallback} - """ A `Vector{SymbolicDiscreteCallback}` that models events. Symbolic analog to `SciMLBase.DiscreteCallback` that executes an affect when a given condition is true at the end of an integration step. Note, one must make sure to call @@ -230,8 +211,7 @@ function JumpSystem(eqs, iv, unknowns, ps; end end - cont_callbacks = SymbolicContinuousCallbacks(continuous_events; iv) - disc_callbacks = SymbolicDiscreteCallbacks(discrete_events; iv) + disc_callbacks = to_cb_vector(SymbolicDiscreteCallback.(discrete_events; iv)) JumpSystem{typeof(ap)}(Threads.atomic_add!(SYSTEM_COUNT, UInt(1)), ap, iv′, us′, ps′, var_to_name, observed, name, description, systems, diff --git a/test/symbolic_events.jl b/test/symbolic_events.jl index 115e681f5a..adcb24dc76 100644 --- a/test/symbolic_events.jl +++ b/test/symbolic_events.jl @@ -1,9 +1,7 @@ using ModelingToolkit, OrdinaryDiffEq, StochasticDiffEq, JumpProcesses, Test using SciMLStructures: canonicalize, Discrete using ModelingToolkit: SymbolicContinuousCallback, - SymbolicContinuousCallbacks, SymbolicDiscreteCallback, - SymbolicDiscreteCallbacks, get_callback, t_nounits as t, D_nounits as D, @@ -88,37 +86,6 @@ affect_neg = [x ~ 1] @test e isa SymbolicContinuousCallback @test isequal(equations(e), eqs) @test e.rootfind == SciMLBase.LeftRootFind - - # test plural constructor - e = SymbolicContinuousCallbacks(eqs[]) - @test e isa Vector{SymbolicContinuousCallback} - @test isequal(equations(e[]), eqs) - @test e[].affect == nothing - - e = SymbolicContinuousCallbacks(eqs) - @test e isa Vector{SymbolicContinuousCallback} - @test isequal(equations(e[]), eqs) - @test e[].affect == nothing - - e = SymbolicContinuousCallbacks(eqs[] => affect) - @test e isa Vector{SymbolicContinuousCallback} - @test isequal(equations(e[]), eqs) - @test e[].affect isa AffectSystem - - e = SymbolicContinuousCallbacks(eqs => affect) - @test e isa Vector{SymbolicContinuousCallback} - @test isequal(equations(e[]), eqs) - @test e[].affect isa AffectSystem - - e = SymbolicContinuousCallbacks([eqs[] => affect]) - @test e isa Vector{SymbolicContinuousCallback} - @test isequal(equations(e[]), eqs) - @test e[].affect isa AffectSystem - - e = SymbolicContinuousCallbacks([eqs => affect]) - @test e isa Vector{SymbolicContinuousCallback} - @test isequal(equations(e[]), eqs) - @test e[].affect isa AffectSystem end @testset "ImperativeAffect constructors" begin diff --git a/test/symbolic_indexing_interface.jl b/test/symbolic_indexing_interface.jl index 44821987b5..613bfc8213 100644 --- a/test/symbolic_indexing_interface.jl +++ b/test/symbolic_indexing_interface.jl @@ -1,5 +1,6 @@ using ModelingToolkit, SymbolicIndexingInterface, SciMLBase -using ModelingToolkit: t_nounits as t, D_nounits as D, ParameterIndex +using ModelingToolkit: t_nounits as t, D_nounits as D, ParameterIndex, + SymbolicContinuousCallback using SciMLStructures: Tunable @testset "ODESystem" begin From 84903a548dcd73a6cee14ea6d8fbba04dcb4b0a7 Mon Sep 17 00:00:00 2001 From: vyudu Date: Tue, 1 Apr 2025 23:51:47 -0400 Subject: [PATCH 072/122] fix: fix model parsing error --- src/systems/callbacks.jl | 1 + src/systems/diffeqs/odesystem.jl | 2 -- src/systems/jumps/jumpsystem.jl | 2 +- src/systems/model_parsing.jl | 38 ++++++++++++++++---------------- 4 files changed, 21 insertions(+), 22 deletions(-) diff --git a/src/systems/callbacks.jl b/src/systems/callbacks.jl index f469c1a1ec..cfe31511c4 100644 --- a/src/systems/callbacks.jl +++ b/src/systems/callbacks.jl @@ -263,6 +263,7 @@ function make_affect(affect::Vector{Equation}; discrete_parameters = Any[], @warn "No independent variable specified. Defaulting to t_nounits." end + discrete_parameters isa AbstractVector || (discrete_parameters = [discrete_parameters]) for p in discrete_parameters occursin(unwrap(iv), unwrap(p)) || error("Non-time dependent parameter $p passed in as a discrete. Must be declared as @parameters $p(t).") diff --git a/src/systems/diffeqs/odesystem.jl b/src/systems/diffeqs/odesystem.jl index 0f2d63cf69..bc32d9d9fb 100644 --- a/src/systems/diffeqs/odesystem.jl +++ b/src/systems/diffeqs/odesystem.jl @@ -338,8 +338,6 @@ function ODESystem(deqs::AbstractVector{<:Equation}, iv, dvs, ps; alg_eqs = filter(eq -> eq.lhs isa Union{Symbolic, Number} && !is_diff_equation(eq), deqs) - @show continuous_events - @show discrete_events cont_callbacks = to_cb_vector(SymbolicContinuousCallback.( continuous_events; alg_eqs, iv)) disc_callbacks = to_cb_vector(SymbolicDiscreteCallback.(discrete_events; alg_eqs, iv)) diff --git a/src/systems/jumps/jumpsystem.jl b/src/systems/jumps/jumpsystem.jl index 9acdd64cfa..610283591e 100644 --- a/src/systems/jumps/jumpsystem.jl +++ b/src/systems/jumps/jumpsystem.jl @@ -216,7 +216,7 @@ function JumpSystem(eqs, iv, unknowns, ps; JumpSystem{typeof(ap)}(Threads.atomic_add!(SYSTEM_COUNT, UInt(1)), ap, iv′, us′, ps′, var_to_name, observed, name, description, systems, defaults, guesses, initializesystem, initialization_eqs, connector_type, - cont_callbacks, disc_callbacks, + disc_callbacks, parameter_dependencies, metadata, gui_metadata, checks = checks) end diff --git a/src/systems/model_parsing.jl b/src/systems/model_parsing.jl index 93b92509b6..33ade7faf3 100644 --- a/src/systems/model_parsing.jl +++ b/src/systems/model_parsing.jl @@ -144,24 +144,24 @@ function _model_macro(mod, fullname::Union{Expr, Symbol}, expr, isconnector) isconnector && push!(exprs.args, :($Setfield.@set!(var"#___sys___".connector_type=$connector_type(var"#___sys___")))) - push!(exprs.args, :(alg_eqs = $(alg_equations)(var"#___sys___"))) - d_evt_exs = map(d_evts) do evt - length(evt.args) == 2 ? - :($SymbolicDiscreteCallback($(evt.args[1]); iv = $iv, alg_eqs, $(evt.args[2]...))) : - :($SymbolicDiscreteCallback($(evt.args[1]); iv = $iv, alg_eqs)) - end - - !isempty(d_evts) && push!(exprs.args, - :($Setfield.@set!(var"#___sys___".discrete_events=[$(d_evt_exs...)]))) + if !isempty(d_evts) || !isempty(c_evts) + push!(exprs.args, :(alg_eqs = $(alg_equations)(var"#___sys___"))) + !isempty(d_evts) && begin + d_exprs = [:($(SymbolicDiscreteCallback)( + $(evt.args[1]); iv = $iv, alg_eqs, $(evt.args[2])...)) + for evt in d_evts] + push!(exprs.args, + :($Setfield.@set!(var"#___sys___".discrete_events=[$(d_exprs...)]))) + end - c_evt_exs = map(c_evts) do evt - length(evt.args) == 2 ? - :($SymbolicContinuousCallback( - $(evt.args[1]); iv = $iv, alg_eqs, $(evt.args[2]...))) : - :($SymbolicContinuousCallback($(evt.args[1]); iv = $iv, alg_eqs)) + !isempty(c_evts) && begin + c_exprs = [:($(SymbolicContinuousCallback)( + $(evt.args[1]); iv = $iv, alg_eqs, $(evt.args[2])...)) + for evt in c_evts] + push!(exprs.args, + :($Setfield.@set!(var"#___sys___".continuous_events=[$(c_exprs...)]))) + end end - !isempty(c_evts) && push!(exprs.args, - :($Setfield.@set!(var"#___sys___".continuous_events=[$(c_evt_exs...)]))) f = if length(where_types) == 0 :($(Symbol(:__, name, :__))(; name, $(kwargs...)) = $exprs) @@ -1158,7 +1158,7 @@ function parse_continuous_events!(c_evts, dict, body) Base.remove_linenums!(body) for line in body.args if length(line.args) == 3 && line.args[1] == :(=>) - push!(c_evts, :(($line,))) + push!(c_evts, :(($line, ()))) elseif length(line.args) == 2 event = line.args[1] kwargs = parse_event_kwargs(line.args[2]) @@ -1175,7 +1175,7 @@ function parse_discrete_events!(d_evts, dict, body) Base.remove_linenums!(body) for line in body.args if length(line.args) == 3 && line.args[1] == :(=>) - push!(d_evts, :(($line,))) + push!(d_evts, :(($line, ()))) elseif length(line.args) == 2 event = line.args[1] kwargs = parse_event_kwargs(line.args[2]) @@ -1192,7 +1192,7 @@ function parse_event_kwargs(disc_expr) for arg in disc_expr.args (arg.head != :(=)) && error("Malformed event kwarg $arg.") (arg.args[1] isa Symbol) || error("Invalid keyword argument name $(arg.args[1]).") - push!(kwargs.args, arg) + push!(kwargs.args, :($(QuoteNode(arg.args[1])) => $(arg.args[2]))) end kwargs end From 0c09dd0c5bad700323d54f6e61f1ba46a35f9afc Mon Sep 17 00:00:00 2001 From: vyudu Date: Wed, 2 Apr 2025 00:07:58 -0400 Subject: [PATCH 073/122] fix: add continuous_events back --- src/systems/jumps/jumpsystem.jl | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/systems/jumps/jumpsystem.jl b/src/systems/jumps/jumpsystem.jl index 610283591e..f660e9f412 100644 --- a/src/systems/jumps/jumpsystem.jl +++ b/src/systems/jumps/jumpsystem.jl @@ -75,6 +75,7 @@ struct JumpSystem{U <: ArrayPartition} <: AbstractTimeDependentSystem Type of the system. """ connector_type::Any + continuous_events::Vector{SymbolicContinuousCallback} """ A `Vector{SymbolicDiscreteCallback}` that models events. Symbolic analog to `SciMLBase.DiscreteCallback` that executes an affect when a given condition is @@ -212,11 +213,12 @@ function JumpSystem(eqs, iv, unknowns, ps; end disc_callbacks = to_cb_vector(SymbolicDiscreteCallback.(discrete_events; iv)) + cont_callbacks = to_cb_vector(SymbolicContinuousCallback.(discrete_events; iv)) JumpSystem{typeof(ap)}(Threads.atomic_add!(SYSTEM_COUNT, UInt(1)), ap, iv′, us′, ps′, var_to_name, observed, name, description, systems, defaults, guesses, initializesystem, initialization_eqs, connector_type, - disc_callbacks, + cont_callbacks, disc_callbacks, parameter_dependencies, metadata, gui_metadata, checks = checks) end From 50e9368a28cbdd88fdea359f1514c63047846bcd Mon Sep 17 00:00:00 2001 From: vyudu Date: Wed, 2 Apr 2025 00:24:06 -0400 Subject: [PATCH 074/122] fix: allow Arr in tovar --- src/parameters.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/parameters.jl b/src/parameters.jl index 91121b7cbb..d1690da968 100644 --- a/src/parameters.jl +++ b/src/parameters.jl @@ -62,7 +62,7 @@ toparam(s::Num) = wrap(toparam(value(s))) Maps the variable to an unknown. """ -tovar(s::Symbolic) = setmetadata(s, MTKVariableTypeCtx, VARIABLE) +tovar(s::Union{Symbolic, Arr}) = setmetadata(s, MTKVariableTypeCtx, VARIABLE) tovar(s::Num) = Num(tovar(value(s))) """ From 45ec229b249f1f16aaaf24c51dd566f76556c09b Mon Sep 17 00:00:00 2001 From: vyudu Date: Wed, 2 Apr 2025 00:27:46 -0400 Subject: [PATCH 075/122] fix: allow Arr in tovar --- src/parameters.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/parameters.jl b/src/parameters.jl index d1690da968..ca8bc76c2b 100644 --- a/src/parameters.jl +++ b/src/parameters.jl @@ -62,7 +62,7 @@ toparam(s::Num) = wrap(toparam(value(s))) Maps the variable to an unknown. """ -tovar(s::Union{Symbolic, Arr}) = setmetadata(s, MTKVariableTypeCtx, VARIABLE) +tovar(s::Union{Symbolic, Symbolics.Arr}) = setmetadata(s, MTKVariableTypeCtx, VARIABLE) tovar(s::Num) = Num(tovar(value(s))) """ From 57dd74705ce110414e3510fe20bfebcddf560aa7 Mon Sep 17 00:00:00 2001 From: vyudu Date: Wed, 2 Apr 2025 00:30:46 -0400 Subject: [PATCH 076/122] fix JumpSystem --- src/systems/jumps/jumpsystem.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/systems/jumps/jumpsystem.jl b/src/systems/jumps/jumpsystem.jl index f660e9f412..746f319e33 100644 --- a/src/systems/jumps/jumpsystem.jl +++ b/src/systems/jumps/jumpsystem.jl @@ -213,7 +213,7 @@ function JumpSystem(eqs, iv, unknowns, ps; end disc_callbacks = to_cb_vector(SymbolicDiscreteCallback.(discrete_events; iv)) - cont_callbacks = to_cb_vector(SymbolicContinuousCallback.(discrete_events; iv)) + cont_callbacks = to_cb_vector(SymbolicContinuousCallback.(continuous_events; iv)) JumpSystem{typeof(ap)}(Threads.atomic_add!(SYSTEM_COUNT, UInt(1)), ap, iv′, us′, ps′, var_to_name, observed, name, description, systems, From c6e7fdbc41b9ca3d3052c2939f7d74a3762158e9 Mon Sep 17 00:00:00 2001 From: vyudu Date: Wed, 2 Apr 2025 00:53:29 -0400 Subject: [PATCH 077/122] fix: unwrap s in tovar --- src/parameters.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/parameters.jl b/src/parameters.jl index ca8bc76c2b..35e4206743 100644 --- a/src/parameters.jl +++ b/src/parameters.jl @@ -62,7 +62,7 @@ toparam(s::Num) = wrap(toparam(value(s))) Maps the variable to an unknown. """ -tovar(s::Union{Symbolic, Symbolics.Arr}) = setmetadata(s, MTKVariableTypeCtx, VARIABLE) +tovar(s::Union{Symbolic, Symbolics.Arr}) = setmetadata(unwrap(s), MTKVariableTypeCtx, VARIABLE) tovar(s::Num) = Num(tovar(value(s))) """ From 692c0893f10ad124af85bc3057a9d6ce7ffcddb3 Mon Sep 17 00:00:00 2001 From: vyudu Date: Wed, 2 Apr 2025 00:54:53 -0400 Subject: [PATCH 078/122] up --- src/parameters.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/parameters.jl b/src/parameters.jl index 35e4206743..8c0f9f1b00 100644 --- a/src/parameters.jl +++ b/src/parameters.jl @@ -62,8 +62,8 @@ toparam(s::Num) = wrap(toparam(value(s))) Maps the variable to an unknown. """ -tovar(s::Union{Symbolic, Symbolics.Arr}) = setmetadata(unwrap(s), MTKVariableTypeCtx, VARIABLE) -tovar(s::Num) = Num(tovar(value(s))) +tovar(s::Symbolic) = setmetadata(s, MTKVariableTypeCtx, VARIABLE) +tovar(s::Union{Num, Symbolics.Arr}) = Num(tovar(value(s))) """ $(SIGNATURES) From f5f5a88bf60bc8a47b7987d42b25062014e19e90 Mon Sep 17 00:00:00 2001 From: vyudu Date: Wed, 2 Apr 2025 02:39:17 -0400 Subject: [PATCH 079/122] fix: fix several tests --- src/systems/callbacks.jl | 41 +++++++++++++++++++++----------- src/systems/diffeqs/odesystem.jl | 5 ++-- src/systems/diffeqs/sdesystem.jl | 5 ++-- src/systems/jumps/jumpsystem.jl | 6 +++-- src/systems/model_parsing.jl | 22 ++--------------- test/extensions/ad.jl | 3 ++- test/jumpsystem.jl | 38 ++++++++++++++--------------- test/mtkparameters.jl | 3 ++- 8 files changed, 62 insertions(+), 61 deletions(-) diff --git a/src/systems/callbacks.jl b/src/systems/callbacks.jl index cfe31511c4..1e6264e96a 100644 --- a/src/systems/callbacks.jl +++ b/src/systems/callbacks.jl @@ -217,14 +217,12 @@ struct SymbolicContinuousCallback <: AbstractCallback function SymbolicContinuousCallback( conditions::Union{Equation, Vector{Equation}}, affect = nothing; - discrete_parameters = Any[], affect_neg = affect, initialize = nothing, finalize = nothing, rootfind = SciMLBase.LeftRootFind, reinitializealg = nothing, - iv = nothing, - alg_eqs = Equation[]) + kwargs...) conditions = (conditions isa AbstractVector) ? conditions : [conditions] if isnothing(reinitializealg) @@ -233,11 +231,12 @@ struct SymbolicContinuousCallback <: AbstractCallback reinitializealg = SciMLBase.CheckInit() : reinitializealg = SciMLBase.NoInit() end + @show kwargs - new(conditions, make_affect(affect; iv, alg_eqs, discrete_parameters), - make_affect(affect_neg; iv, alg_eqs, discrete_parameters), - make_affect(initialize; iv, alg_eqs, discrete_parameters), make_affect( - finalize; iv, alg_eqs, discrete_parameters), + new(conditions, make_affect(affect; kwargs...), + make_affect(affect_neg; kwargs...), + make_affect(initialize; kwargs...), make_affect( + finalize; kwargs...), rootfind, reinitializealg) end # Default affect to nothing end @@ -247,6 +246,13 @@ function SymbolicContinuousCallback(p::Pair, args...; kwargs...) end SymbolicContinuousCallback(cb::SymbolicContinuousCallback, args...; kwargs...) = cb SymbolicContinuousCallback(cb::Nothing, args...; kwargs...) = nothing +function SymbolicContinuousCallback(cb::Tuple, args...; kwargs...) + if length(cb) == 2 + SymbolicContinuousCallback(cb[1]; kwargs..., cb[2]...) + else + error("Malformed tuple specifying callback. Should be a condition => affect pair, followed by a vector of kwargs.") + end +end make_affect(affect::Nothing; kwargs...) = nothing make_affect(affect::Tuple; kwargs...) = FunctionalAffect(affect...) @@ -254,9 +260,9 @@ make_affect(affect::NamedTuple; kwargs...) = FunctionalAffect(; affect...) make_affect(affect::Affect; kwargs...) = affect function make_affect(affect::Vector{Equation}; discrete_parameters = Any[], - iv = nothing, alg_eqs::Vector{Equation} = Equation[]) + iv = nothing, alg_eqs::Vector{Equation} = Equation[], warn_no_algebraic = true, kwargs...) isempty(affect) && return nothing - isempty(alg_eqs) && + isempty(alg_eqs) && warn_no_algebraic && @warn "No algebraic equations were found for the callback defined by $(join(affect, ", ")). If the system has no algebraic equations, this can be disregarded. Otherwise pass in `alg_eqs` to the SymbolicContinuousCallback constructor." if isnothing(iv) iv = t_nounits @@ -423,7 +429,7 @@ struct SymbolicDiscreteCallback <: AbstractCallback function SymbolicDiscreteCallback( condition, affect = nothing; initialize = nothing, finalize = nothing, iv = nothing, - alg_eqs = Equation[], discrete_parameters = Any[], reinitializealg = nothing) + reinitializealg = nothing, kwargs...) c = is_timed_condition(condition) ? condition : value(scalarize(condition)) if isnothing(reinitializealg) @@ -432,9 +438,9 @@ struct SymbolicDiscreteCallback <: AbstractCallback reinitializealg = SciMLBase.CheckInit() : reinitializealg = SciMLBase.NoInit() end - new(c, make_affect(affect; iv, alg_eqs, discrete_parameters), - make_affect(initialize; iv, alg_eqs, discrete_parameters), - make_affect(finalize; iv, alg_eqs, discrete_parameters), reinitializealg) + new(c, make_affect(affect; kwargs...), + make_affect(initialize; kwargs...), + make_affect(finalize; kwargs...), reinitializealg) end # Default affect to nothing end @@ -443,6 +449,13 @@ function SymbolicDiscreteCallback(p::Pair, args...; kwargs...) end SymbolicDiscreteCallback(cb::SymbolicDiscreteCallback, args...; kwargs...) = cb SymbolicDiscreteCallback(cb::Nothing, args...; kwargs...) = nothing +function SymbolicDiscreteCallback(cb::Tuple, args...; kwargs...) + if length(cb) == 2 + SymbolicDiscreteCallback(cb[1]; cb[2]...) + else + error("Malformed tuple specifying callback. Should be a condition => affect pair, followed by a vector of kwargs.") + end +end function is_timed_condition(condition::T) where {T} if T === Num @@ -861,7 +874,7 @@ Compile an affect defined by a set of equations. Systems with algebraic equation function compile_equational_affect( aff::Union{AffectSystem, Vector{Equation}}, sys; reset_jumps = false, kwargs...) if aff isa AbstractVector - aff = make_affect(aff; iv = get_iv(sys)) + aff = make_affect(aff; iv = get_iv(sys), warn_no_algebraic = false) end affsys = system(aff) ps_to_update = discretes(aff) diff --git a/src/systems/diffeqs/odesystem.jl b/src/systems/diffeqs/odesystem.jl index bc32d9d9fb..9adf8c440e 100644 --- a/src/systems/diffeqs/odesystem.jl +++ b/src/systems/diffeqs/odesystem.jl @@ -339,8 +339,9 @@ function ODESystem(deqs::AbstractVector{<:Equation}, iv, dvs, ps; alg_eqs = filter(eq -> eq.lhs isa Union{Symbolic, Number} && !is_diff_equation(eq), deqs) cont_callbacks = to_cb_vector(SymbolicContinuousCallback.( - continuous_events; alg_eqs, iv)) - disc_callbacks = to_cb_vector(SymbolicDiscreteCallback.(discrete_events; alg_eqs, iv)) + continuous_events; alg_eqs = alg_eqs, iv = iv, warn_no_algebraic = false)) + disc_callbacks = to_cb_vector(SymbolicDiscreteCallback.( + discrete_events; alg_eqs = alg_eqs, iv = iv, warn_no_algebraic = false)) if is_dde === nothing is_dde = _check_if_dde(deqs, iv′, systems) diff --git a/src/systems/diffeqs/sdesystem.jl b/src/systems/diffeqs/sdesystem.jl index ee0329cbe1..131041a793 100644 --- a/src/systems/diffeqs/sdesystem.jl +++ b/src/systems/diffeqs/sdesystem.jl @@ -273,8 +273,9 @@ function SDESystem(deqs::AbstractVector{<:Equation}, neqs::AbstractArray, iv, dv alg_eqs = filter(eq -> eq.lhs isa Union{Symbolic, Number} && !is_diff_equation(eq), deqs) cont_callbacks = to_cb_vector(SymbolicContinuousCallback.( - continuous_events; alg_eqs, iv)) - disc_callbacks = to_cb_vector(SymbolicDiscreteCallback.(discrete_events; alg_eqs, iv)) + continuous_events; alg_eqs = alg_eqs, iv = iv, warn_no_algebraic = false)) + disc_callbacks = to_cb_vector(SymbolicDiscreteCallback.( + discrete_events; alg_eqs = alg_eqs, iv = iv, warn_no_algebraic = false)) if is_dde === nothing is_dde = _check_if_dde(deqs, iv′, systems) diff --git a/src/systems/jumps/jumpsystem.jl b/src/systems/jumps/jumpsystem.jl index 746f319e33..ac3c7bbcd6 100644 --- a/src/systems/jumps/jumpsystem.jl +++ b/src/systems/jumps/jumpsystem.jl @@ -212,8 +212,10 @@ function JumpSystem(eqs, iv, unknowns, ps; end end - disc_callbacks = to_cb_vector(SymbolicDiscreteCallback.(discrete_events; iv)) - cont_callbacks = to_cb_vector(SymbolicContinuousCallback.(continuous_events; iv)) + disc_callbacks = to_cb_vector(SymbolicDiscreteCallback.( + discrete_events; iv = iv, warn_no_algebraic = false)) + cont_callbacks = to_cb_vector(SymbolicContinuousCallback.( + continuous_events; iv = iv, warn_no_algebraic = false)) JumpSystem{typeof(ap)}(Threads.atomic_add!(SYSTEM_COUNT, UInt(1)), ap, iv′, us′, ps′, var_to_name, observed, name, description, systems, diff --git a/src/systems/model_parsing.jl b/src/systems/model_parsing.jl index 33ade7faf3..bc1c56943c 100644 --- a/src/systems/model_parsing.jl +++ b/src/systems/model_parsing.jl @@ -130,7 +130,8 @@ function _model_macro(mod, fullname::Union{Expr, Symbol}, expr, isconnector) sys = :($ODESystem($(flatten_equations)(equations), $iv, variables, parameters; name, description = $description, systems, gui_metadata = $gui_metadata, - defaults, continuous_events = cont_events, discrete_events = disc_events)) + continuous_events = [$(c_evts...)], discrete_events = [$(d_evts...)], + defaults)) sys = :($type($(flatten_equations)(equations), $iv, variables, parameters; name, description = $description, systems, gui_metadata = $gui_metadata, defaults, costs = [$(costs...)], constraints = [$(cons...)], consolidate = $consolidate)) @@ -144,25 +145,6 @@ function _model_macro(mod, fullname::Union{Expr, Symbol}, expr, isconnector) isconnector && push!(exprs.args, :($Setfield.@set!(var"#___sys___".connector_type=$connector_type(var"#___sys___")))) - if !isempty(d_evts) || !isempty(c_evts) - push!(exprs.args, :(alg_eqs = $(alg_equations)(var"#___sys___"))) - !isempty(d_evts) && begin - d_exprs = [:($(SymbolicDiscreteCallback)( - $(evt.args[1]); iv = $iv, alg_eqs, $(evt.args[2])...)) - for evt in d_evts] - push!(exprs.args, - :($Setfield.@set!(var"#___sys___".discrete_events=[$(d_exprs...)]))) - end - - !isempty(c_evts) && begin - c_exprs = [:($(SymbolicContinuousCallback)( - $(evt.args[1]); iv = $iv, alg_eqs, $(evt.args[2])...)) - for evt in c_evts] - push!(exprs.args, - :($Setfield.@set!(var"#___sys___".continuous_events=[$(c_exprs...)]))) - end - end - f = if length(where_types) == 0 :($(Symbol(:__, name, :__))(; name, $(kwargs...)) = $exprs) else diff --git a/test/extensions/ad.jl b/test/extensions/ad.jl index adaf6117c6..845d1ad818 100644 --- a/test/extensions/ad.jl +++ b/test/extensions/ad.jl @@ -59,7 +59,8 @@ end @parameters a b[1:3] c(t) d::Integer e[1:3] f[1:3, 1:3]::Int g::Vector{AbstractFloat} h::String @named sys = ODESystem( Equation[], t, [], [a, b, c, d, e, f, g, h], - continuous_events = [[a ~ 0] => [c ~ 0]]) + continuous_events = [ModelingToolkit.SymbolicContinuousCallback( + [a ~ 0] => [c ~ 0], discrete_parameters = c)]) sys = complete(sys) ivs = Dict(c => 3a, b => ones(3), a => 1.0, d => 4, e => [5.0, 6.0, 7.0], diff --git a/test/jumpsystem.jl b/test/jumpsystem.jl index a9b76b1e89..290851bda9 100644 --- a/test/jumpsystem.jl +++ b/test/jumpsystem.jl @@ -80,7 +80,7 @@ function getmean(jprob, Nsims; use_stepper = true) end m / Nsims end -@btime m = $getmean($jprob, $Nsims) +m = getmean(jprob, Nsims) # test auto-alg selection works jprobb = JumpProblem(js2, dprob; save_positions = (false, false), rng) @@ -248,7 +248,7 @@ end rate = k affect = [X ~ X - 1] -crj = ConstantRateJump(1.0, [X ~ X - 1]) +crj = ConstantRateJump(1.0, [X ~ Pre(X) - 1]) js1 = complete(JumpSystem([crj], t, [X], [k]; name = :js1)) js2 = complete(JumpSystem([crj], t, [X], []; name = :js2)) @@ -275,9 +275,9 @@ dp4 = DiscreteProblem(js4, u0, tspan) @parameters k @variables X(t) rate = k -affect = [X ~ X - 1] +affect = [X ~ Pre(X) - 1] -j1 = ConstantRateJump(k, [X ~ X - 1]) +j1 = ConstantRateJump(k, [X ~ Pre(X) - 1]) @test_nowarn @mtkbuild js1 = JumpSystem([j1], t, [X], [k]) # test correct autosolver is selected, which implies appropriate dep graphs are available @@ -285,8 +285,8 @@ let @parameters k @variables X(t) rate = k - affect = [X ~ X - 1] - j1 = ConstantRateJump(k, [X ~ X - 1]) + affect = [X ~ Pre(X) - 1] + j1 = ConstantRateJump(k, [X ~ Pre(X) - 1]) Nv = [1, JumpProcesses.USE_DIRECT_THRESHOLD + 1, JumpProcesses.USE_RSSA_THRESHOLD + 1] algtypes = [Direct, RSSA, RSSACR] @@ -305,7 +305,7 @@ let Random.seed!(rng, 1111) @variables A(t) B(t) C(t) @parameters k - vrj = VariableRateJump(k * (sin(t) + 1), [A ~ A + 1, C ~ C + 2]) + vrj = VariableRateJump(k * (sin(t) + 1), [A ~ Pre(A) + 1, C ~ Pre(C) + 2]) js = complete(JumpSystem([vrj], t, [A, C], [k]; name = :js, observed = [B ~ C * A])) oprob = ODEProblem(js, [A => 0, C => 0], (0.0, 10.0), [k => 1.0]) jprob = JumpProblem(js, oprob, Direct(); rng) @@ -346,9 +346,9 @@ end let @variables x1(t) x2(t) x3(t) x4(t) x5(t) @parameters p1 p2 p3 p4 p5 - j1 = ConstantRateJump(p1, [x1 ~ x1 + 1]) + j1 = ConstantRateJump(p1, [x1 ~ Pre(x1) + 1]) j2 = MassActionJump(p2, [x2 => 1], [x3 => -1]) - j3 = VariableRateJump(p3, [x3 ~ x3 + 1, x4 ~ x4 + 1]) + j3 = VariableRateJump(p3, [x3 ~ Pre(x3) + 1, x4 ~ Pre(x4) + 1]) j4 = MassActionJump(p4 * p5, [x1 => 1, x5 => 1], [x1 => -1, x5 => -1, x2 => 1]) us = Set() ps = Set() @@ -388,11 +388,11 @@ let p3 = ParentScope(ParentScope(p3)) p4 = GlobalScope(p4) - j1 = ConstantRateJump(p1, [x1 ~ x1 + 1]) + j1 = ConstantRateJump(p1, [x1 ~ Pre(x1) + 1]) j2 = MassActionJump(p2, [x2 => 1], [x3 => -1]) - j3 = VariableRateJump(p3, [x3 ~ x3 + 1, x4 ~ x4 + 1]) - j4 = MassActionJump(p4 * p4, [x1 => 1, x4 => 1], [x1 => -1, x4 => -1, x2 => 1]) - @named js = JumpSystem([j1, j2, j3, j4], t, [x1, x2, x3, x4], [p1, p2, p3, p4]) + j3 = VariableRateJump(p3, [x3 ~ Pre(x3) + 1, x4 ~ Pre(x4) + 1]) + j4 = MassActionJump(p4 * p5, [x1 => 1, x5 => 1], [x1 => -1, x5 => -1, x2 => 1]) + @named js = JumpSystem([j1, j2, j3, j4], t, [x1, x2, x3, x4, x5], [p1, p2, p3, p4, p5]) us = Set() ps = Set() @@ -428,8 +428,8 @@ let Random.seed!(rng, seed) @variables X(t) Y(t) @parameters k1 k2 - vrj1 = VariableRateJump(k1 * X, [X ~ X - 1]; save_positions = (false, false)) - vrj2 = VariableRateJump(k1, [Y ~ Y + 1]; save_positions = (false, false)) + vrj1 = VariableRateJump(k1 * X, [X ~ Pre(X) - 1]; save_positions = (false, false)) + vrj2 = VariableRateJump(k1, [Y ~ Pre(Y) + 1]; save_positions = (false, false)) eqs = [D(X) ~ k2, D(Y) ~ -k2 / 10 * Y] @named jsys = JumpSystem([vrj1, vrj2, eqs[1], eqs[2]], t, [X, Y], [k1, k2]) jsys = complete(jsys) @@ -470,8 +470,8 @@ let Random.seed!(rng, seed) @variables X(t) Y(t) @parameters α β - vrj = VariableRateJump(β * X, [X ~ X - 1]; save_positions = (false, false)) - crj = ConstantRateJump(β * Y, [Y ~ Y - 1]) + vrj = VariableRateJump(β * X, [X ~ Pre(X) - 1]; save_positions = (false, false)) + crj = ConstantRateJump(β * Y, [Y ~ Pre(Y) - 1]) maj = MassActionJump(α, [0 => 1], [Y => 1]) eqs = [D(X) ~ α * (1 + Y)] @named jsys = JumpSystem([maj, crj, vrj, eqs[1]], t, [X, Y], [α, β]) @@ -538,8 +538,8 @@ end @variables X(t) rate1 = p rate2 = X * d - affect1 = [X ~ X + 1] - affect2 = [X ~ X - 1] + affect1 = [X ~ Pre(X) + 1] + affect2 = [X ~ Pre(X) - 1] j1 = ConstantRateJump(rate1, affect1) j2 = ConstantRateJump(rate2, affect2) diff --git a/test/mtkparameters.jl b/test/mtkparameters.jl index 55de0768e0..5cc26ba79a 100644 --- a/test/mtkparameters.jl +++ b/test/mtkparameters.jl @@ -10,7 +10,8 @@ using JET @parameters a b c(t) d::Integer e[1:3] f[1:3, 1:3]::Int g::Vector{AbstractFloat} h::String @named sys = ODESystem( Equation[], t, [], [a, c, d, e, f, g, h], parameter_dependencies = [b ~ 2a], - continuous_events = [[a ~ 0] => [c ~ 0]], defaults = Dict(a => 0.0)) + continuous_events = [ModelingToolkit.SymbolicContinuousCallback( + [a ~ 0] => [c ~ 0], discrete_parameters = c)], defaults = Dict(a => 0.0)) sys = complete(sys) ivs = Dict(c => 3a, d => 4, e => [5.0, 6.0, 7.0], From d95aa83c941c64c416034c55f48d890d99790e5c Mon Sep 17 00:00:00 2001 From: vyudu Date: Wed, 2 Apr 2025 02:56:40 -0400 Subject: [PATCH 080/122] docs: fix doc discrete_events example --- docs/src/basics/Events.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/docs/src/basics/Events.md b/docs/src/basics/Events.md index 2725938952..0d19e474ba 100644 --- a/docs/src/basics/Events.md +++ b/docs/src/basics/Events.md @@ -409,8 +409,9 @@ example: @variables x(t) @parameters c(t) +ev = SymbolicDiscreteCallback(1.0 => [c ~ Pre(c) + 1], discrete_parameters = c) @mtkbuild sys = ODESystem( - D(x) ~ c * cos(x), t, [x], [c]; discrete_events = [1.0 => [c ~ Pre(c) + 1]]) + D(x) ~ c * cos(x), t, [x], [c]; discrete_events = [ev]) prob = ODEProblem(sys, [x => 0.0], (0.0, 2pi), [c => 1.0]) sol = solve(prob, Tsit5()) @@ -423,12 +424,12 @@ The solution object can also be interpolated with the discrete variables sol([1.0, 2.0], idxs = [c, c * cos(x)]) ``` -Note that only time-dependent parameters will be saved. If we repeat the above example with -this change: +Note that only time-dependent parameters that are explicitly passed as `discrete_parameters` +will be saved. If we repeat the above example with `c` not a `discrete_parameter`: ```@example events @variables x(t) -@parameters c +@parameters c(t) @mtkbuild sys = ODESystem( D(x) ~ c * cos(x), t, [x], [c]; discrete_events = [1.0 => [c ~ Pre(c) + 1]]) From 0b344ebc25b7bf8d8e8aa10030c57507fa2906b6 Mon Sep 17 00:00:00 2001 From: vyudu Date: Wed, 2 Apr 2025 03:26:40 -0400 Subject: [PATCH 081/122] docs: fix doc example --- docs/src/basics/Events.md | 2 +- src/systems/callbacks.jl | 7 +++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/docs/src/basics/Events.md b/docs/src/basics/Events.md index 0d19e474ba..4131ef76b6 100644 --- a/docs/src/basics/Events.md +++ b/docs/src/basics/Events.md @@ -409,7 +409,7 @@ example: @variables x(t) @parameters c(t) -ev = SymbolicDiscreteCallback(1.0 => [c ~ Pre(c) + 1], discrete_parameters = c) +ev = ModelingToolkit.SymbolicDiscreteCallback(1.0 => [c ~ Pre(c) + 1], discrete_parameters = c, iv = t) @mtkbuild sys = ODESystem( D(x) ~ c * cos(x), t, [x], [c]; discrete_events = [ev]) diff --git a/src/systems/callbacks.jl b/src/systems/callbacks.jl index 1e6264e96a..bd2901188b 100644 --- a/src/systems/callbacks.jl +++ b/src/systems/callbacks.jl @@ -231,7 +231,6 @@ struct SymbolicContinuousCallback <: AbstractCallback reinitializealg = SciMLBase.CheckInit() : reinitializealg = SciMLBase.NoInit() end - @show kwargs new(conditions, make_affect(affect; kwargs...), make_affect(affect_neg; kwargs...), @@ -420,15 +419,15 @@ Arguments: - alg_eqs: Algebraic equations of the system that must be satisfied after the callback occurs. """ struct SymbolicDiscreteCallback <: AbstractCallback - conditions::Any + conditions::Union{Number, Vector{<:Number}} affect::Union{Affect, Nothing} initialize::Union{Affect, Nothing} finalize::Union{Affect, Nothing} reinitializealg::SciMLBase.DAEInitializationAlgorithm function SymbolicDiscreteCallback( - condition, affect = nothing; - initialize = nothing, finalize = nothing, iv = nothing, + condition::Union{Number, Vector{<:Number}}, affect = nothing; + initialize = nothing, finalize = nothing, reinitializealg = nothing, kwargs...) c = is_timed_condition(condition) ? condition : value(scalarize(condition)) From 4e8c75271e1c292939aed5abbeaa5edc7c98e68d Mon Sep 17 00:00:00 2001 From: vyudu Date: Wed, 2 Apr 2025 10:15:20 -0400 Subject: [PATCH 082/122] docs: fix more doc examples --- docs/src/basics/Events.md | 14 +++++++++----- docs/src/tutorials/fmi.md | 8 +++++--- ext/MTKFMIExt.jl | 2 +- 3 files changed, 15 insertions(+), 9 deletions(-) diff --git a/docs/src/basics/Events.md b/docs/src/basics/Events.md index 4131ef76b6..35e7f84356 100644 --- a/docs/src/basics/Events.md +++ b/docs/src/basics/Events.md @@ -330,7 +330,8 @@ event time, the event condition now returns false. Here we used logical and, cannot be used within symbolic expressions. Let's now also add a drug at time `tkill` that turns off production of new -cells, modeled by setting `α = 0.0` +cells, modeled by setting `α = 0.0`. Since this is a parameter we must explicitly +set it as `discrete_parameters`. ```@example events @parameters tkill @@ -339,7 +340,8 @@ cells, modeled by setting `α = 0.0` injection = (t == tinject) => [N ~ Pre(N) + M] # at time tkill we turn off production of cells -killing = (t == tkill) => [α ~ 0.0] +killing = ModelingToolkit.SymbolicDiscreteCallback( + (t == tkill) => [α ~ 0.0]; discrete_parameters = α, iv = t) tspan = (0.0, 30.0) p = [α => 100.0, tinject => 10.0, M => 50, tkill => 20.0] @@ -368,7 +370,8 @@ As such, our last example with treatment and killing could instead be modeled by ```@example events injection = [10.0] => [N ~ Pre(N) + M] -killing = [20.0] => [α ~ 0.0] +killing = ModelingToolkit.SymbolicDiscreteCallback( + [20.0] => [α ~ 0.0], discrete_parameters = α, iv = t) p = [α => 100.0, M => 50] @mtkbuild osys = ODESystem(eqs, t, [N], [α, M]; @@ -409,7 +412,8 @@ example: @variables x(t) @parameters c(t) -ev = ModelingToolkit.SymbolicDiscreteCallback(1.0 => [c ~ Pre(c) + 1], discrete_parameters = c, iv = t) +ev = ModelingToolkit.SymbolicDiscreteCallback( + 1.0 => [c ~ Pre(c) + 1], discrete_parameters = c, iv = t) @mtkbuild sys = ODESystem( D(x) ~ c * cos(x), t, [x], [c]; discrete_events = [ev]) @@ -424,7 +428,7 @@ The solution object can also be interpolated with the discrete variables sol([1.0, 2.0], idxs = [c, c * cos(x)]) ``` -Note that only time-dependent parameters that are explicitly passed as `discrete_parameters` +Note that only time-dependent parameters that are explicitly passed as `discrete_parameters` will be saved. If we repeat the above example with `c` not a `discrete_parameter`: ```@example events diff --git a/docs/src/tutorials/fmi.md b/docs/src/tutorials/fmi.md index ef00477c78..ed4fb3e2a8 100644 --- a/docs/src/tutorials/fmi.md +++ b/docs/src/tutorials/fmi.md @@ -94,7 +94,8 @@ we will create a model from a CoSimulation FMU. ```@example fmi fmu = loadFMU("SpringPendulum1D", "Dymola", "2023x", "3.0"; type = :CS) @named inner = ModelingToolkit.FMIComponent( - Val(3); fmu, communication_step_size = 0.001, type = :CS) + Val(3); fmu, communication_step_size = 0.001, type = :CS, + reinitializealg = BrownFullBasicInit()) ``` This FMU has fewer equations, partly due to missing aliasing variables and partly due to being a CS FMU. @@ -170,7 +171,8 @@ end `a` and `b` are inputs, `c` is a state, and `out` and `out2` are outputs of the component. ```@repl fmi -@named adder = ModelingToolkit.FMIComponent(Val(2); fmu, type = :ME); +@named adder = ModelingToolkit.FMIComponent( + Val(2); fmu, type = :ME, reinitializealg = SciMLBase.BrownFullBasicInit()); isinput(adder.a) isinput(adder.b) isoutput(adder.out) @@ -214,7 +216,7 @@ fmu = loadFMU( type = :CS) @named adder = ModelingToolkit.FMIComponent( Val(2); fmu, type = :CS, communication_step_size = 1e-3, - reinitializealg = BrownFullBasicInit()) + reinitializealg = SciMLBase.BrownFullBasicInit()) @mtkbuild sys = ODESystem( [adder.a ~ a, adder.b ~ b, D(a) ~ t, D(b) ~ adder.out + adder.c, c^2 ~ adder.out + adder.value], diff --git a/ext/MTKFMIExt.jl b/ext/MTKFMIExt.jl index 0baf37c34b..32023d0749 100644 --- a/ext/MTKFMIExt.jl +++ b/ext/MTKFMIExt.jl @@ -93,7 +93,7 @@ with the name `namespace__variable`. - `name`: The name of the system. """ function MTK.FMIComponent(::Val{Ver}; fmu = nothing, tolerance = 1e-6, - communication_step_size = nothing, type, name, reinitializealg = nothing) where {Ver} + communication_step_size = nothing, reinitializealg = nothing, type, name) where {Ver} if Ver != 2 && Ver != 3 throw(ArgumentError("FMI Version must be `2` or `3`")) end From 710fb4a7a094aa663045778b3c27c910f6ce7ab5 Mon Sep 17 00:00:00 2001 From: vyudu Date: Wed, 2 Apr 2025 15:27:53 -0400 Subject: [PATCH 083/122] allow symbolic in Discrete condition --- src/systems/callbacks.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/systems/callbacks.jl b/src/systems/callbacks.jl index bd2901188b..6a919615c9 100644 --- a/src/systems/callbacks.jl +++ b/src/systems/callbacks.jl @@ -412,14 +412,14 @@ A callback that triggers at the first timestep that the conditions are satisfied The condition can be one of: - Δt::Real - periodic events with period Δt - ts::Vector{Real} - events trigger at these preset times given by `ts` -- eqs::Vector{Equation} - events trigger when the condition evaluates to true +- eqs::Vector{Symbolic} - events trigger when the condition evaluates to true Arguments: - iv: The independent variable of the system. This must be specified if the independent variable appaers in one of the equations explicitly, as in x ~ t + 1. - alg_eqs: Algebraic equations of the system that must be satisfied after the callback occurs. """ struct SymbolicDiscreteCallback <: AbstractCallback - conditions::Union{Number, Vector{<:Number}} + conditions::Union{Number, Vector{<:Number}, Symbolic} affect::Union{Affect, Nothing} initialize::Union{Affect, Nothing} finalize::Union{Affect, Nothing} From 19e43681e5492211f5bc35ef49a41d570e73d7e8 Mon Sep 17 00:00:00 2001 From: vyudu Date: Wed, 2 Apr 2025 15:31:59 -0400 Subject: [PATCH 084/122] require Bool --- src/systems/callbacks.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/systems/callbacks.jl b/src/systems/callbacks.jl index 6a919615c9..166122b64c 100644 --- a/src/systems/callbacks.jl +++ b/src/systems/callbacks.jl @@ -419,7 +419,7 @@ Arguments: - alg_eqs: Algebraic equations of the system that must be satisfied after the callback occurs. """ struct SymbolicDiscreteCallback <: AbstractCallback - conditions::Union{Number, Vector{<:Number}, Symbolic} + conditions::Union{Number, Vector{<:Number}, Symbolic{Bool}} affect::Union{Affect, Nothing} initialize::Union{Affect, Nothing} finalize::Union{Affect, Nothing} From b34c766a28cbf7294ece40075f54c0103a0a5d51 Mon Sep 17 00:00:00 2001 From: vyudu Date: Thu, 3 Apr 2025 10:19:23 -0400 Subject: [PATCH 085/122] more docs fixes --- docs/src/basics/Events.md | 2 +- docs/src/tutorials/fmi.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/src/basics/Events.md b/docs/src/basics/Events.md index 35e7f84356..f15d3d895d 100644 --- a/docs/src/basics/Events.md +++ b/docs/src/basics/Events.md @@ -288,7 +288,7 @@ Suppose we have a population of `N(t)` cells that can grow and die, and at time `t1` we want to inject `M` more cells into the population. We can model this by ```@example events -@parameters M tinject α +@parameters M tinject α(t) @variables N(t) Dₜ = Differential(t) eqs = [Dₜ(N) ~ α - N] diff --git a/docs/src/tutorials/fmi.md b/docs/src/tutorials/fmi.md index ed4fb3e2a8..0e01393652 100644 --- a/docs/src/tutorials/fmi.md +++ b/docs/src/tutorials/fmi.md @@ -172,7 +172,7 @@ end ```@repl fmi @named adder = ModelingToolkit.FMIComponent( - Val(2); fmu, type = :ME, reinitializealg = SciMLBase.BrownFullBasicInit()); + Val(2); fmu, type = :ME, reinitializealg = BrownFullBasicInit()); isinput(adder.a) isinput(adder.b) isoutput(adder.out) @@ -216,7 +216,7 @@ fmu = loadFMU( type = :CS) @named adder = ModelingToolkit.FMIComponent( Val(2); fmu, type = :CS, communication_step_size = 1e-3, - reinitializealg = SciMLBase.BrownFullBasicInit()) + reinitializealg = BrownFullBasicInit()) @mtkbuild sys = ODESystem( [adder.a ~ a, adder.b ~ b, D(a) ~ t, D(b) ~ adder.out + adder.c, c^2 ~ adder.out + adder.value], From f682cac6c48b91405bbb6ef7e8d70cb679f93037 Mon Sep 17 00:00:00 2001 From: vyudu Date: Mon, 21 Apr 2025 13:58:49 -0400 Subject: [PATCH 086/122] update NewsMD --- NEWS.md | 19 +++++++++++++++++++ docs/src/basics/Events.md | 2 +- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/NEWS.md b/NEWS.md index 038b1d79f6..65fc6778e4 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,3 +1,22 @@ +# ModelingToolkit v10 Release Notes + +### Callbacks + +Callback semantics have changed. + - There is a new `Pre` operator that is used to specify which values are before the callback. + For example, the affect `A ~ A + 1` should now be written as `A ~ Pre(A) + 1`. This is + **required** to be specified - `A ~ A + 1` will now be interpreted as an equation to be + satisfied after the callback (and will thus error since it is unsatisfiable). + + - All parameters that are changed by a callback must be declared as discrete parameters to + the callback constructor, using the `discrete_parameters` keyword argument. + +```julia +event = SymbolicDiscreteCallback( + [t == 1] => [p ~ Pre(p) + 1], discrete_parameters = [p]) +``` + + # ModelingToolkit v9 Release Notes ### Upgrade guide diff --git a/docs/src/basics/Events.md b/docs/src/basics/Events.md index f15d3d895d..2808204d61 100644 --- a/docs/src/basics/Events.md +++ b/docs/src/basics/Events.md @@ -25,7 +25,7 @@ the event occurs). These can both be specified symbolically, but a more [general functional affect](@ref func_affects) representation is also allowed, as described below. -## Symbolic Callback Semantics (changed in V10) +## Symbolic Callback Semantics In callbacks, there is a distinction between values of the unknowns and parameters *before* the callback, and the desired values *after* the callback. In MTK, this From 79278a2f55f8b05dfebf23495f6d63d598688651 Mon Sep 17 00:00:00 2001 From: vyudu Date: Tue, 22 Apr 2025 18:52:15 -0400 Subject: [PATCH 087/122] fix: fix sfmi bugs and --- src/systems/callbacks.jl | 24 +++++++++++++++++------- src/systems/diffeqs/odesystem.jl | 6 ++---- src/systems/diffeqs/sdesystem.jl | 6 ++---- src/systems/jumps/jumpsystem.jl | 6 ++---- 4 files changed, 23 insertions(+), 19 deletions(-) diff --git a/src/systems/callbacks.jl b/src/systems/callbacks.jl index 166122b64c..6805a3f1a9 100644 --- a/src/systems/callbacks.jl +++ b/src/systems/callbacks.jl @@ -426,7 +426,7 @@ struct SymbolicDiscreteCallback <: AbstractCallback reinitializealg::SciMLBase.DAEInitializationAlgorithm function SymbolicDiscreteCallback( - condition::Union{Number, Vector{<:Number}}, affect = nothing; + condition::Union{Symbolic{Bool}, Number, Vector{<:Number}}, affect = nothing; initialize = nothing, finalize = nothing, reinitializealg = nothing, kwargs...) c = is_timed_condition(condition) ? condition : value(scalarize(condition)) @@ -468,10 +468,17 @@ function is_timed_condition(condition::T) where {T} end end -to_cb_vector(cbs::Vector{<:AbstractCallback}) = cbs -to_cb_vector(cbs::Vector) = Vector{AbstractCallback}(cbs) -to_cb_vector(cbs::Nothing) = AbstractCallback[] -to_cb_vector(cb::AbstractCallback) = [cb] +to_cb_vector(cbs::Vector{<:AbstractCallback}; kwargs...) = cbs +to_cb_vector(cbs::Union{Nothing, Vector{Nothing}}; kwargs...) = AbstractCallback[] +to_cb_vector(cb::AbstractCallback; kwargs...) = [cb] +function to_cb_vector(cbs; CB_TYPE = SymbolicContinuousCallback, kwargs...) + if cbs isa Pair + @show cbs + [CB_TYPE(cbs; kwargs...)] + else + Vector{CB_TYPE}([CB_TYPE(cb; kwargs...) for cb in cbs]) + end +end ############################################ ########## Namespacing Utilities ########### @@ -906,10 +913,10 @@ function compile_equational_affect( u_up, u_up! = build_function_wrapper(sys, (@view rhss[is_u]), dvs, _ps..., t; wrap_code = add_integrator_header(sys, integ, :u), - expression = Val{false}, outputidxs = u_idxs, wrap_mtkparameters) + expression = Val{false}, outputidxs = u_idxs, wrap_mtkparameters, cse = false) p_up, p_up! = build_function_wrapper(sys, (@view rhss[is_p]), dvs, _ps..., t; wrap_code = add_integrator_header(sys, integ, :p), - expression = Val{false}, outputidxs = p_idxs, wrap_mtkparameters) + expression = Val{false}, outputidxs = p_idxs, wrap_mtkparameters, cse = false) return function explicit_affect!(integ) isempty(dvs_to_update) || u_up!(integ) @@ -934,7 +941,10 @@ function compile_equational_affect( end affprob = ImplicitDiscreteProblem(affsys, u0, (integ.t, integ.t), pmap; build_initializeprob = false, check_length = false) + @show pmap + @show u0 affsol = init(affprob, IDSolve()) + @show affsol (check_error(affsol) === ReturnCode.InitialFailure) && throw(UnsolvableCallbackError(all_equations(aff))) for u in dvs_to_update diff --git a/src/systems/diffeqs/odesystem.jl b/src/systems/diffeqs/odesystem.jl index 9adf8c440e..c7518672f4 100644 --- a/src/systems/diffeqs/odesystem.jl +++ b/src/systems/diffeqs/odesystem.jl @@ -338,10 +338,8 @@ function ODESystem(deqs::AbstractVector{<:Equation}, iv, dvs, ps; alg_eqs = filter(eq -> eq.lhs isa Union{Symbolic, Number} && !is_diff_equation(eq), deqs) - cont_callbacks = to_cb_vector(SymbolicContinuousCallback.( - continuous_events; alg_eqs = alg_eqs, iv = iv, warn_no_algebraic = false)) - disc_callbacks = to_cb_vector(SymbolicDiscreteCallback.( - discrete_events; alg_eqs = alg_eqs, iv = iv, warn_no_algebraic = false)) + cont_callbacks = to_cb_vector(continuous_events; CB_TYPE = SymbolicContinuousCallback, iv = iv, alg_eqs = alg_eqs, warn_no_algebraic = false) + disc_callbacks = to_cb_vector(discrete_events; CB_TYPE = SymbolicDiscreteCallback, iv = iv, alg_eqs = alg_eqs, warn_no_algebraic = false) if is_dde === nothing is_dde = _check_if_dde(deqs, iv′, systems) diff --git a/src/systems/diffeqs/sdesystem.jl b/src/systems/diffeqs/sdesystem.jl index 131041a793..12df27e68f 100644 --- a/src/systems/diffeqs/sdesystem.jl +++ b/src/systems/diffeqs/sdesystem.jl @@ -272,10 +272,8 @@ function SDESystem(deqs::AbstractVector{<:Equation}, neqs::AbstractArray, iv, dv alg_eqs = filter(eq -> eq.lhs isa Union{Symbolic, Number} && !is_diff_equation(eq), deqs) - cont_callbacks = to_cb_vector(SymbolicContinuousCallback.( - continuous_events; alg_eqs = alg_eqs, iv = iv, warn_no_algebraic = false)) - disc_callbacks = to_cb_vector(SymbolicDiscreteCallback.( - discrete_events; alg_eqs = alg_eqs, iv = iv, warn_no_algebraic = false)) + cont_callbacks = to_cb_vector(continuous_events; CB_TYPE = SymbolicContinuousCallback, iv = iv, alg_eqs = alg_eqs, warn_no_algebraic = false) + disc_callbacks = to_cb_vector(discrete_events; CB_TYPE = SymbolicDiscreteCallback, iv = iv, alg_eqs = alg_eqs, warn_no_algebraic = false) if is_dde === nothing is_dde = _check_if_dde(deqs, iv′, systems) diff --git a/src/systems/jumps/jumpsystem.jl b/src/systems/jumps/jumpsystem.jl index ac3c7bbcd6..45839f6a80 100644 --- a/src/systems/jumps/jumpsystem.jl +++ b/src/systems/jumps/jumpsystem.jl @@ -212,10 +212,8 @@ function JumpSystem(eqs, iv, unknowns, ps; end end - disc_callbacks = to_cb_vector(SymbolicDiscreteCallback.( - discrete_events; iv = iv, warn_no_algebraic = false)) - cont_callbacks = to_cb_vector(SymbolicContinuousCallback.( - continuous_events; iv = iv, warn_no_algebraic = false)) + cont_callbacks = to_cb_vector(continuous_events; CB_TYPE = SymbolicContinuousCallback, iv = iv, warn_no_algebraic = false) + disc_callbacks = to_cb_vector(discrete_events; CB_TYPE = SymbolicDiscreteCallback, iv = iv, warn_no_algebraic = false) JumpSystem{typeof(ap)}(Threads.atomic_add!(SYSTEM_COUNT, UInt(1)), ap, iv′, us′, ps′, var_to_name, observed, name, description, systems, From 04b86db14efe5842c8bca2da75fbf031b239b319 Mon Sep 17 00:00:00 2001 From: vyudu Date: Tue, 22 Apr 2025 20:40:19 -0400 Subject: [PATCH 088/122] fix remaining tests --- Project.toml | 1 + src/systems/model_parsing.jl | 2 +- test/fmi/fmi.jl | 4 ++-- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Project.toml b/Project.toml index 58df778436..1afe6ab76d 100644 --- a/Project.toml +++ b/Project.toml @@ -115,6 +115,7 @@ ForwardDiff = "0.10.3" FunctionWrappers = "1.1" FunctionWrappersWrappers = "0.1" Graphs = "1.5.2" +ImplicitDiscreteSolve = "0.1.2" InfiniteOpt = "0.5" InteractiveUtils = "1" JuliaFormatter = "1.0.47, 2" diff --git a/src/systems/model_parsing.jl b/src/systems/model_parsing.jl index bc1c56943c..15697e6d7e 100644 --- a/src/systems/model_parsing.jl +++ b/src/systems/model_parsing.jl @@ -128,7 +128,7 @@ function _model_macro(mod, fullname::Union{Expr, Symbol}, expr, isconnector) @inline pop_structure_dict!.( Ref(dict), [:constants, :defaults, :kwargs, :structural_parameters]) - sys = :($ODESystem($(flatten_equations)(equations), $iv, variables, parameters; + sys = :($type($(flatten_equations)(equations), $iv, variables, parameters; name, description = $description, systems, gui_metadata = $gui_metadata, continuous_events = [$(c_evts...)], discrete_events = [$(d_evts...)], defaults)) diff --git a/test/fmi/fmi.jl b/test/fmi/fmi.jl index 98c93398ff..edbbb312d6 100644 --- a/test/fmi/fmi.jl +++ b/test/fmi/fmi.jl @@ -158,7 +158,7 @@ end fmu = loadFMU(joinpath(FMU_DIR, "SimpleAdder.fmu"); type = :CS) @named adder = MTK.FMIComponent( Val(2); fmu, type = :CS, communication_step_size = 1e-6, - reinitializealg = BrownFullBasicInit()) + reinitializealg = BrownFullBasicInit(abstol = 1e-7)) @test MTK.isinput(adder.a) @test MTK.isinput(adder.b) @test MTK.isoutput(adder.out) @@ -211,7 +211,7 @@ end fmu = loadFMU(joinpath(FMU_DIR, "StateSpace.fmu"); type = :CS) @named sspace = MTK.FMIComponent( Val(3); fmu, communication_step_size = 1e-6, type = :CS, - reinitializealg = BrownFullBasicInit()) + reinitializealg = BrownFullBasicInit(abstol = 1e-7)) @test MTK.isinput(sspace.u) @test MTK.isoutput(sspace.y) @test !MTK.isinput(sspace.x) && !MTK.isoutput(sspace.x) From 589e20e9a57cb426335a84ded56a95ddd5af50b3 Mon Sep 17 00:00:00 2001 From: vyudu Date: Tue, 22 Apr 2025 20:51:38 -0400 Subject: [PATCH 089/122] format --- NEWS.md | 6 +++--- src/systems/diffeqs/odesystem.jl | 6 ++++-- src/systems/diffeqs/sdesystem.jl | 6 ++++-- src/systems/jumps/jumpsystem.jl | 6 ++++-- 4 files changed, 15 insertions(+), 9 deletions(-) diff --git a/NEWS.md b/NEWS.md index 65fc6778e4..d316ac23fb 100644 --- a/NEWS.md +++ b/NEWS.md @@ -3,8 +3,9 @@ ### Callbacks Callback semantics have changed. - - There is a new `Pre` operator that is used to specify which values are before the callback. - For example, the affect `A ~ A + 1` should now be written as `A ~ Pre(A) + 1`. This is + + - There is a new `Pre` operator that is used to specify which values are before the callback. + For example, the affect `A ~ A + 1` should now be written as `A ~ Pre(A) + 1`. This is **required** to be specified - `A ~ A + 1` will now be interpreted as an equation to be satisfied after the callback (and will thus error since it is unsatisfiable). @@ -16,7 +17,6 @@ event = SymbolicDiscreteCallback( [t == 1] => [p ~ Pre(p) + 1], discrete_parameters = [p]) ``` - # ModelingToolkit v9 Release Notes ### Upgrade guide diff --git a/src/systems/diffeqs/odesystem.jl b/src/systems/diffeqs/odesystem.jl index c7518672f4..8283a10201 100644 --- a/src/systems/diffeqs/odesystem.jl +++ b/src/systems/diffeqs/odesystem.jl @@ -338,8 +338,10 @@ function ODESystem(deqs::AbstractVector{<:Equation}, iv, dvs, ps; alg_eqs = filter(eq -> eq.lhs isa Union{Symbolic, Number} && !is_diff_equation(eq), deqs) - cont_callbacks = to_cb_vector(continuous_events; CB_TYPE = SymbolicContinuousCallback, iv = iv, alg_eqs = alg_eqs, warn_no_algebraic = false) - disc_callbacks = to_cb_vector(discrete_events; CB_TYPE = SymbolicDiscreteCallback, iv = iv, alg_eqs = alg_eqs, warn_no_algebraic = false) + cont_callbacks = to_cb_vector(continuous_events; CB_TYPE = SymbolicContinuousCallback, + iv = iv, alg_eqs = alg_eqs, warn_no_algebraic = false) + disc_callbacks = to_cb_vector(discrete_events; CB_TYPE = SymbolicDiscreteCallback, + iv = iv, alg_eqs = alg_eqs, warn_no_algebraic = false) if is_dde === nothing is_dde = _check_if_dde(deqs, iv′, systems) diff --git a/src/systems/diffeqs/sdesystem.jl b/src/systems/diffeqs/sdesystem.jl index 12df27e68f..7baf94e1a3 100644 --- a/src/systems/diffeqs/sdesystem.jl +++ b/src/systems/diffeqs/sdesystem.jl @@ -272,8 +272,10 @@ function SDESystem(deqs::AbstractVector{<:Equation}, neqs::AbstractArray, iv, dv alg_eqs = filter(eq -> eq.lhs isa Union{Symbolic, Number} && !is_diff_equation(eq), deqs) - cont_callbacks = to_cb_vector(continuous_events; CB_TYPE = SymbolicContinuousCallback, iv = iv, alg_eqs = alg_eqs, warn_no_algebraic = false) - disc_callbacks = to_cb_vector(discrete_events; CB_TYPE = SymbolicDiscreteCallback, iv = iv, alg_eqs = alg_eqs, warn_no_algebraic = false) + cont_callbacks = to_cb_vector(continuous_events; CB_TYPE = SymbolicContinuousCallback, + iv = iv, alg_eqs = alg_eqs, warn_no_algebraic = false) + disc_callbacks = to_cb_vector(discrete_events; CB_TYPE = SymbolicDiscreteCallback, + iv = iv, alg_eqs = alg_eqs, warn_no_algebraic = false) if is_dde === nothing is_dde = _check_if_dde(deqs, iv′, systems) diff --git a/src/systems/jumps/jumpsystem.jl b/src/systems/jumps/jumpsystem.jl index 45839f6a80..313aada900 100644 --- a/src/systems/jumps/jumpsystem.jl +++ b/src/systems/jumps/jumpsystem.jl @@ -212,8 +212,10 @@ function JumpSystem(eqs, iv, unknowns, ps; end end - cont_callbacks = to_cb_vector(continuous_events; CB_TYPE = SymbolicContinuousCallback, iv = iv, warn_no_algebraic = false) - disc_callbacks = to_cb_vector(discrete_events; CB_TYPE = SymbolicDiscreteCallback, iv = iv, warn_no_algebraic = false) + cont_callbacks = to_cb_vector(continuous_events; CB_TYPE = SymbolicContinuousCallback, + iv = iv, warn_no_algebraic = false) + disc_callbacks = to_cb_vector(discrete_events; CB_TYPE = SymbolicDiscreteCallback, + iv = iv, warn_no_algebraic = false) JumpSystem{typeof(ap)}(Threads.atomic_add!(SYSTEM_COUNT, UInt(1)), ap, iv′, us′, ps′, var_to_name, observed, name, description, systems, From 29cdece75439689782944aa65c0f34281e737212 Mon Sep 17 00:00:00 2001 From: vyudu Date: Tue, 29 Apr 2025 16:38:43 -0400 Subject: [PATCH 090/122] fix: improve performance of implicit_affect --- src/systems/callbacks.jl | 91 ++++++++++--------- .../implicit_discrete_system.jl | 2 +- src/systems/jumps/jumpsystem.jl | 4 + src/systems/problem_utils.jl | 4 +- src/systems/systems.jl | 4 - test/symbolic_events.jl | 24 ++--- 6 files changed, 69 insertions(+), 60 deletions(-) diff --git a/src/systems/callbacks.jl b/src/systems/callbacks.jl index 6805a3f1a9..0bc4fabc47 100644 --- a/src/systems/callbacks.jl +++ b/src/systems/callbacks.jl @@ -56,9 +56,13 @@ function has_functional_affect(cb) end struct AffectSystem + """The internal implicit discrete system whose equations are solved to obtain values after the affect.""" system::ImplicitDiscreteSystem + """Unknowns of the parent ODESystem whose values are modified or accessed by the affect.""" unknowns::Vector + """Parameters of the parent ODESystem whose values are accessed by the affect.""" parameters::Vector + """Parameters of the parent ODESystem whose values are modified by the affect.""" discretes::Vector """Maps the symbols of unknowns/observed in the ImplicitDiscreteSystem to its corresponding unknown/parameter in the parent system.""" aff_to_sys::Dict @@ -226,10 +230,12 @@ struct SymbolicContinuousCallback <: AbstractCallback conditions = (conditions isa AbstractVector) ? conditions : [conditions] if isnothing(reinitializealg) - any(a -> (a isa FunctionalAffect || a isa ImperativeAffect), - [affect, affect_neg, initialize, finalize]) ? - reinitializealg = SciMLBase.CheckInit() : - reinitializealg = SciMLBase.NoInit() + if any(a -> (a isa FunctionalAffect || a isa ImperativeAffect), + [affect, affect_neg, initialize, finalize]) + reinitializealg = SciMLBase.CheckInit() + else + reinitializealg = SciMLBase.NoInit() + end end new(conditions, make_affect(affect; kwargs...), @@ -261,8 +267,6 @@ make_affect(affect::Affect; kwargs...) = affect function make_affect(affect::Vector{Equation}; discrete_parameters = Any[], iv = nothing, alg_eqs::Vector{Equation} = Equation[], warn_no_algebraic = true, kwargs...) isempty(affect) && return nothing - isempty(alg_eqs) && warn_no_algebraic && - @warn "No algebraic equations were found for the callback defined by $(join(affect, ", ")). If the system has no algebraic equations, this can be disregarded. Otherwise pass in `alg_eqs` to the SymbolicContinuousCallback constructor." if isnothing(iv) iv = t_nounits @warn "No independent variable specified. Defaulting to t_nounits." @@ -304,7 +308,7 @@ function make_affect(affect::Vector{Equation}; discrete_parameters = Any[], @named affectsys = ImplicitDiscreteSystem( vcat(affect, alg_eqs), iv, collect(union(_dvs, discretes)), collect(union(pre_params, sys_params))) - affectsys = structural_simplify(affectsys; fully_determined = false) + affectsys = structural_simplify(affectsys; fully_determined = nothing) # get accessed parameters p from Pre(p) in the callback parameters accessed_params = filter(isparameter, map(unPre, collect(pre_params))) union!(accessed_params, sys_params) @@ -415,7 +419,7 @@ The condition can be one of: - eqs::Vector{Symbolic} - events trigger when the condition evaluates to true Arguments: -- iv: The independent variable of the system. This must be specified if the independent variable appaers in one of the equations explicitly, as in x ~ t + 1. +- iv: The independent variable of the system. This must be specified if the independent variable appears in one of the equations explicitly, as in x ~ t + 1. - alg_eqs: Algebraic equations of the system that must be satisfied after the callback occurs. """ struct SymbolicDiscreteCallback <: AbstractCallback @@ -473,7 +477,6 @@ to_cb_vector(cbs::Union{Nothing, Vector{Nothing}}; kwargs...) = AbstractCallback to_cb_vector(cb::AbstractCallback; kwargs...) = [cb] function to_cb_vector(cbs; CB_TYPE = SymbolicContinuousCallback, kwargs...) if cbs isa Pair - @show cbs [CB_TYPE(cbs; kwargs...)] else Vector{CB_TYPE}([CB_TYPE(cb; kwargs...) for cb in cbs]) @@ -741,13 +744,17 @@ function generate_callback(cbs::Vector{SymbolicContinuousCallback}, sys; kwargs. [fill(i, num_eqs[i]) for i in eachindex(affects)]) eqs = reduce(vcat, eqs) - affect = function (integ, idx) - affects[eq2affect[idx]](integ) + affect = let eq2affect = eq2affect, affects = affects + function (integ, idx) + affects[eq2affect[idx]](integ) + end end - affect_neg = function (integ, idx) - f = affect_negs[eq2affect[idx]] - isnothing(f) && return - f(integ) + affect_neg = let eq2affect = eq2affect, affect_negs = affect_negs + function (integ, idx) + f = affect_negs[eq2affect[idx]] + isnothing(f) && return + f(integ) + end end initialize = wrap_vector_optional_affect(inits, SciMLBase.INITIALIZE_DEFAULT) finalize = wrap_vector_optional_affect(finals, SciMLBase.FINALIZE_DEFAULT) @@ -832,10 +839,10 @@ function compile_affect( end function wrap_save_discretes(f, save_idxs) - let save_idxs = save_idxs + let save_idxs = save_idxs, f = f if f === SciMLBase.INITIALIZE_DEFAULT (c, u, t, i) -> begin - isnothing(f) || f(c, u, t, i) + f(c, u, t, i) for idx in save_idxs SciMLBase.save_discretes!(i, idx) end @@ -918,40 +925,40 @@ function compile_equational_affect( wrap_code = add_integrator_header(sys, integ, :p), expression = Val{false}, outputidxs = p_idxs, wrap_mtkparameters, cse = false) - return function explicit_affect!(integ) - isempty(dvs_to_update) || u_up!(integ) - isempty(ps_to_update) || p_up!(integ) - reset_jumps && reset_aggregated_jumps!(integ) + return let dvs_to_update = dvs_to_update, ps_to_update = ps_to_update, reset_jump = reset_jump, u_up! = u_up!, p_up! = p_up! + function explicit_affect!(integ) + isempty(dvs_to_update) || u_up!(integ) + isempty(ps_to_update) || p_up!(integ) + reset_jumps && reset_aggregated_jumps!(integ) + end 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 + affsys = affsys, ps_to_update = ps_to_update, aff = aff, sys = sys + + affu_getters = [getsym(affsys, u) for u in unknowns(affsys)] + affp_getters = [getsym(affsys, unPre(p)) for p in parameters(affsys)] + u_setters = [setsym(sys, u) for u in dvs_to_update] + p_setters = [setsym(sys, p) for p in ps_to_update] + solu_getters = [getsym(affsys, sys_map[u]) for u in dvs_to_update] + solp_getters = [getsym(affsys, sys_map[p]) for p in ps_to_update] + + affprob = ImplicitDiscreteProblem(affsys, u0map, (integ.t, integ.t), pmap; + build_initializeprob = false, check_length = false) function implicit_affect!(integ) - pmap = Pair[] - for pre_p in parameters(affsys) - p = unPre(pre_p) - pval = isparameter(p) ? integ.ps[p] : integ[p] - push!(pmap, pre_p => pval) - end - u0 = Pair[] - for u in unknowns(affsys) - uval = isparameter(aff_map[u]) ? integ.ps[aff_map[u]] : integ[u] - push!(u0, u => uval) - end - affprob = ImplicitDiscreteProblem(affsys, u0, (integ.t, integ.t), pmap; - build_initializeprob = false, check_length = false) - @show pmap - @show u0 + pmap = [p => getp(integ) for (p, getp) in zip(parameters(affsys), p_getters)] + u0map = [u => getu(integ) for (u, getu) in zip(unknowns(affsys), u_getters)] + affprob = remake(affprob, u0 = u0map, p = pmap) affsol = init(affprob, IDSolve()) - @show affsol (check_error(affsol) === ReturnCode.InitialFailure) && throw(UnsolvableCallbackError(all_equations(aff))) - for u in dvs_to_update - integ[u] = affsol[sys_map[u]] + + for (setu!, getu) in zip(u_setters, solu_getters) + setu!(integ, getu(affsol)) end - for p in ps_to_update - integ.ps[p] = affsol[sys_map[p]] + for (setp!, getp) in zip(p_setters, solp_getters) + setp!(integ, getp(affsol)) end end end diff --git a/src/systems/discrete_system/implicit_discrete_system.jl b/src/systems/discrete_system/implicit_discrete_system.jl index eb3a094ef5..c176d801dc 100644 --- a/src/systems/discrete_system/implicit_discrete_system.jl +++ b/src/systems/discrete_system/implicit_discrete_system.jl @@ -299,7 +299,7 @@ function shift_u0map_forward(sys::ImplicitDiscreteSystem, u0map, defs) v = u0map[k] if !((op = operation(k)) isa Shift) isnothing(getunshifted(k)) && - @warn "Initial condition given in term of current state of the unknown. If `build_initializeprob = false`, this may be overriden by the implicit discrete solver." + @warn "Initial condition given in term of current state of the unknown. If `build_initializeprob = false`, this may be overridden by the implicit discrete solver." updated[k] = v elseif op.steps > 0 diff --git a/src/systems/jumps/jumpsystem.jl b/src/systems/jumps/jumpsystem.jl index 313aada900..5cfabefe1e 100644 --- a/src/systems/jumps/jumpsystem.jl +++ b/src/systems/jumps/jumpsystem.jl @@ -75,6 +75,10 @@ struct JumpSystem{U <: ArrayPartition} <: AbstractTimeDependentSystem Type of the system. """ connector_type::Any + """ + A `Vector{SymbolicContinuousCallback}` that model events. + The integrator will use root finding to guarantee that it steps at each zero crossing. + """ continuous_events::Vector{SymbolicContinuousCallback} """ A `Vector{SymbolicDiscreteCallback}` that models events. Symbolic diff --git a/src/systems/problem_utils.jl b/src/systems/problem_utils.jl index a7867380e7..47bf3c678d 100644 --- a/src/systems/problem_utils.jl +++ b/src/systems/problem_utils.jl @@ -380,7 +380,9 @@ function better_varmap_to_vars(varmap::AbstractDict, vars::Vector; vals = promote_to_concrete(vals; tofloat = tofloat, use_union = false) end - if container_type <: Tuple + if isempty(vals) + return nothing + elseif container_type <: Tuple return (vals...,) else return SymbolicUtils.Code.create_array(container_type, eltype(vals), Val{1}(), diff --git a/src/systems/systems.jl b/src/systems/systems.jl index bed7824d58..4e4a7f7ba4 100644 --- a/src/systems/systems.jl +++ b/src/systems/systems.jl @@ -43,10 +43,6 @@ function structural_simplify( end if newsys isa DiscreteSystem && any(eq -> symbolic_type(eq.lhs) == NotSymbolic(), equations(newsys)) - #error(""" - # Encountered algebraic equations when simplifying discrete system. Please construct \ - # an ImplicitDiscreteSystem instead. - #""") end for pass in additional_passes newsys = pass(newsys) diff --git a/test/symbolic_events.jl b/test/symbolic_events.jl index adcb24dc76..eed3329a76 100644 --- a/test/symbolic_events.jl +++ b/test/symbolic_events.jl @@ -22,43 +22,43 @@ affect_neg = [x ~ 1] e = SymbolicContinuousCallback(eqs[]) @test e isa SymbolicContinuousCallback @test isequal(equations(e), eqs) - @test e.affect == nothing - @test e.affect_neg == nothing + @test e.affect === nothing + @test e.affect_neg === nothing @test e.rootfind == SciMLBase.LeftRootFind e = SymbolicContinuousCallback(eqs) @test e isa SymbolicContinuousCallback @test isequal(equations(e), eqs) - @test e.affect == nothing - @test e.affect_neg == nothing + @test e.affect === nothing + @test e.affect_neg === nothing @test e.rootfind == SciMLBase.LeftRootFind e = SymbolicContinuousCallback(eqs, nothing) @test e isa SymbolicContinuousCallback @test isequal(equations(e), eqs) - @test e.affect == nothing - @test e.affect_neg == nothing + @test e.affect === nothing + @test e.affect_neg === nothing @test e.rootfind == SciMLBase.LeftRootFind e = SymbolicContinuousCallback(eqs[], nothing) @test e isa SymbolicContinuousCallback @test isequal(equations(e), eqs) - @test e.affect == nothing - @test e.affect_neg == nothing + @test e.affect === nothing + @test e.affect_neg === nothing @test e.rootfind == SciMLBase.LeftRootFind e = SymbolicContinuousCallback(eqs => nothing) @test e isa SymbolicContinuousCallback @test isequal(equations(e), eqs) - @test e.affect == nothing - @test e.affect_neg == nothing + @test e.affect === nothing + @test e.affect_neg === nothing @test e.rootfind == SciMLBase.LeftRootFind e = SymbolicContinuousCallback(eqs[] => nothing) @test e isa SymbolicContinuousCallback @test isequal(equations(e), eqs) - @test e.affect == nothing - @test e.affect_neg == nothing + @test e.affect === nothing + @test e.affect_neg === nothing @test e.rootfind == SciMLBase.LeftRootFind ## With affect From b72681087b0a1c51500ee78ed4666038f90469ea Mon Sep 17 00:00:00 2001 From: vyudu Date: Tue, 29 Apr 2025 17:24:15 -0400 Subject: [PATCH 091/122] fix: fix implicit_affect --- src/systems/callbacks.jl | 27 +++++++++++++++------------ test/symbolic_events.jl | 2 +- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/src/systems/callbacks.jl b/src/systems/callbacks.jl index 0bc4fabc47..f6d97790f3 100644 --- a/src/systems/callbacks.jl +++ b/src/systems/callbacks.jl @@ -887,7 +887,7 @@ Compile an affect defined by a set of equations. Systems with algebraic equation function compile_equational_affect( aff::Union{AffectSystem, Vector{Equation}}, sys; reset_jumps = false, kwargs...) if aff isa AbstractVector - aff = make_affect(aff; iv = get_iv(sys), warn_no_algebraic = false) + aff = make_affect(aff; iv = get_iv(sys), alg_eqs = alg_equations(sys), warn_no_algebraic = false) end affsys = system(aff) ps_to_update = discretes(aff) @@ -925,7 +925,7 @@ function compile_equational_affect( wrap_code = add_integrator_header(sys, integ, :p), expression = Val{false}, outputidxs = p_idxs, wrap_mtkparameters, cse = false) - return let dvs_to_update = dvs_to_update, ps_to_update = ps_to_update, reset_jump = reset_jump, u_up! = u_up!, p_up! = p_up! + return let dvs_to_update = dvs_to_update, ps_to_update = ps_to_update, reset_jumps = reset_jumps, u_up! = u_up!, p_up! = p_up! function explicit_affect!(integ) isempty(dvs_to_update) || u_up!(integ) isempty(ps_to_update) || p_up!(integ) @@ -936,28 +936,31 @@ function compile_equational_affect( 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 - affu_getters = [getsym(affsys, u) for u in unknowns(affsys)] - affp_getters = [getsym(affsys, unPre(p)) for p in parameters(affsys)] + dvs_to_access = unknowns(affsys) + ps_to_access = parameters(affsys) + + u_getters = [getsym(sys, aff_map[u]) for u in dvs_to_access] + p_getters = [getsym(sys, unPre(p)) for p in ps_to_access] u_setters = [setsym(sys, u) for u in dvs_to_update] p_setters = [setsym(sys, p) for p in ps_to_update] - solu_getters = [getsym(affsys, sys_map[u]) for u in dvs_to_update] - solp_getters = [getsym(affsys, sys_map[p]) for p in ps_to_update] + affu_getters = [getsym(affsys, sys_map[u]) for u in dvs_to_update] + affp_getters = [getsym(affsys, sys_map[p]) for p in ps_to_update] - affprob = ImplicitDiscreteProblem(affsys, u0map, (integ.t, integ.t), pmap; + affprob = ImplicitDiscreteProblem(affsys, [dv => 0 for dv in dvs_to_access], (0, 0), [p => 0 for p in ps_to_access]; build_initializeprob = false, check_length = false) function implicit_affect!(integ) - pmap = [p => getp(integ) for (p, getp) in zip(parameters(affsys), p_getters)] - u0map = [u => getu(integ) for (u, getu) in zip(unknowns(affsys), u_getters)] - affprob = remake(affprob, u0 = u0map, p = pmap) + pmap = Pair[p => getp(integ) for (p, getp) in zip(ps_to_access, p_getters)] + u0map = Pair[u => getu(integ) for (u, getu) in zip(dvs_to_access, u_getters)] + affprob = remake(affprob, u0 = u0map, p = pmap, tspan = (integ.t, integ.t)) affsol = init(affprob, IDSolve()) (check_error(affsol) === ReturnCode.InitialFailure) && throw(UnsolvableCallbackError(all_equations(aff))) - for (setu!, getu) in zip(u_setters, solu_getters) + for (setu!, getu) in zip(u_setters, affu_getters) setu!(integ, getu(affsol)) end - for (setp!, getp) in zip(p_setters, solp_getters) + for (setp!, getp) in zip(p_setters, affp_getters) setp!(integ, getp(affsol)) end end diff --git a/test/symbolic_events.jl b/test/symbolic_events.jl index eed3329a76..355a541f60 100644 --- a/test/symbolic_events.jl +++ b/test/symbolic_events.jl @@ -789,7 +789,7 @@ end @test sign.(cos.(required_crossings_c1 .- 1e-6)) == sign.(last.(cr1)) @test sign.(cos.(3 * (required_crossings_c2 .+ 1e-6))) == sign.(last.(cr2)) end -# + @testset "Discrete event reinitialization (#3142)" begin @connector LiquidPort begin p(t)::Float64, [description = "Set pressure in bar", From f556dc5c3c8cef22e2a225782a5d5b61f966f267 Mon Sep 17 00:00:00 2001 From: vyudu Date: Tue, 29 Apr 2025 17:24:46 -0400 Subject: [PATCH 092/122] format --- src/systems/callbacks.jl | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/systems/callbacks.jl b/src/systems/callbacks.jl index f6d97790f3..9328777454 100644 --- a/src/systems/callbacks.jl +++ b/src/systems/callbacks.jl @@ -887,7 +887,8 @@ Compile an affect defined by a set of equations. Systems with algebraic equation function compile_equational_affect( aff::Union{AffectSystem, Vector{Equation}}, sys; reset_jumps = false, kwargs...) if aff isa AbstractVector - aff = make_affect(aff; iv = get_iv(sys), alg_eqs = alg_equations(sys), warn_no_algebraic = false) + aff = make_affect( + aff; iv = get_iv(sys), alg_eqs = alg_equations(sys), warn_no_algebraic = false) end affsys = system(aff) ps_to_update = discretes(aff) @@ -925,7 +926,9 @@ function compile_equational_affect( wrap_code = add_integrator_header(sys, integ, :p), expression = Val{false}, outputidxs = p_idxs, wrap_mtkparameters, cse = false) - return let dvs_to_update = dvs_to_update, ps_to_update = ps_to_update, reset_jumps = reset_jumps, u_up! = u_up!, p_up! = p_up! + return let dvs_to_update = dvs_to_update, ps_to_update = ps_to_update, + reset_jumps = reset_jumps, u_up! = u_up!, p_up! = p_up! + function explicit_affect!(integ) isempty(dvs_to_update) || u_up!(integ) isempty(ps_to_update) || p_up!(integ) @@ -935,7 +938,7 @@ function compile_equational_affect( 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 - + dvs_to_access = unknowns(affsys) ps_to_access = parameters(affsys) @@ -946,12 +949,14 @@ function compile_equational_affect( affu_getters = [getsym(affsys, sys_map[u]) for u in dvs_to_update] affp_getters = [getsym(affsys, sys_map[p]) for p in ps_to_update] - affprob = ImplicitDiscreteProblem(affsys, [dv => 0 for dv in dvs_to_access], (0, 0), [p => 0 for p in ps_to_access]; + affprob = ImplicitDiscreteProblem(affsys, [dv => 0 for dv in dvs_to_access], + (0, 0), [p => 0 for p in ps_to_access]; build_initializeprob = false, check_length = false) function implicit_affect!(integ) pmap = Pair[p => getp(integ) for (p, getp) in zip(ps_to_access, p_getters)] - u0map = Pair[u => getu(integ) for (u, getu) in zip(dvs_to_access, u_getters)] + u0map = Pair[u => getu(integ) + for (u, getu) in zip(dvs_to_access, u_getters)] affprob = remake(affprob, u0 = u0map, p = pmap, tspan = (integ.t, integ.t)) affsol = init(affprob, IDSolve()) (check_error(affsol) === ReturnCode.InitialFailure) && From 44cf0fd22e01233240446bf741eb60e0a29e6e32 Mon Sep 17 00:00:00 2001 From: vyudu Date: Wed, 30 Apr 2025 08:53:56 -0400 Subject: [PATCH 093/122] fix: improve performance of implicit affect --- src/systems/callbacks.jl | 40 +++++++++++-------- src/systems/diffeqs/odesystem.jl | 8 +--- src/systems/diffeqs/sdesystem.jl | 7 +--- .../implicit_discrete_system.jl | 3 -- 4 files changed, 26 insertions(+), 32 deletions(-) diff --git a/src/systems/callbacks.jl b/src/systems/callbacks.jl index 9328777454..d017fb3cd2 100644 --- a/src/systems/callbacks.jl +++ b/src/systems/callbacks.jl @@ -942,32 +942,27 @@ function compile_equational_affect( dvs_to_access = unknowns(affsys) ps_to_access = parameters(affsys) - u_getters = [getsym(sys, aff_map[u]) for u in dvs_to_access] - p_getters = [getsym(sys, unPre(p)) for p in ps_to_access] - u_setters = [setsym(sys, u) for u in dvs_to_update] - p_setters = [setsym(sys, p) for p in ps_to_update] - affu_getters = [getsym(affsys, sys_map[u]) for u in dvs_to_update] - affp_getters = [getsym(affsys, sys_map[p]) for p in ps_to_update] + u_getter = getsym(sys, [aff_map[u] for u in dvs_to_access]) + p_getter = getsym(sys, [unPre(p) for p in ps_to_access]) + u_setter! = setsym(sys, dvs_to_update) + p_setter! = setsym(sys, ps_to_update) + affu_getter = getsym(affsys, [sys_map[u] for u in dvs_to_update]) + affp_getter = getsym(affsys, [sys_map[p] for p in ps_to_update]) affprob = ImplicitDiscreteProblem(affsys, [dv => 0 for dv in dvs_to_access], (0, 0), [p => 0 for p in ps_to_access]; build_initializeprob = false, check_length = false) function implicit_affect!(integ) - pmap = Pair[p => getp(integ) for (p, getp) in zip(ps_to_access, p_getters)] - u0map = Pair[u => getu(integ) - for (u, getu) in zip(dvs_to_access, u_getters)] - affprob = remake(affprob, u0 = u0map, p = pmap, tspan = (integ.t, integ.t)) + new_us = u_getter(integ) + new_ps = p_getter(integ) + affprob = remake(affprob, u0 = new_us, p = new_ps, tspan = (integ.t, integ.t)) affsol = init(affprob, IDSolve()) (check_error(affsol) === ReturnCode.InitialFailure) && throw(UnsolvableCallbackError(all_equations(aff))) - for (setu!, getu) in zip(u_setters, affu_getters) - setu!(integ, getu(affsol)) - end - for (setp!, getp) in zip(p_setters, affp_getters) - setp!(integ, getp(affsol)) - end + u_setter!(integ, affu_getter(affsol)) + p_setter!(integ, affp_getter(affsol)) end end end @@ -1080,3 +1075,16 @@ function continuous_events_toplevel(sys::AbstractSystem) end return get_continuous_events(sys) end + +""" +Process the symbolic events of a system. +""" +function create_symbolic_events(cont_events, disc_events, sys_eqs, iv) + alg_eqs = filter(eq -> eq.lhs isa Union{Symbolic, Number} && !is_diff_equation(eq), + sys_eqs) + cont_callbacks = to_cb_vector(cont_events; CB_TYPE = SymbolicContinuousCallback, + iv = iv, alg_eqs = alg_eqs, warn_no_algebraic = false) + disc_callbacks = to_cb_vector(disc_events; CB_TYPE = SymbolicDiscreteCallback, + iv = iv, alg_eqs = alg_eqs, warn_no_algebraic = false) + cont_callbacks, disc_callbacks +end diff --git a/src/systems/diffeqs/odesystem.jl b/src/systems/diffeqs/odesystem.jl index 8283a10201..8f8db03429 100644 --- a/src/systems/diffeqs/odesystem.jl +++ b/src/systems/diffeqs/odesystem.jl @@ -336,13 +336,7 @@ function ODESystem(deqs::AbstractVector{<:Equation}, iv, dvs, ps; throw(ArgumentError("System names must be unique.")) end - alg_eqs = filter(eq -> eq.lhs isa Union{Symbolic, Number} && !is_diff_equation(eq), - deqs) - cont_callbacks = to_cb_vector(continuous_events; CB_TYPE = SymbolicContinuousCallback, - iv = iv, alg_eqs = alg_eqs, warn_no_algebraic = false) - disc_callbacks = to_cb_vector(discrete_events; CB_TYPE = SymbolicDiscreteCallback, - iv = iv, alg_eqs = alg_eqs, warn_no_algebraic = false) - + cont_callbacks, disc_callbacks = create_symbolic_events(continuous_events, discrete_events, deqs, iv) if is_dde === nothing is_dde = _check_if_dde(deqs, iv′, systems) end diff --git a/src/systems/diffeqs/sdesystem.jl b/src/systems/diffeqs/sdesystem.jl index 7baf94e1a3..1d51b8b9dd 100644 --- a/src/systems/diffeqs/sdesystem.jl +++ b/src/systems/diffeqs/sdesystem.jl @@ -270,12 +270,7 @@ function SDESystem(deqs::AbstractVector{<:Equation}, neqs::AbstractArray, iv, dv Wfact = RefValue(EMPTY_JAC) Wfact_t = RefValue(EMPTY_JAC) - alg_eqs = filter(eq -> eq.lhs isa Union{Symbolic, Number} && !is_diff_equation(eq), - deqs) - cont_callbacks = to_cb_vector(continuous_events; CB_TYPE = SymbolicContinuousCallback, - iv = iv, alg_eqs = alg_eqs, warn_no_algebraic = false) - disc_callbacks = to_cb_vector(discrete_events; CB_TYPE = SymbolicDiscreteCallback, - iv = iv, alg_eqs = alg_eqs, warn_no_algebraic = false) + cont_callbacks, disc_callbacks = create_symbolic_events(continuous_events, discrete_events, deqs, iv) if is_dde === nothing is_dde = _check_if_dde(deqs, iv′, systems) diff --git a/src/systems/discrete_system/implicit_discrete_system.jl b/src/systems/discrete_system/implicit_discrete_system.jl index c176d801dc..b818ffcc8d 100644 --- a/src/systems/discrete_system/implicit_discrete_system.jl +++ b/src/systems/discrete_system/implicit_discrete_system.jl @@ -298,9 +298,6 @@ function shift_u0map_forward(sys::ImplicitDiscreteSystem, u0map, defs) for k in collect(keys(u0map)) v = u0map[k] if !((op = operation(k)) isa Shift) - isnothing(getunshifted(k)) && - @warn "Initial condition given in term of current state of the unknown. If `build_initializeprob = false`, this may be overridden by the implicit discrete solver." - updated[k] = v elseif op.steps > 0 error("Initial conditions must be for the current or past state of the unknowns. Instead of providing the condition for $k, provide the condition for $(Shift(iv, -1)(only(arguments(k)))).") From 9c7c597e21266d4c5ed4ffaa9fbd3a18264e0081 Mon Sep 17 00:00:00 2001 From: vyudu Date: Wed, 30 Apr 2025 09:58:01 -0400 Subject: [PATCH 094/122] fix: drop alg_eqs for JumpSystem --- src/systems/callbacks.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/systems/callbacks.jl b/src/systems/callbacks.jl index d017fb3cd2..ee49e192d3 100644 --- a/src/systems/callbacks.jl +++ b/src/systems/callbacks.jl @@ -888,7 +888,7 @@ function compile_equational_affect( aff::Union{AffectSystem, Vector{Equation}}, sys; reset_jumps = false, kwargs...) if aff isa AbstractVector aff = make_affect( - aff; iv = get_iv(sys), alg_eqs = alg_equations(sys), warn_no_algebraic = false) + aff; iv = get_iv(sys), warn_no_algebraic = false) end affsys = system(aff) ps_to_update = discretes(aff) From d2ec633e31ed2cb7732e53bb2a81523d2a9841a7 Mon Sep 17 00:00:00 2001 From: vyudu Date: Wed, 30 Apr 2025 14:17:29 -0400 Subject: [PATCH 095/122] fix: fix jumpsystem test --- src/systems/callbacks.jl | 2 +- test/jumpsystem.jl | 2 +- test/symbolic_events.jl | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/systems/callbacks.jl b/src/systems/callbacks.jl index ee49e192d3..4b07930848 100644 --- a/src/systems/callbacks.jl +++ b/src/systems/callbacks.jl @@ -283,7 +283,7 @@ function make_affect(affect::Vector{Equation}; discrete_parameters = Any[], for eq in affect if !haspre(eq) && !(symbolic_type(eq.rhs) === NotSymbolic() || symbolic_type(eq.lhs) === NotSymbolic()) - @warn "Affect equation $eq has no `Pre` operator. As such it will be interpreted as an algebraic equation to be satisfied after the callback. If you intended to use the value of a variable x before the affect, use Pre(x)." + @warn "Affect equation $eq has no `Pre` operator. As such it will be interpreted as an algebraic equation to be satisfied after the callback. If you intended to use the value of a variable x before the affect, use Pre(x). Errors may be thrown if there is no `Pre` and algebraic equations are unsatisfiable, such as X ~ X + 1." end collect_vars!(dvs, params, eq, iv; op = Pre) diffvs = collect_applied_operators(eq, Differential) diff --git a/test/jumpsystem.jl b/test/jumpsystem.jl index 290851bda9..385a705bd3 100644 --- a/test/jumpsystem.jl +++ b/test/jumpsystem.jl @@ -556,7 +556,7 @@ end @parameters a b eq = D(X) ~ a rate = b * X - affect = [X ~ X - 1] + affect = [X ~ Pre(X) - 1] crj = ConstantRateJump(rate, affect) @named jsys = JumpSystem([crj, eq], t, [X], [a, b]) jsys = complete(jsys) diff --git a/test/symbolic_events.jl b/test/symbolic_events.jl index 355a541f60..e208a74dd5 100644 --- a/test/symbolic_events.jl +++ b/test/symbolic_events.jl @@ -638,8 +638,8 @@ end sol = solve(prob, Tsit5(), saveat = 0.1) @test typeof(oneosc_ce_simpl) == ODESystem - @test sol[1, 6] < 1.0 # test whether x(t) decreases over time - @test sol[1, 18] > 0.5 # test whether event happened + @test sol(0.5, idxs = oscce.x) < 1.0 # test whether x(t) decreases over time + @test sol(1.5, idxs = oscce.x) > 0.5 # test whether event happened end @testset "Additional SymbolicContinuousCallback options" begin From 15dc5e23c388f4d4bbf4e0580a699989018d0629 Mon Sep 17 00:00:00 2001 From: vyudu Date: Wed, 30 Apr 2025 14:18:52 -0400 Subject: [PATCH 096/122] format --- src/systems/callbacks.jl | 3 ++- src/systems/diffeqs/odesystem.jl | 3 ++- src/systems/diffeqs/sdesystem.jl | 3 ++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/systems/callbacks.jl b/src/systems/callbacks.jl index 4b07930848..8114f4c5d3 100644 --- a/src/systems/callbacks.jl +++ b/src/systems/callbacks.jl @@ -956,7 +956,8 @@ function compile_equational_affect( function implicit_affect!(integ) new_us = u_getter(integ) new_ps = p_getter(integ) - affprob = remake(affprob, u0 = new_us, p = new_ps, tspan = (integ.t, integ.t)) + affprob = remake( + affprob, u0 = new_us, p = new_ps, tspan = (integ.t, integ.t)) affsol = init(affprob, IDSolve()) (check_error(affsol) === ReturnCode.InitialFailure) && throw(UnsolvableCallbackError(all_equations(aff))) diff --git a/src/systems/diffeqs/odesystem.jl b/src/systems/diffeqs/odesystem.jl index 8f8db03429..b3116989eb 100644 --- a/src/systems/diffeqs/odesystem.jl +++ b/src/systems/diffeqs/odesystem.jl @@ -336,7 +336,8 @@ function ODESystem(deqs::AbstractVector{<:Equation}, iv, dvs, ps; throw(ArgumentError("System names must be unique.")) end - cont_callbacks, disc_callbacks = create_symbolic_events(continuous_events, discrete_events, deqs, iv) + cont_callbacks, disc_callbacks = create_symbolic_events( + continuous_events, discrete_events, deqs, iv) if is_dde === nothing is_dde = _check_if_dde(deqs, iv′, systems) end diff --git a/src/systems/diffeqs/sdesystem.jl b/src/systems/diffeqs/sdesystem.jl index 1d51b8b9dd..7f92853008 100644 --- a/src/systems/diffeqs/sdesystem.jl +++ b/src/systems/diffeqs/sdesystem.jl @@ -270,7 +270,8 @@ function SDESystem(deqs::AbstractVector{<:Equation}, neqs::AbstractArray, iv, dv Wfact = RefValue(EMPTY_JAC) Wfact_t = RefValue(EMPTY_JAC) - cont_callbacks, disc_callbacks = create_symbolic_events(continuous_events, discrete_events, deqs, iv) + cont_callbacks, disc_callbacks = create_symbolic_events( + continuous_events, discrete_events, deqs, iv) if is_dde === nothing is_dde = _check_if_dde(deqs, iv′, systems) From 115b8db373b7638b24c1af00aabd5521487a2572 Mon Sep 17 00:00:00 2001 From: vyudu Date: Thu, 1 May 2025 13:03:46 -0400 Subject: [PATCH 097/122] fix: fix SII issue in the implicit affect --- src/systems/callbacks.jl | 33 +++++++++++-------- .../implicit_discrete_system.jl | 2 ++ 2 files changed, 21 insertions(+), 14 deletions(-) diff --git a/src/systems/callbacks.jl b/src/systems/callbacks.jl index 8114f4c5d3..0e141ab430 100644 --- a/src/systems/callbacks.jl +++ b/src/systems/callbacks.jl @@ -283,7 +283,7 @@ function make_affect(affect::Vector{Equation}; discrete_parameters = Any[], for eq in affect if !haspre(eq) && !(symbolic_type(eq.rhs) === NotSymbolic() || symbolic_type(eq.lhs) === NotSymbolic()) - @warn "Affect equation $eq has no `Pre` operator. As such it will be interpreted as an algebraic equation to be satisfied after the callback. If you intended to use the value of a variable x before the affect, use Pre(x). Errors may be thrown if there is no `Pre` and algebraic equations are unsatisfiable, such as X ~ X + 1." + @warn "Affect equation $eq has no `Pre` operator. As such it will be interpreted as an algebraic equation to be satisfied after the callback. If you intended to use the value of a variable x before the affect, use Pre(x). Errors may be thrown if there is no `Pre` and the algebraic equation is unsatisfiable, such as X ~ X + 1." end collect_vars!(dvs, params, eq, iv; op = Pre) diffvs = collect_applied_operators(eq, Differential) @@ -939,31 +939,36 @@ function compile_equational_affect( 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 - dvs_to_access = unknowns(affsys) - ps_to_access = parameters(affsys) + dvs_to_access = [aff_map[u] for u in unknowns(affsys)] + ps_to_access = [unPre(p) for p in parameters(affsys)] - u_getter = getsym(sys, [aff_map[u] for u in dvs_to_access]) - p_getter = getsym(sys, [unPre(p) for p in ps_to_access]) + affu_getter = getsym(sys, dvs_to_access) + affp_getter = getsym(sys, ps_to_access) + affu_setter! = setsym(sys, unknowns(affsys)) + affp_setter! = setsym(sys, parameters(affsys)) u_setter! = setsym(sys, dvs_to_update) p_setter! = setsym(sys, ps_to_update) - affu_getter = getsym(affsys, [sys_map[u] for u in dvs_to_update]) - affp_getter = getsym(affsys, [sys_map[p] for p in ps_to_update]) + u_getter = getsym(affsys, [sys_map[u] for u in dvs_to_update]) + p_getter = getsym(affsys, [sys_map[p] for p in ps_to_update]) - affprob = ImplicitDiscreteProblem(affsys, [dv => 0 for dv in dvs_to_access], - (0, 0), [p => 0 for p in ps_to_access]; + affprob = ImplicitDiscreteProblem(affsys, [dv => 0 for dv in unknowns(affsys)], + (0, 0), [p => 0. for p in parameters(affsys)]; build_initializeprob = false, check_length = false) function implicit_affect!(integ) - new_us = u_getter(integ) - new_ps = p_getter(integ) + new_u0 = affu_getter(integ) + affu_setter!(affporb, new_u0) + new_ps = affp_getter(integ) + affp_setter!(affprob, new_ps) + affprob = remake( - affprob, u0 = new_us, p = new_ps, tspan = (integ.t, integ.t)) + affprob, tspan = (integ.t, integ.t)) affsol = init(affprob, IDSolve()) (check_error(affsol) === ReturnCode.InitialFailure) && throw(UnsolvableCallbackError(all_equations(aff))) - u_setter!(integ, affu_getter(affsol)) - p_setter!(integ, affp_getter(affsol)) + u_setter!(integ, u_getter(affsol)) + p_setter!(integ, p_getter(affsol)) end end end diff --git a/src/systems/discrete_system/implicit_discrete_system.jl b/src/systems/discrete_system/implicit_discrete_system.jl index b818ffcc8d..24643fa5cd 100644 --- a/src/systems/discrete_system/implicit_discrete_system.jl +++ b/src/systems/discrete_system/implicit_discrete_system.jl @@ -384,6 +384,8 @@ function SciMLBase.ImplicitDiscreteFunction{iip, specialize}( else resid_prototype = calculate_resid_prototype(length(equations(sys)), u0, p) end + out = zeros(length(dvs)) + u = zeros(length(dvs)) if specialize === SciMLBase.FunctionWrapperSpecialize && iip if u0 === nothing || p === nothing || t === nothing From 26537d1275a8077bb8846a1b24c77bbdece14bc2 Mon Sep 17 00:00:00 2001 From: vyudu Date: Thu, 1 May 2025 13:04:56 -0400 Subject: [PATCH 098/122] cleanup --- src/systems/discrete_system/implicit_discrete_system.jl | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/systems/discrete_system/implicit_discrete_system.jl b/src/systems/discrete_system/implicit_discrete_system.jl index 24643fa5cd..b818ffcc8d 100644 --- a/src/systems/discrete_system/implicit_discrete_system.jl +++ b/src/systems/discrete_system/implicit_discrete_system.jl @@ -384,8 +384,6 @@ function SciMLBase.ImplicitDiscreteFunction{iip, specialize}( else resid_prototype = calculate_resid_prototype(length(equations(sys)), u0, p) end - out = zeros(length(dvs)) - u = zeros(length(dvs)) if specialize === SciMLBase.FunctionWrapperSpecialize && iip if u0 === nothing || p === nothing || t === nothing From 5409f2b3f09fc37a4dbe03044125b867dbfd0bef Mon Sep 17 00:00:00 2001 From: vyudu Date: Thu, 1 May 2025 15:48:05 -0400 Subject: [PATCH 099/122] fix: typo and setsym error --- src/systems/callbacks.jl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/systems/callbacks.jl b/src/systems/callbacks.jl index 0e141ab430..92dbfddda6 100644 --- a/src/systems/callbacks.jl +++ b/src/systems/callbacks.jl @@ -944,8 +944,8 @@ function compile_equational_affect( affu_getter = getsym(sys, dvs_to_access) affp_getter = getsym(sys, ps_to_access) - affu_setter! = setsym(sys, unknowns(affsys)) - affp_setter! = setsym(sys, parameters(affsys)) + affu_setter! = setsym(affsys, unknowns(affsys)) + affp_setter! = setsym(affsys, parameters(affsys)) u_setter! = setsym(sys, dvs_to_update) p_setter! = setsym(sys, ps_to_update) u_getter = getsym(affsys, [sys_map[u] for u in dvs_to_update]) @@ -957,7 +957,7 @@ function compile_equational_affect( function implicit_affect!(integ) new_u0 = affu_getter(integ) - affu_setter!(affporb, new_u0) + affu_setter!(affprob, new_u0) new_ps = affp_getter(integ) affp_setter!(affprob, new_ps) From 9ed02ce325c45075048d797097f5f853c82ffb4b Mon Sep 17 00:00:00 2001 From: vyudu Date: Mon, 5 May 2025 10:00:36 -0400 Subject: [PATCH 100/122] rebase callback --- src/systems/model_parsing.jl | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/systems/model_parsing.jl b/src/systems/model_parsing.jl index 15697e6d7e..2a852813a2 100644 --- a/src/systems/model_parsing.jl +++ b/src/systems/model_parsing.jl @@ -131,10 +131,7 @@ function _model_macro(mod, fullname::Union{Expr, Symbol}, expr, isconnector) sys = :($type($(flatten_equations)(equations), $iv, variables, parameters; name, description = $description, systems, gui_metadata = $gui_metadata, continuous_events = [$(c_evts...)], discrete_events = [$(d_evts...)], - defaults)) - sys = :($type($(flatten_equations)(equations), $iv, variables, parameters; - name, description = $description, systems, gui_metadata = $gui_metadata, defaults, - costs = [$(costs...)], constraints = [$(cons...)], consolidate = $consolidate)) + defaults, costs = [$(costs...)], constraints = [$(cons...)], consolidate = $consolidate)) if length(ext) == 0 push!(exprs.args, :(var"#___sys___" = $sys)) From 985b8ae799ad2a1d42aeb49cfbfa02f4f661aadc Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Mon, 14 Apr 2025 18:57:00 +0530 Subject: [PATCH 101/122] chore!: bump MAJOR version --- Project.toml | 2 +- TestEnv/Project.toml | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) create mode 100644 TestEnv/Project.toml diff --git a/Project.toml b/Project.toml index b661dc402d..47cec3add7 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "ModelingToolkit" uuid = "961ee093-0014-501f-94e3-6117800e7a78" authors = ["Yingbo Ma ", "Chris Rackauckas and contributors"] -version = "9.78.0" +version = "10.0.0" [deps] ADTypes = "47edcb42-4c32-4615-8424-f2b9edc5f35b" diff --git a/TestEnv/Project.toml b/TestEnv/Project.toml new file mode 100644 index 0000000000..4c82d62efa --- /dev/null +++ b/TestEnv/Project.toml @@ -0,0 +1,8 @@ +[deps] +ModelingToolkit = "961ee093-0014-501f-94e3-6117800e7a78" +ModelingToolkitStandardLibrary = "16a59e39-deab-5bd0-87e4-056b12336739" +NonlinearSolve = "8913a72c-1f9b-4ce2-8d82-65094dcecaec" +OrdinaryDiffEq = "1dea7af3-3e70-54e6-95c3-0bf5283fa5ed" +SymbolicIndexingInterface = "2efcf032-c050-4f8e-a9bb-153293bab1f5" +SymbolicUtils = "d1185830-fcd6-423d-90d6-eec64667417b" +Symbolics = "0c5d862f-8b57-4792-8d23-62f2024744c7" From 5af08a4a48beed5c71e3d03c06d544e5856bc69f Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Mon, 14 Apr 2025 18:57:09 +0530 Subject: [PATCH 102/122] ci: run workflows on PR to v10 branch --- .github/workflows/Documentation.yml | 1 + .github/workflows/FormatCheck.yml | 1 + .github/workflows/Tests.yml | 1 + 3 files changed, 3 insertions(+) diff --git a/.github/workflows/Documentation.yml b/.github/workflows/Documentation.yml index 14bea532b3..e696b42380 100644 --- a/.github/workflows/Documentation.yml +++ b/.github/workflows/Documentation.yml @@ -4,6 +4,7 @@ on: push: branches: - master + - v10 tags: '*' pull_request: diff --git a/.github/workflows/FormatCheck.yml b/.github/workflows/FormatCheck.yml index 6185015c44..0d3052b969 100644 --- a/.github/workflows/FormatCheck.yml +++ b/.github/workflows/FormatCheck.yml @@ -4,6 +4,7 @@ on: push: branches: - 'master' + - v10 tags: '*' pull_request: diff --git a/.github/workflows/Tests.yml b/.github/workflows/Tests.yml index 52c5482970..a62f7f272d 100644 --- a/.github/workflows/Tests.yml +++ b/.github/workflows/Tests.yml @@ -5,6 +5,7 @@ on: branches: - master - 'release-' + - v10 paths-ignore: - 'docs/**' push: From 0fbd4ee60530c8937f0918b0ad6c434ef43bc675 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Mon, 14 Apr 2025 19:10:06 +0530 Subject: [PATCH 103/122] docs: bump MTK compat --- docs/Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Project.toml b/docs/Project.toml index ca13773492..ae0d89dd3a 100644 --- a/docs/Project.toml +++ b/docs/Project.toml @@ -40,7 +40,7 @@ Documenter = "1" DynamicQuantities = "^0.11.2, 0.12, 1" FMI = "0.14" FMIZoo = "1" -ModelingToolkit = "8.33, 9" +ModelingToolkit = "10" ModelingToolkitStandardLibrary = "2.19" NonlinearSolve = "3, 4" Optim = "1.7" From 2f4112e889519f2e4d53be31f5c0f57d29023602 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Mon, 14 Apr 2025 19:15:37 +0530 Subject: [PATCH 104/122] TEMP COMMIT: use branch of MTKStdlib --- Project.toml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 47cec3add7..1d7899d521 100644 --- a/Project.toml +++ b/Project.toml @@ -174,6 +174,7 @@ Logging = "56ddb016-857b-54e1-b83d-db4d58db5568" ModelingToolkitStandardLibrary = "16a59e39-deab-5bd0-87e4-056b12336739" NonlinearSolve = "8913a72c-1f9b-4ce2-8d82-65094dcecaec" Optimization = "7f7a1694-90dd-40f0-9382-eb1efda571ba" +OptimizationBase = "bca83a33-5cc9-4baa-983d-23429ab6bcbb" OptimizationMOI = "fd9f6733-72f4-499f-8506-86b2bdd0dea1" OptimizationOptimJL = "36348300-93cb-4f02-beb5-3c3902f8871e" OrdinaryDiffEq = "1dea7af3-3e70-54e6-95c3-0bf5283fa5ed" @@ -193,5 +194,10 @@ StochasticDiffEq = "789caeaf-c7a9-5a7d-9973-96adeb23e2a0" Sundials = "c3572dad-4567-51f8-b174-8c6c989267f4" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" +[sources] +ModelingToolkitStandardLibrary = { url = "https://github.com/SciML/ModelingToolkitStandardLibrary.jl/", rev = "mtk-v10" } +OptimizationBase = { url = "https://github.com/AayushSabharwal/OptimizationBase.jl", rev = "as/mtk-v10" } +OptimizationMOI = { url = "https://github.com/AayushSabharwal/Optimization.jl", subdir = "lib/OptimizationMOI", rev = "as/mtk-v10" } + [targets] -test = ["AmplNLWriter", "BenchmarkTools", "BoundaryValueDiffEqMIRK", "BoundaryValueDiffEqAscher", "ControlSystemsBase", "DataInterpolations", "DelayDiffEq", "NonlinearSolve", "ForwardDiff", "Ipopt", "Ipopt_jll", "ModelingToolkitStandardLibrary", "Optimization", "OptimizationOptimJL", "OptimizationMOI", "OrdinaryDiffEq", "OrdinaryDiffEqCore", "OrdinaryDiffEqDefault", "REPL", "Random", "ReferenceTests", "SafeTestsets", "StableRNGs", "Statistics", "SteadyStateDiffEq", "Test", "StochasticDiffEq", "Sundials", "StochasticDelayDiffEq", "Pkg", "JET", "OrdinaryDiffEqNonlinearSolve", "Logging"] +test = ["AmplNLWriter", "BenchmarkTools", "BoundaryValueDiffEqMIRK", "BoundaryValueDiffEqAscher", "ControlSystemsBase", "DataInterpolations", "DelayDiffEq", "NonlinearSolve", "ForwardDiff", "Ipopt", "Ipopt_jll", "ModelingToolkitStandardLibrary", "Optimization", "OptimizationOptimJL", "OptimizationMOI", "OrdinaryDiffEq", "OrdinaryDiffEqCore", "OrdinaryDiffEqDefault", "REPL", "Random", "ReferenceTests", "SafeTestsets", "StableRNGs", "Statistics", "SteadyStateDiffEq", "Test", "StochasticDiffEq", "Sundials", "StochasticDelayDiffEq", "Pkg", "JET", "OrdinaryDiffEqNonlinearSolve", "Logging", "OptimizationBase"] From 138c568a135128c0f3b2f3aa4396ad5405dd6f3a Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Thu, 3 Apr 2025 17:07:53 +0530 Subject: [PATCH 105/122] feat: make `@named` always wrap arguments in `ParentScope` --- src/systems/abstractsystem.jl | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/systems/abstractsystem.jl b/src/systems/abstractsystem.jl index 6c00806b7d..a7e2c24ab1 100644 --- a/src/systems/abstractsystem.jl +++ b/src/systems/abstractsystem.jl @@ -2400,11 +2400,7 @@ function default_to_parentscope(v) uv = unwrap(v) uv isa Symbolic || return v apply_to_variables(v) do sym - if !hasmetadata(uv, SymScope) - ParentScope(sym) - else - sym - end + ParentScope(sym) end end From f603e8c9ef1cd4a16d49afe98c46bb7476fb9814 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Mon, 14 Apr 2025 21:38:27 +0530 Subject: [PATCH 106/122] test: test `@named` always wrapping in `ParentScope` --- test/odesystem.jl | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/test/odesystem.jl b/test/odesystem.jl index 9219fca5fb..d5abd0db0e 100644 --- a/test/odesystem.jl +++ b/test/odesystem.jl @@ -9,6 +9,7 @@ using SymbolicUtils: issym using ForwardDiff using ModelingToolkit: value using ModelingToolkit: t_nounits as t, D_nounits as D +using Symbolics: unwrap # Define some variables @parameters σ ρ β @@ -1730,3 +1731,26 @@ end @test obsfn_expr_oop isa Expr @test obsfn_expr_iip isa Expr end + +@testset "`@named` always wraps in `ParentScope`" begin + function SysA(; name, var1) + @variables x(t) + scope = ModelingToolkit.getmetadata(unwrap(var1), ModelingToolkit.SymScope, nothing) + @test scope isa ParentScope + @test scope.parent isa ParentScope + @test scope.parent.parent isa LocalScope + return ODESystem(D(x) ~ var1, t; name) + end + function SysB(; name, var1) + @variables x(t) + @named subsys = SysA(; var1) + return ODESystem(D(x) ~ x, t; systems = [subsys], name) + end + function SysC(; name) + @variables x(t) + @named subsys = SysB(; var1 = x) + return ODESystem(D(x) ~ x, t; systems = [subsys], name) + end + @mtkbuild sys = SysC() + @test length(unknowns(sys)) == 3 +end From c13fe32740a54232ee6f699af52b7ab56c6e954f Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Mon, 14 Apr 2025 19:47:53 +0530 Subject: [PATCH 107/122] refactor: remove `DelayParentScope` --- docs/src/basics/Composition.md | 20 +++------ src/ModelingToolkit.jl | 2 +- src/systems/abstractsystem.jl | 58 ------------------------ src/utils.jl | 2 - test/jumpsystem.jl | 22 +++++----- test/variable_scope.jl | 80 +++++++++++++++------------------- 6 files changed, 53 insertions(+), 131 deletions(-) diff --git a/docs/src/basics/Composition.md b/docs/src/basics/Composition.md index 2e5d4be831..d3de71d696 100644 --- a/docs/src/basics/Composition.md +++ b/docs/src/basics/Composition.md @@ -135,16 +135,14 @@ sys.y = u * 1.1 In a hierarchical system, variables of the subsystem get namespaced by the name of the system they are in. This prevents naming clashes, but also enforces that every unknown and parameter is local to the subsystem it is used in. In some cases it might be desirable to have variables and parameters that are shared between subsystems, or even global. This can be accomplished as follows. ```julia -@parameters a b c d e f +@parameters a b c d # a is a local variable b = ParentScope(b) # b is a variable that belongs to one level up in the hierarchy c = ParentScope(ParentScope(c)) # ParentScope can be nested -d = DelayParentScope(d) # skips one level before applying ParentScope -e = DelayParentScope(e, 2) # second argument allows skipping N levels -f = GlobalScope(f) +d = GlobalScope(d) -p = [a, b, c, d, e, f] +p = [a, b, c, d] level0 = ODESystem(Equation[], t, [], p; name = :level0) level1 = ODESystem(Equation[], t, [], []; name = :level1) ∘ level0 @@ -152,25 +150,19 @@ parameters(level1) #level0₊a #b #c -#level0₊d -#level0₊e -#f +#d level2 = ODESystem(Equation[], t, [], []; name = :level2) ∘ level1 parameters(level2) #level1₊level0₊a #level1₊b #c -#level0₊d -#level1₊level0₊e -#f +#d level3 = ODESystem(Equation[], t, [], []; name = :level3) ∘ level2 parameters(level3) #level2₊level1₊level0₊a #level2₊level1₊b #level2₊c -#level2₊level0₊d -#level1₊level0₊e -#f +#d ``` ## Structural Simplify diff --git a/src/ModelingToolkit.jl b/src/ModelingToolkit.jl index d2d20f902e..1799b75ed9 100644 --- a/src/ModelingToolkit.jl +++ b/src/ModelingToolkit.jl @@ -297,7 +297,7 @@ export PDESystem export Differential, expand_derivatives, @derivatives export Equation, ConstrainedEquation export Term, Sym -export SymScope, LocalScope, ParentScope, DelayParentScope, GlobalScope +export SymScope, LocalScope, ParentScope, GlobalScope export independent_variable, equations, controls, observed, full_equations export initialization_equations, guesses, defaults, parameter_dependencies, hierarchy export structural_simplify, expand_connections, linearize, linearization_function, diff --git a/src/systems/abstractsystem.jl b/src/systems/abstractsystem.jl index a7e2c24ab1..aa850edc67 100644 --- a/src/systems/abstractsystem.jl +++ b/src/systems/abstractsystem.jl @@ -1190,55 +1190,6 @@ function ParentScope(sym::Union{Num, Symbolic, Symbolics.Arr{Num}}) end end -""" - $(TYPEDEF) - -Denotes that a variable belongs to a system that is at least `N + 1` levels up in the -hierarchy from the system whose equations it is involved in. It is namespaced by the -first `N` parents and not namespaced by the `N+1`th parent in the hierarchy. The scope -of the variable after this point is given by `parent`. - -In other words, this scope delays applying `ParentScope` by `N` levels, and applies -`LocalScope` in the meantime. - -# Fields - -$(TYPEDFIELDS) -""" -struct DelayParentScope <: SymScope - parent::SymScope - N::Int -end - -""" - $(TYPEDSIGNATURES) - -Apply `DelayParentScope` to `sym`, with a delay of `N` and `parent` being `LocalScope`. -""" -function DelayParentScope(sym::Union{Num, Symbolic, Symbolics.Arr{Num}}, N) - Base.depwarn( - "`DelayParentScope` is deprecated and will be removed soon", :DelayParentScope) - apply_to_variables(sym) do sym - if iscall(sym) && operation(sym) == getindex - args = arguments(sym) - a1 = setmetadata(args[1], SymScope, - DelayParentScope(getmetadata(value(args[1]), SymScope, LocalScope()), N)) - maketerm(typeof(sym), operation(sym), [a1, args[2:end]...], - metadata(sym)) - else - setmetadata(sym, SymScope, - DelayParentScope(getmetadata(value(sym), SymScope, LocalScope()), N)) - end - end -end - -""" - $(TYPEDSIGNATURES) - -Apply `DelayParentScope` to `sym`, with a delay of `1` and `parent` being `LocalScope`. -""" -DelayParentScope(sym::Union{Num, Symbolic, Symbolics.Arr{Num}}) = DelayParentScope(sym, 1) - """ $(TYPEDEF) @@ -1291,15 +1242,6 @@ function renamespace(sys, x) rename(x, renamespace(getname(sys), getname(x)))::T elseif scope isa ParentScope setmetadata(x, SymScope, scope.parent)::T - elseif scope isa DelayParentScope - if scope.N > 0 - x = setmetadata(x, SymScope, - DelayParentScope(scope.parent, scope.N - 1)) - rename(x, renamespace(getname(sys), getname(x)))::T - else - #rename(x, renamespace(getname(sys), getname(x))) - setmetadata(x, SymScope, scope.parent)::T - end else # GlobalScope x::T end diff --git a/src/utils.jl b/src/utils.jl index 1884a91c19..2b3cbedab0 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -632,8 +632,6 @@ function check_scope_depth(scope, depth) return depth == 0 elseif scope isa ParentScope return depth > 0 && check_scope_depth(scope.parent, depth - 1) - elseif scope isa DelayParentScope - return depth >= scope.N && check_scope_depth(scope.parent, depth - scope.N - 1) elseif scope isa GlobalScope return depth == -1 end diff --git a/test/jumpsystem.jl b/test/jumpsystem.jl index 1ee0408758..6c96055270 100644 --- a/test/jumpsystem.jl +++ b/test/jumpsystem.jl @@ -378,22 +378,20 @@ end # scoping tests let - @variables x1(t) x2(t) x3(t) x4(t) x5(t) + @variables x1(t) x2(t) x3(t) x4(t) x2 = ParentScope(x2) x3 = ParentScope(ParentScope(x3)) - x4 = DelayParentScope(x4) - x5 = GlobalScope(x5) - @parameters p1 p2 p3 p4 p5 + x4 = GlobalScope(x4) + @parameters p1 p2 p3 p4 p2 = ParentScope(p2) p3 = ParentScope(ParentScope(p3)) - p4 = DelayParentScope(p4) - p5 = GlobalScope(p5) + p4 = GlobalScope(p4) j1 = ConstantRateJump(p1, [x1 ~ x1 + 1]) j2 = MassActionJump(p2, [x2 => 1], [x3 => -1]) j3 = VariableRateJump(p3, [x3 ~ x3 + 1, x4 ~ x4 + 1]) - j4 = MassActionJump(p4 * p5, [x1 => 1, x5 => 1], [x1 => -1, x5 => -1, x2 => 1]) - @named js = JumpSystem([j1, j2, j3, j4], t, [x1, x2, x3, x4, x5], [p1, p2, p3, p4, p5]) + j4 = MassActionJump(p4 * p4, [x1 => 1, x4 => 1], [x1 => -1, x4 => -1, x2 => 1]) + @named js = JumpSystem([j1, j2, j3, j4], t, [x1, x2, x3, x4], [p1, p2, p3, p4]) us = Set() ps = Set() @@ -414,13 +412,13 @@ let empty!.((us, ps)) MT.collect_scoped_vars!(us, ps, js, iv; depth = 2) - @test issetequal(us, [x3, x4]) - @test issetequal(ps, [p3, p4]) + @test issetequal(us, [x3]) + @test issetequal(ps, [p3]) empty!.((us, ps)) MT.collect_scoped_vars!(us, ps, js, iv; depth = -1) - @test issetequal(us, [x5]) - @test issetequal(ps, [p5]) + @test issetequal(us, [x4]) + @test issetequal(ps, [p4]) end # PDMP test diff --git a/test/variable_scope.jl b/test/variable_scope.jl index bd1d3cb0cf..59647bf441 100644 --- a/test/variable_scope.jl +++ b/test/variable_scope.jl @@ -51,13 +51,11 @@ end @test renamed([:foo :bar :baz], c) == Symbol("foo₊c") @test renamed([:foo :bar :baz], d) == :d -@parameters a b c d e f +@parameters a b c d p = [a ParentScope(b) ParentScope(ParentScope(c)) - DelayParentScope(d) - DelayParentScope(e, 2) - GlobalScope(f)] + GlobalScope(d)] level0 = ODESystem(Equation[], t, [], p; name = :level0) level1 = ODESystem(Equation[], t, [], []; name = :level1) ∘ level0 @@ -69,9 +67,7 @@ ps = ModelingToolkit.getname.(parameters(level3)) @test isequal(ps[1], :level2₊level1₊level0₊a) @test isequal(ps[2], :level2₊level1₊b) @test isequal(ps[3], :level2₊c) -@test isequal(ps[4], :level2₊level0₊d) -@test isequal(ps[5], :level1₊level0₊e) -@test isequal(ps[6], :f) +@test isequal(ps[4], :d) # Issue@2252 # Tests from PR#2354 @@ -102,40 +98,36 @@ defs = ModelingToolkit.defaults(bar) @test defs[bar.p] == 2 @test isequal(defs[bar.foo.p], bar.p) -# Issue#3101 -@variables x1(t) x2(t) x3(t) x4(t) x5(t) -x2 = ParentScope(x2) -x3 = ParentScope(ParentScope(x3)) -x4 = DelayParentScope(x4) -x5 = GlobalScope(x5) -@parameters p1 p2 p3 p4 p5 -p2 = ParentScope(p2) -p3 = ParentScope(ParentScope(p3)) -p4 = DelayParentScope(p4) -p5 = GlobalScope(p5) - -@named sys1 = ODESystem([D(x1) ~ p1, D(x2) ~ p2, D(x3) ~ p3, D(x4) ~ p4, D(x5) ~ p5], t) -@test isequal(x1, only(unknowns(sys1))) -@test isequal(p1, only(parameters(sys1))) -@named sys2 = ODESystem(Equation[], t; systems = [sys1]) -@test length(unknowns(sys2)) == 2 -@test any(isequal(x2), unknowns(sys2)) -@test length(parameters(sys2)) == 2 -@test any(isequal(p2), parameters(sys2)) -@named sys3 = ODESystem(Equation[], t) -sys3 = sys3 ∘ sys2 -@test length(unknowns(sys3)) == 4 -@test any(isequal(x3), unknowns(sys3)) -@test any(isequal(ModelingToolkit.renamespace(sys1, x4)), unknowns(sys3)) -@test length(parameters(sys3)) == 4 -@test any(isequal(p3), parameters(sys3)) -@test any(isequal(ModelingToolkit.renamespace(sys1, p4)), parameters(sys3)) -sys4 = complete(sys3) -@test length(unknowns(sys3)) == 4 -@test length(parameters(sys4)) == 5 -@test any(isequal(p5), parameters(sys4)) -sys5 = structural_simplify(sys3) -@test length(unknowns(sys5)) == 5 -@test any(isequal(x5), unknowns(sys5)) -@test length(parameters(sys5)) == 5 -@test any(isequal(p5), parameters(sys5)) +@testset "Issue#3101" begin + @variables x1(t) x2(t) x3(t) x4(t) + x2 = ParentScope(x2) + x3 = ParentScope(ParentScope(x3)) + x4 = GlobalScope(x4) + @parameters p1 p2 p3 p4 + p2 = ParentScope(p2) + p3 = ParentScope(ParentScope(p3)) + p4 = GlobalScope(p4) + + @named sys1 = ODESystem([D(x1) ~ p1, D(x2) ~ p2, D(x3) ~ p3, D(x4) ~ p4], t) + @test isequal(x1, only(unknowns(sys1))) + @test isequal(p1, only(parameters(sys1))) + @named sys2 = ODESystem(Equation[], t; systems = [sys1]) + @test length(unknowns(sys2)) == 2 + @test any(isequal(x2), unknowns(sys2)) + @test length(parameters(sys2)) == 2 + @test any(isequal(p2), parameters(sys2)) + @named sys3 = ODESystem(Equation[], t) + sys3 = sys3 ∘ sys2 + @test length(unknowns(sys3)) == 3 + @test any(isequal(x3), unknowns(sys3)) + @test length(parameters(sys3)) == 3 + @test any(isequal(p3), parameters(sys3)) + sys4 = complete(sys3) + @test length(unknowns(sys4)) == 3 + @test length(parameters(sys4)) == 4 + sys5 = structural_simplify(sys3) + @test length(unknowns(sys5)) == 4 + @test any(isequal(x4), unknowns(sys5)) + @test length(parameters(sys5)) == 4 + @test any(isequal(p4), parameters(sys5)) +end From b6b35afee4923a0e7c0069724c6b4d47df4d485d Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Mon, 14 Apr 2025 20:15:04 +0530 Subject: [PATCH 108/122] refactor: remove `time_varying_as_func` --- src/inputoutput.jl | 2 +- src/systems/abstractsystem.jl | 14 -------------- src/systems/callbacks.jl | 8 ++++---- src/systems/codegen_utils.jl | 11 ----------- 4 files changed, 5 insertions(+), 30 deletions(-) diff --git a/src/inputoutput.jl b/src/inputoutput.jl index 8c1a084c9b..375d493c35 100644 --- a/src/inputoutput.jl +++ b/src/inputoutput.jl @@ -220,7 +220,7 @@ function generate_control_function(sys::AbstractODESystem, inputs = unbound_inpu inputs = setdiff(inputs, disturbance_inputs) # ps = [ps; disturbance_inputs] end - inputs = map(x -> time_varying_as_func(value(x), sys), inputs) + inputs = map(value, inputs) disturbance_inputs = unwrap.(disturbance_inputs) eqs = [eq for eq in full_equations(sys)] diff --git a/src/systems/abstractsystem.jl b/src/systems/abstractsystem.jl index aa850edc67..5fdb7b4e25 100644 --- a/src/systems/abstractsystem.jl +++ b/src/systems/abstractsystem.jl @@ -1791,20 +1791,6 @@ function isaffine(sys::AbstractSystem) all(isaffine(r, unknowns(sys)) for r in rhs) end -function time_varying_as_func(x, sys::AbstractTimeDependentSystem) - # if something is not x(t) (the current unknown) - # but is `x(t-1)` or something like that, pass in `x` as a callable function rather - # than pass in a value in place of x(t). - # - # This is done by just making `x` the argument of the function. - if iscall(x) && - issym(operation(x)) && - !(length(arguments(x)) == 1 && isequal(arguments(x)[1], get_iv(sys))) - return operation(x) - end - return x -end - """ $(SIGNATURES) diff --git a/src/systems/callbacks.jl b/src/systems/callbacks.jl index 07809bf611..7d542d9bd0 100644 --- a/src/systems/callbacks.jl +++ b/src/systems/callbacks.jl @@ -598,8 +598,8 @@ Notes """ function compile_condition(cb::SymbolicDiscreteCallback, sys, dvs, ps; expression = Val{true}, eval_expression = false, eval_module = @__MODULE__, kwargs...) - u = map(x -> time_varying_as_func(value(x), sys), dvs) - p = map.(x -> time_varying_as_func(value(x), sys), reorder_parameters(sys, ps)) + u = map(value, dvs) + p = map.(value, reorder_parameters(sys, ps)) t = get_iv(sys) condit = condition(cb) cs = collect_constants(condit) @@ -685,8 +685,8 @@ function compile_affect(eqs::Vector{Equation}, cb, sys, dvs, ps; outputidxs = no _ps = ps ps = reorder_parameters(sys, ps) if checkvars - u = map(x -> time_varying_as_func(value(x), sys), dvs) - p = map.(x -> time_varying_as_func(value(x), sys), ps) + u = map(value, dvs) + p = map.(value, ps) else u = dvs p = ps diff --git a/src/systems/codegen_utils.jl b/src/systems/codegen_utils.jl index a3fe53b95d..bedfdbcc37 100644 --- a/src/systems/codegen_utils.jl +++ b/src/systems/codegen_utils.jl @@ -179,17 +179,6 @@ function build_function_wrapper(sys::AbstractSystem, expr, args...; p_start = 2, args = ntuple(Val(length(args))) do i arg = args[i] - # for time-dependent systems, all arguments are passed through `time_varying_as_func` - # TODO: This is legacy behavior and a candidate for removal in v10 since we have callable - # parameters now. - if is_time_dependent(sys) - arg = if symbolic_type(arg) == NotSymbolic() - arg isa AbstractArray ? - map(x -> time_varying_as_func(unwrap(x), sys), arg) : arg - else - time_varying_as_func(unwrap(arg), sys) - end - end # Make sure to use the proper names for arguments if symbolic_type(arg) == NotSymbolic() && arg isa AbstractArray DestructuredArgs(arg, generated_argument_name(i); create_bindings) From faa5bbf2d3f6321407a0d91d7fbeb4eae2eaebc4 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Mon, 14 Apr 2025 20:37:56 +0530 Subject: [PATCH 109/122] test: update tests with removed `time_varying_as_func` --- test/odesystem.jl | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/test/odesystem.jl b/test/odesystem.jl index d5abd0db0e..8411cdd55e 100644 --- a/test/odesystem.jl +++ b/test/odesystem.jl @@ -136,19 +136,7 @@ du = zeros(3) tgrad_iip(du, u, p, t) @test du == [0.0, -u[2], 0.0] -@parameters σ′(t - 1) -eqs = [D(x) ~ σ′ * (y - x), - D(y) ~ x * (ρ - z) - y, - D(z) ~ x * y - β * z * κ] -@named de = ODESystem(eqs, t) -test_diffeq_inference("global iv-varying", de, t, (x, y, z), (σ′, ρ, β)) - -f = generate_function(de, [x, y, z], [σ′, ρ, β], expression = Val{false})[2] -du = [0.0, 0.0, 0.0] -f(du, [1.0, 2.0, 3.0], [x -> x + 7, 2, 3], 5.0) -@test du ≈ [11, -3, -7] - -@parameters σ(..) +@parameters (σ::Function)(..) eqs = [D(x) ~ σ(t - 1) * (y - x), D(y) ~ x * (ρ - z) - y, D(z) ~ x * y - β * z * κ] From 26292ffe6868f2649e178f68f412853f70726b39 Mon Sep 17 00:00:00 2001 From: vyudu Date: Fri, 18 Apr 2025 18:02:18 -0400 Subject: [PATCH 110/122] refactor: remove input_idxs output --- src/inputoutput.jl | 20 +++++++------- src/systems/clock_inference.jl | 13 +++++++++ src/systems/systems.jl | 19 ++++++++----- src/systems/systemstructure.jl | 49 ++++++++++++++-------------------- 4 files changed, 55 insertions(+), 46 deletions(-) diff --git a/src/inputoutput.jl b/src/inputoutput.jl index 375d493c35..3e78392b78 100644 --- a/src/inputoutput.jl +++ b/src/inputoutput.jl @@ -179,9 +179,6 @@ The return values also include the chosen state-realization (the remaining unkno If `disturbance_inputs` is an array of variables, the generated dynamics function will preserve any state and dynamics associated with disturbance inputs, but the disturbance inputs themselves will (by default) not be included as inputs to the generated function. The use case for this is to generate dynamics for state observers that estimate the influence of unmeasured disturbances, and thus require unknown variables for the disturbance model, but without disturbance inputs since the disturbances are not available for measurement. To add an input argument corresponding to the disturbance inputs, either include the disturbance inputs among the control inputs, or set `disturbance_argument=true`, in which case an additional input argument `w` is added to the generated function `(x,u,p,t,w)->rhs`. -!!! note "Un-simplified system" - This function expects `sys` to be un-simplified, i.e., `structural_simplify` or `@mtkbuild` should not be called on the system before passing it into this function. `generate_control_function` calls a special version of `structural_simplify` internally. - # Example ``` @@ -201,17 +198,17 @@ function generate_control_function(sys::AbstractODESystem, inputs = unbound_inpu eval_expression = false, eval_module = @__MODULE__, kwargs...) - isempty(inputs) && @warn("No unbound inputs were found in system.") + # Remove this when the ControlFunction gets merged. + if !iscomplete(sys) + error("A completed `ODESystem` is required. Call `complete` or `structural_simplify` on the system before creating the control function.") + end + isempty(inputs) && @warn("No unbound inputs were found in system.") if disturbance_inputs !== nothing # add to inputs for the purposes of io processing inputs = [inputs; disturbance_inputs] end - if !iscomplete(sys) - sys, _ = io_preprocessing(sys, inputs, []; simplify, kwargs...) - end - dvs = unknowns(sys) ps = parameters(sys; initial_parameters = true) ps = setdiff(ps, inputs) @@ -257,8 +254,11 @@ function generate_control_function(sys::AbstractODESystem, inputs = unbound_inpu (; f, dvs, ps, io_sys = sys) end -function inputs_to_parameters!(state::TransformationState, io) - check_bound = io === nothing +""" +Turn input variables into parameters of the system. +""" +function inputs_to_parameters!(state::TransformationState, inputsyms) + check_bound = inputsyms === nothing @unpack structure, fullvars, sys = state @unpack var_to_diff, graph, solvable_graph = structure @assert solvable_graph === nothing diff --git a/src/systems/clock_inference.jl b/src/systems/clock_inference.jl index b535773061..42fe28f7c7 100644 --- a/src/systems/clock_inference.jl +++ b/src/systems/clock_inference.jl @@ -1,7 +1,11 @@ struct ClockInference{S} + """Tearing state.""" ts::S + """The time domain (discrete clock, continuous) of each equation.""" eq_domain::Vector{TimeDomain} + """The output time domain (discrete clock, continuous) of each variable.""" var_domain::Vector{TimeDomain} + """The set of variables with concrete domains.""" inferred::BitSet end @@ -67,6 +71,9 @@ function substitute_sample_time(ex, dt) end end +""" +Update the equation-to-time domain mapping by inferring the time domain from the variables. +""" function infer_clocks!(ci::ClockInference) @unpack ts, eq_domain, var_domain, inferred = ci @unpack var_to_diff, graph = ts.structure @@ -132,6 +139,9 @@ function is_time_domain_conversion(v) input_timedomain(o) != output_timedomain(o) end +""" +For multi-clock systems, create a separate system for each clock in the system, along with associated equations. Return the updated tearing state, and the sets of clocked variables associated with each time domain. +""" function split_system(ci::ClockInference{S}) where {S} @unpack ts, eq_domain, var_domain, inferred = ci fullvars = get_fullvars(ts) @@ -143,11 +153,14 @@ function split_system(ci::ClockInference{S}) where {S} cid_to_eq = Vector{Int}[] var_to_cid = Vector{Int}(undef, ndsts(graph)) cid_to_var = Vector{Int}[] + # cid_counter = number of clocks cid_counter = Ref(0) for (i, d) in enumerate(eq_domain) cid = let cid_counter = cid_counter, id_to_clock = id_to_clock, continuous_id = continuous_id + # Fill the clock_to_id dict as you go, + # ContinuousClock() => 1, ... get!(clock_to_id, d) do cid = (cid_counter[] += 1) push!(id_to_clock, d) diff --git a/src/systems/systems.jl b/src/systems/systems.jl index 52f93afb9b..f3d1dba12c 100644 --- a/src/systems/systems.jl +++ b/src/systems/systems.jl @@ -27,12 +27,15 @@ topological sort of the observed equations in `sys`. + `sort_eqs=true` controls whether equations are sorted lexicographically before simplification or not. """ function structural_simplify( - sys::AbstractSystem, io = nothing; additional_passes = [], simplify = false, split = true, + sys::AbstractSystem; additional_passes = [], simplify = false, split = true, allow_symbolic = false, allow_parameter = true, conservative = false, fully_determined = true, + inputs = nothing, outputs = nothing, + disturbance_inputs = nothing, kwargs...) isscheduled(sys) && throw(RepeatedStructuralSimplificationError()) - newsys′ = __structural_simplify(sys, io; simplify, + newsys′ = __structural_simplify(sys; simplify, allow_symbolic, allow_parameter, conservative, fully_determined, + inputs, outputs, disturbance_inputs, kwargs...) if newsys′ isa Tuple @assert length(newsys′) == 2 @@ -70,8 +73,9 @@ function __structural_simplify(sys::SDESystem, args...; kwargs...) return __structural_simplify(ODESystem(sys), args...; kwargs...) end -function __structural_simplify( - sys::AbstractSystem, io = nothing; simplify = false, sort_eqs = true, +function __structural_simplify(sys::AbstractSystem; simplify = false, + inputs = Any[], outputs = Any[], + disturbance_inputs = Any[], kwargs...) sys = expand_connections(sys) state = TearingState(sys; sort_eqs) @@ -90,7 +94,8 @@ function __structural_simplify( end end if isempty(brown_vars) - return structural_simplify!(state, io; simplify, kwargs...) + return structural_simplify!( + state; simplify, inputs, outputs, disturbance_inputs, kwargs...) else Is = Int[] Js = Int[] @@ -122,8 +127,8 @@ function __structural_simplify( for (i, v) in enumerate(fullvars) if !iszero(new_idxs[i]) && invview(var_to_diff)[i] === nothing] - # TODO: IO is not handled. - ode_sys = structural_simplify(sys, io; simplify, kwargs...) + ode_sys = structural_simplify( + sys; simplify, inputs, outputs, disturbance_inputs, kwargs...) eqs = equations(ode_sys) sorted_g_rows = zeros(Num, length(eqs), size(g, 2)) for (i, eq) in enumerate(eqs) diff --git a/src/systems/systemstructure.jl b/src/systems/systemstructure.jl index e0feb0d34d..81bf605805 100644 --- a/src/systems/systemstructure.jl +++ b/src/systems/systemstructure.jl @@ -657,29 +657,21 @@ function Base.show(io::IO, mime::MIME"text/plain", ms::MatchedSystemStructure) printstyled(io, " SelectedState") end -# TODO: clean up -function merge_io(io, inputs) - isempty(inputs) && return io - if io === nothing - io = (inputs, []) - else - io = ([inputs; io[1]], io[2]) - end - return io -end - -function structural_simplify!(state::TearingState, io = nothing; simplify = false, +function structural_simplify!(state::TearingState; simplify = false, check_consistency = true, fully_determined = true, warn_initialize_determined = true, + inputs = nothing, outputs = nothing, + disturbance_inputs = nothing, kwargs...) if state.sys isa ODESystem ci = ModelingToolkit.ClockInference(state) ci = ModelingToolkit.infer_clocks!(ci) time_domains = merge(Dict(state.fullvars .=> ci.var_domain), Dict(default_toterm.(state.fullvars) .=> ci.var_domain)) - tss, inputs, continuous_id, id_to_clock = ModelingToolkit.split_system(ci) - cont_io = merge_io(io, inputs[continuous_id]) - sys, input_idxs = _structural_simplify!(tss[continuous_id], cont_io; simplify, + tss, clocked_inputs, continuous_id, id_to_clock = ModelingToolkit.split_system(ci) + cont_inputs = [inputs; clocked_inputs[continuous_id]] + sys = _structural_simplify!(tss[continuous_id]; simplify, check_consistency, fully_determined, + cont_inputs, outputs, disturbance_inputs, kwargs...) if length(tss) > 1 if continuous_id > 0 @@ -695,8 +687,9 @@ function structural_simplify!(state::TearingState, io = nothing; simplify = fals discrete_subsystems[i] = sys continue end - dist_io = merge_io(io, inputs[i]) - ss, = _structural_simplify!(state, dist_io; simplify, check_consistency, + disc_inputs = [inputs; clocked_inputs[i]] + ss, = _structural_simplify!(state; simplify, check_consistency, + inputs = disc_inputs, outputs, disturbance_inputs, fully_determined, kwargs...) append!(appended_parameters, inputs[i], unknowns(ss)) discrete_subsystems[i] = ss @@ -713,31 +706,29 @@ function structural_simplify!(state::TearingState, io = nothing; simplify = fals for sym in get_ps(sys)] @set! sys.ps = ps else - sys, input_idxs = _structural_simplify!(state, io; simplify, check_consistency, + sys = _structural_simplify!(state; simplify, check_consistency, + inputs, outputs, disturbance_inputs, fully_determined, kwargs...) end - has_io = io !== nothing - return has_io ? (sys, input_idxs) : sys + return sys end -function _structural_simplify!(state::TearingState, io; simplify = false, +function _structural_simplify!(state::TearingState; simplify = false, check_consistency = true, fully_determined = true, warn_initialize_determined = false, dummy_derivative = true, + inputs = nothing, outputs = nothing, + disturbance_inputs = nothing, kwargs...) if fully_determined isa Bool check_consistency &= fully_determined else check_consistency = true end - has_io = io !== nothing + has_io = inputs !== nothing || outputs !== nothing orig_inputs = Set() if has_io - ModelingToolkit.markio!(state, orig_inputs, io...) - end - if io !== nothing - state, input_idxs = ModelingToolkit.inputs_to_parameters!(state, io) - else - input_idxs = 0:-1 # Empty range + ModelingToolkit.markio!(state, orig_inputs, inputs, outputs) + state = ModelingToolkit.inputs_to_parameters!(state, inputs) end sys, mm = ModelingToolkit.alias_elimination!(state; kwargs...) if check_consistency @@ -761,5 +752,5 @@ function _structural_simplify!(state::TearingState, io; simplify = false, fullunknowns = [observables(sys); unknowns(sys)] @set! sys.observed = ModelingToolkit.topsort_equations(observed(sys), fullunknowns) - ModelingToolkit.invalidate_cache!(sys), input_idxs + ModelingToolkit.invalidate_cache!(sys) end From 21b231acbf4915d71e024c28072d1405b1de2df1 Mon Sep 17 00:00:00 2001 From: vyudu Date: Mon, 21 Apr 2025 14:42:16 -0400 Subject: [PATCH 111/122] refactor: use new `structural_simplify` in linearization --- src/linearization.jl | 41 ++++++++++++++++++++++------------- src/systems/abstractsystem.jl | 15 ------------- 2 files changed, 26 insertions(+), 30 deletions(-) diff --git a/src/linearization.jl b/src/linearization.jl index 77f4422b63..b197fb023e 100644 --- a/src/linearization.jl +++ b/src/linearization.jl @@ -58,9 +58,8 @@ function linearization_function(sys::AbstractSystem, inputs, outputs = mapreduce(vcat, outputs; init = []) do var symbolic_type(var) == ArraySymbolic() ? collect(var) : [var] end - ssys, diff_idxs, alge_idxs, input_idxs = io_preprocessing(sys, inputs, outputs; - simplify, - kwargs...) + ssys = structural_simplify(sys; inputs, outputs, simplify, kwargs...) + diff_idxs, alge_idxs = eq_idxs(ssys) if zero_dummy_der dummyder = setdiff(unknowns(ssys), unknowns(sys)) defs = Dict(x => 0.0 for x in dummyder) @@ -87,9 +86,9 @@ function linearization_function(sys::AbstractSystem, inputs, p = parameter_values(prob) t0 = current_time(prob) - inputvals = [p[idx] for idx in input_idxs] + inputvals = [prob.ps[i] for i in inputs] - hp_fun = let fun = h, setter = setp_oop(sys, input_idxs) + hp_fun = let fun = h, setter = setp_oop(sys, inputs) function hpf(du, input, u, p, t) p = setter(p, input) fun(du, u, p, t) @@ -113,7 +112,7 @@ function linearization_function(sys::AbstractSystem, inputs, # observed function is a `GeneratedFunctionWrapper` with iip component h_jac = PreparedJacobian{true}(h, similar(prob.u0, size(outputs)), autodiff, prob.u0, DI.Constant(p), DI.Constant(t0)) - pf_fun = let fun = prob.f, setter = setp_oop(sys, input_idxs) + pf_fun = let fun = prob.f, setter = setp_oop(sys, inputs) function pff(du, input, u, p, t) p = setter(p, input) SciMLBase.ParamJacobianWrapper(fun, t, u)(du, p) @@ -127,12 +126,24 @@ function linearization_function(sys::AbstractSystem, inputs, end lin_fun = LinearizationFunction( - diff_idxs, alge_idxs, input_idxs, length(unknowns(sys)), + diff_idxs, alge_idxs, length(unknowns(sys)), prob, h, u0 === nothing ? nothing : similar(u0), uf_jac, h_jac, pf_jac, hp_jac, initializealg, initialization_kwargs) return lin_fun, sys end +function eq_idxs(sys::AbstractSystem) + eqs = equations(sys) + alg_start_idx = findfirst(!isdiffeq, eqs) + if alg_start_idx === nothing + alg_start_idx = length(eqs) + 1 + end + diff_idxs = 1:(alg_start_idx - 1) + alge_idxs = alg_start_idx:length(eqs) + + diff_idxs, alge_idxs +end + """ $(TYPEDEF) @@ -192,7 +203,7 @@ A callable struct which linearizes a system. $(TYPEDFIELDS) """ struct LinearizationFunction{ - DI <: AbstractVector{Int}, AI <: AbstractVector{Int}, II, P <: ODEProblem, + DI <: AbstractVector{Int}, AI <: AbstractVector{Int}, I, P <: ODEProblem, H, C, J1, J2, J3, J4, IA <: SciMLBase.DAEInitializationAlgorithm, IK} """ The indexes of differential equations in the linearized system. @@ -206,7 +217,7 @@ struct LinearizationFunction{ The indexes of parameters in the linearized system which represent input variables. """ - input_idxs::II + inputs::I """ The number of unknowns in the linearized system. """ @@ -281,6 +292,7 @@ function (linfun::LinearizationFunction)(u, p, t) end fun = linfun.prob.f + input_vals = [linfun.prob.ps[i] for i in linfun.inputs] if u !== nothing # Handle systems without unknowns linfun.num_states == length(u) || error("Number of unknown variables ($(linfun.num_states)) does not match the number of input unknowns ($(length(u)))") @@ -294,15 +306,15 @@ function (linfun::LinearizationFunction)(u, p, t) end fg_xz = linfun.uf_jac(u, DI.Constant(p), DI.Constant(t)) h_xz = linfun.h_jac(u, DI.Constant(p), DI.Constant(t)) - fg_u = linfun.pf_jac([p[idx] for idx in linfun.input_idxs], + fg_u = linfun.pf_jac(input_vals, DI.Constant(u), DI.Constant(p), DI.Constant(t)) else linfun.num_states == 0 || error("Number of unknown variables (0) does not match the number of input unknowns ($(length(u)))") fg_xz = zeros(0, 0) - h_xz = fg_u = zeros(0, length(linfun.input_idxs)) + h_xz = fg_u = zeros(0, length(linfun.inputs)) end - h_u = linfun.hp_jac([p[idx] for idx in linfun.input_idxs], + h_u = linfun.hp_jac(input_vals, DI.Constant(u), DI.Constant(p), DI.Constant(t)) (f_x = fg_xz[linfun.diff_idxs, linfun.diff_idxs], f_z = fg_xz[linfun.diff_idxs, linfun.alge_idxs], @@ -482,9 +494,8 @@ function linearize_symbolic(sys::AbstractSystem, inputs, outputs; simplify = false, allow_input_derivatives = false, eval_expression = false, eval_module = @__MODULE__, kwargs...) - sys, diff_idxs, alge_idxs, input_idxs = io_preprocessing( - sys, inputs, outputs; simplify, - kwargs...) + sys = structural_simplify(sys; inputs, outputs, simplify, kwargs...) + diff_idxs, alge_idxs = eq_idxs(sys) sts = unknowns(sys) t = get_iv(sys) ps = parameters(sys; initial_parameters = true) diff --git a/src/systems/abstractsystem.jl b/src/systems/abstractsystem.jl index 5fdb7b4e25..1da2a8ff73 100644 --- a/src/systems/abstractsystem.jl +++ b/src/systems/abstractsystem.jl @@ -2486,21 +2486,6 @@ function eliminate_constants(sys::AbstractSystem) return sys end -function io_preprocessing(sys::AbstractSystem, inputs, - outputs; simplify = false, kwargs...) - sys, input_idxs = structural_simplify(sys, (inputs, outputs); simplify, kwargs...) - - eqs = equations(sys) - alg_start_idx = findfirst(!isdiffeq, eqs) - if alg_start_idx === nothing - alg_start_idx = length(eqs) + 1 - end - diff_idxs = 1:(alg_start_idx - 1) - alge_idxs = alg_start_idx:length(eqs) - - sys, diff_idxs, alge_idxs, input_idxs -end - @latexrecipe function f(sys::AbstractSystem) return latexify(equations(sys)) end From 1d2a6049e13bdcd984cefe6db4377557999412d3 Mon Sep 17 00:00:00 2001 From: vyudu Date: Mon, 21 Apr 2025 23:44:47 -0400 Subject: [PATCH 112/122] fix: fix linearization tests --- src/inputoutput.jl | 16 +++++++--------- src/linearization.jl | 2 +- src/systems/diffeqs/odesystem.jl | 10 +++++----- src/systems/systems.jl | 4 ++-- src/systems/systemstructure.jl | 10 +++++----- test/downstream/linearize.jl | 6 +++--- 6 files changed, 23 insertions(+), 25 deletions(-) diff --git a/src/inputoutput.jl b/src/inputoutput.jl index 3e78392b78..68c6cf7ff9 100644 --- a/src/inputoutput.jl +++ b/src/inputoutput.jl @@ -163,7 +163,7 @@ has_var(ex, x) = x ∈ Set(get_variables(ex)) (f_oop, f_ip), x_sym, p_sym, io_sys = generate_control_function( sys::AbstractODESystem, inputs = unbound_inputs(sys), - disturbance_inputs = nothing; + disturbance_inputs = Any[]; implicit_dae = false, simplify = false, ) @@ -287,7 +287,7 @@ function inputs_to_parameters!(state::TransformationState, inputsyms) push!(new_fullvars, v) end end - ninputs == 0 && return (state, 1:0) + ninputs == 0 && return state nvars = ndsts(graph) - ninputs new_graph = BipartiteGraph(nsrcs(graph), nvars, Val(false)) @@ -316,14 +316,13 @@ function inputs_to_parameters!(state::TransformationState, inputsyms) @set! sys.unknowns = setdiff(unknowns(sys), keys(input_to_parameters)) ps = parameters(sys) - if io !== nothing - inputs, = io + if inputsyms !== nothing # Change order of new parameters to correspond to user-provided order in argument `inputs` d = Dict{Any, Int}() for (i, inp) in enumerate(new_parameters) d[inp] = i end - permutation = [d[i] for i in inputs] + permutation = [d[i] for i in inputsyms] new_parameters = new_parameters[permutation] end @@ -332,8 +331,7 @@ function inputs_to_parameters!(state::TransformationState, inputsyms) @set! state.sys = sys @set! state.fullvars = new_fullvars @set! state.structure = structure - base_params = length(ps) - return state, (base_params + 1):(base_params + length(new_parameters)) # (1:length(new_parameters)) .+ base_params + return state end """ @@ -359,7 +357,7 @@ function get_disturbance_system(dist::DisturbanceModel{<:ODESystem}) end """ - (f_oop, f_ip), augmented_sys, dvs, p = add_input_disturbance(sys, dist::DisturbanceModel, inputs = nothing) + (f_oop, f_ip), augmented_sys, dvs, p = add_input_disturbance(sys, dist::DisturbanceModel, inputs = Any[]) Add a model of an unmeasured disturbance to `sys`. The disturbance model is an instance of [`DisturbanceModel`](@ref). @@ -408,7 +406,7 @@ model_outputs = [model.inertia1.w, model.inertia2.w, model.inertia1.phi, model.i `f_oop` will have an extra state corresponding to the integrator in the disturbance model. This state will not be affected by any input, but will affect the dynamics from where it enters, in this case it will affect additively from `model.torque.tau.u`. """ -function add_input_disturbance(sys, dist::DisturbanceModel, inputs = nothing; kwargs...) +function add_input_disturbance(sys, dist::DisturbanceModel, inputs = Any[]; kwargs...) t = get_iv(sys) @variables d(t)=0 [disturbance = true] @variables u(t)=0 [input = true] # New system input diff --git a/src/linearization.jl b/src/linearization.jl index b197fb023e..451f262476 100644 --- a/src/linearization.jl +++ b/src/linearization.jl @@ -126,7 +126,7 @@ function linearization_function(sys::AbstractSystem, inputs, end lin_fun = LinearizationFunction( - diff_idxs, alge_idxs, length(unknowns(sys)), + diff_idxs, alge_idxs, inputs, length(unknowns(sys)), prob, h, u0 === nothing ? nothing : similar(u0), uf_jac, h_jac, pf_jac, hp_jac, initializealg, initialization_kwargs) return lin_fun, sys diff --git a/src/systems/diffeqs/odesystem.jl b/src/systems/diffeqs/odesystem.jl index 4a13d7ccf1..18208cb3af 100644 --- a/src/systems/diffeqs/odesystem.jl +++ b/src/systems/diffeqs/odesystem.jl @@ -470,7 +470,7 @@ Generates a function that computes the observed value(s) `ts` in the system `sys - `eval_expression = false`: If true and `expression = false`, evaluates the returned function in the module `eval_module` - `output_type = Array` the type of the array generated by a out-of-place vector-valued function - `param_only = false` if true, only allow the generated function to access system parameters -- `inputs = nothing` additinoal symbolic variables that should be provided to the generated function +- `inputs = Any[]` additional symbolic variables that should be provided to the generated function - `checkbounds = true` checks bounds if true when destructuring parameters - `op = Operator` sets the recursion terminator for the walk done by `vars` to identify the variables that appear in `ts`. See the documentation for `vars` for more detail. - `throw = true` if true, throw an error when generating a function for `ts` that reference variables that do not exist. @@ -500,8 +500,8 @@ For example, a function `g(op, unknowns, p..., inputs, t)` will be the in-place an array of inputs `inputs` is given, and `param_only` is false for a time-dependent system. """ function build_explicit_observed_function(sys, ts; - inputs = nothing, - disturbance_inputs = nothing, + inputs = Any[], + disturbance_inputs = Any[], disturbance_argument = false, expression = false, eval_expression = false, @@ -574,13 +574,13 @@ function build_explicit_observed_function(sys, ts; else (unknowns(sys),) end - if inputs === nothing + if isempty(inputs) inputs = () else ps = setdiff(ps, inputs) # Inputs have been converted to parameters by io_preprocessing, remove those from the parameter list inputs = (inputs,) end - if disturbance_inputs !== nothing + if !isempty(disturbance_inputs) # Disturbance inputs may or may not be included as inputs, depending on disturbance_argument ps = setdiff(ps, disturbance_inputs) end diff --git a/src/systems/systems.jl b/src/systems/systems.jl index f3d1dba12c..270a3f567f 100644 --- a/src/systems/systems.jl +++ b/src/systems/systems.jl @@ -29,8 +29,8 @@ topological sort of the observed equations in `sys`. function structural_simplify( sys::AbstractSystem; additional_passes = [], simplify = false, split = true, allow_symbolic = false, allow_parameter = true, conservative = false, fully_determined = true, - inputs = nothing, outputs = nothing, - disturbance_inputs = nothing, + inputs = Any[], outputs = Any[], + disturbance_inputs = Any[], kwargs...) isscheduled(sys) && throw(RepeatedStructuralSimplificationError()) newsys′ = __structural_simplify(sys; simplify, diff --git a/src/systems/systemstructure.jl b/src/systems/systemstructure.jl index 81bf605805..9f848314cd 100644 --- a/src/systems/systemstructure.jl +++ b/src/systems/systemstructure.jl @@ -659,8 +659,8 @@ end function structural_simplify!(state::TearingState; simplify = false, check_consistency = true, fully_determined = true, warn_initialize_determined = true, - inputs = nothing, outputs = nothing, - disturbance_inputs = nothing, + inputs = Any[], outputs = Any[], + disturbance_inputs = Any[], kwargs...) if state.sys isa ODESystem ci = ModelingToolkit.ClockInference(state) @@ -671,7 +671,7 @@ function structural_simplify!(state::TearingState; simplify = false, cont_inputs = [inputs; clocked_inputs[continuous_id]] sys = _structural_simplify!(tss[continuous_id]; simplify, check_consistency, fully_determined, - cont_inputs, outputs, disturbance_inputs, + inputs = cont_inputs, outputs, disturbance_inputs, kwargs...) if length(tss) > 1 if continuous_id > 0 @@ -716,8 +716,8 @@ end function _structural_simplify!(state::TearingState; simplify = false, check_consistency = true, fully_determined = true, warn_initialize_determined = false, dummy_derivative = true, - inputs = nothing, outputs = nothing, - disturbance_inputs = nothing, + inputs = Any[], outputs = Any[], + disturbance_inputs = Any[], kwargs...) if fully_determined isa Bool check_consistency &= fully_determined diff --git a/test/downstream/linearize.jl b/test/downstream/linearize.jl index d82bd696dd..20a9d317e8 100644 --- a/test/downstream/linearize.jl +++ b/test/downstream/linearize.jl @@ -87,10 +87,10 @@ connections = [f.y ~ c.r # filtered reference to controller reference @named cl = ODESystem(connections, t, systems = [f, c, p]) -lsys0, ssys = linearize(cl, [f.u], [p.x]) +lsys0, ssys = linearize(cl) desired_order = [f.x, p.x] lsys = ModelingToolkit.reorder_unknowns(lsys0, unknowns(ssys), desired_order) -lsys1, ssys = linearize(cl, [f.u], [p.x]; autodiff = AutoFiniteDiff()) +lsys1, ssys = linearize(cl; autodiff = AutoFiniteDiff()) lsys2 = ModelingToolkit.reorder_unknowns(lsys1, unknowns(ssys), desired_order) @test lsys.A == lsys2.A == [-2 0; 1 -2] @@ -266,7 +266,7 @@ closed_loop = ODESystem(connections, t, systems = [model, pid, filt, sensor, r, filt.xd => 0.0 ]) -@test_nowarn linearize(closed_loop, :r, :y; warn_empty_op = false) +@test_nowarn linearize(closed_loop; warn_empty_op = false) # https://discourse.julialang.org/t/mtk-change-in-linearize/115760/3 @mtkmodel Tank_noi begin From c124625d2af11bf91ef62d66ba4ac9d5159c22aa Mon Sep 17 00:00:00 2001 From: vyudu Date: Thu, 24 Apr 2025 15:37:58 -0400 Subject: [PATCH 113/122] test: test updates --- src/systems/systems.jl | 4 +--- test/clock.jl | 7 ++++--- test/code_generation.jl | 2 +- test/input_output_handling.jl | 8 ++++---- test/odesystem.jl | 6 +++--- test/reduction.jl | 2 +- 6 files changed, 14 insertions(+), 15 deletions(-) diff --git a/src/systems/systems.jl b/src/systems/systems.jl index 270a3f567f..a326703262 100644 --- a/src/systems/systems.jl +++ b/src/systems/systems.jl @@ -17,13 +17,11 @@ $(SIGNATURES) Structurally simplify algebraic equations in a system and compute the topological sort of the observed equations in `sys`. -### Optional Arguments: -+ optional argument `io` may take a tuple `(inputs, outputs)`. This will convert all `inputs` to parameters and allow them to be unconnected, i.e., simplification will allow models where `n_unknowns = n_equations - n_inputs`. - ### Optional Keyword Arguments: + When `simplify=true`, the `simplify` function will be applied during the tearing process. + `allow_symbolic=false`, `allow_parameter=true`, and `conservative=false` limit the coefficient types during tearing. In particular, `conservative=true` limits tearing to only solve for trivial linear systems where the coefficient has the absolute value of ``1``. + `fully_determined=true` controls whether or not an error will be thrown if the number of equations don't match the number of inputs, outputs, and equations. ++ `inputs`, `outputs` and `disturbance_inputs` are passed as keyword arguments.` All inputs` get converted to parameters and are allowed to be unconnected, allowing models where `n_unknowns = n_equations - n_inputs`. + `sort_eqs=true` controls whether equations are sorted lexicographically before simplification or not. """ function structural_simplify( diff --git a/test/clock.jl b/test/clock.jl index c6051a52a8..c4c64dbf90 100644 --- a/test/clock.jl +++ b/test/clock.jl @@ -65,10 +65,11 @@ By inference: ci, varmap = infer_clocks(sys) eqmap = ci.eq_domain tss, inputs, continuous_id = ModelingToolkit.split_system(deepcopy(ci)) -sss, = ModelingToolkit._structural_simplify!( - deepcopy(tss[continuous_id]), (inputs[continuous_id], ())) +sss = ModelingToolkit._structural_simplify!( + deepcopy(tss[continuous_id]), inputs = inputs[continuous_id], outputs = []) @test equations(sss) == [D(x) ~ u - x] -sss, = ModelingToolkit._structural_simplify!(deepcopy(tss[1]), (inputs[1], ())) +sss = ModelingToolkit._structural_simplify!( + deepcopy(tss[1]), inputs = inputs[1], outputs = []) @test isempty(equations(sss)) d = Clock(dt) k = ShiftIndex(d) diff --git a/test/code_generation.jl b/test/code_generation.jl index cf3d660b81..3ef5ac3e11 100644 --- a/test/code_generation.jl +++ b/test/code_generation.jl @@ -70,7 +70,7 @@ end @parameters p[1:2] (f::Function)(..) @named sys = ODESystem( [D(x[0]) ~ p[1] * x[0] + x[2], D(x[1]) ~ p[2] * f(x) + x[2]], t) - sys, = structural_simplify(sys, ([x[2]], [])) + sys, = structural_simplify(sys, inputs = [x[2]], outputs = []) @test is_parameter(sys, x[2]) prob = ODEProblem(sys, [x[0] => 1.0, x[1] => 1.0], (0.0, 1.0), [p => ones(2), f => sum, x[2] => 2.0]) diff --git a/test/input_output_handling.jl b/test/input_output_handling.jl index 2c16c89320..bb1e6156f2 100644 --- a/test/input_output_handling.jl +++ b/test/input_output_handling.jl @@ -50,7 +50,7 @@ end @test !is_bound(sys31, sys1.v[2]) # simplification turns input variables into parameters -ssys, _ = structural_simplify(sys, ([u], [])) +ssys = structural_simplify(sys, inputs = [u], outputs = []) @test ModelingToolkit.isparameter(unbound_inputs(ssys)[]) @test !is_bound(ssys, u) @test u ∈ Set(unbound_inputs(ssys)) @@ -281,7 +281,7 @@ i = findfirst(isequal(u[1]), out) @variables x(t) u(t) [input = true] eqs = [D(x) ~ u] @named sys = ODESystem(eqs, t) -@test_nowarn structural_simplify(sys, ([u], [])) +@test_nowarn structural_simplify(sys, inputs = [u], outputs = []) #= ## Disturbance input handling @@ -366,9 +366,9 @@ eqs = [D(y₁) ~ -k₁ * y₁ + k₃ * y₂ * y₃ + u1 @named sys = ODESystem(eqs, t) m_inputs = [u[1], u[2]] m_outputs = [y₂] -sys_simp, input_idxs = structural_simplify(sys, (; inputs = m_inputs, outputs = m_outputs)) +sys_simp = structural_simplify(sys, inputs = m_inputs, outputs = m_outputs) @test isequal(unknowns(sys_simp), collect(x[1:2])) -@test length(input_idxs) == 2 +@test length(inputs(sys_simp)) == 2 # https://github.com/SciML/ModelingToolkit.jl/issues/1577 @named c = Constant(; k = 2) diff --git a/test/odesystem.jl b/test/odesystem.jl index 8411cdd55e..c80efa3157 100644 --- a/test/odesystem.jl +++ b/test/odesystem.jl @@ -1283,11 +1283,11 @@ end @named sys = ODESystem( [D(u) ~ (sum(u) + sum(x) + sum(p) + sum(o)) * x, o ~ prod(u) * x], t, [u..., x..., o...], [p...]) - sys1, = structural_simplify(sys, ([x...], [])) + sys1 = structural_simplify(sys, inputs = [x...], outputs = []) fn1, = ModelingToolkit.generate_function(sys1; expression = Val{false}) ps = MTKParameters(sys1, [x => 2ones(2), p => 3ones(2, 2)]) @test_nowarn fn1(ones(4), ps, 4.0) - sys2, = structural_simplify(sys, ([x...], []); split = false) + sys2 = structural_simplify(sys, inputs = [x...], outputs = [], split = false) fn2, = ModelingToolkit.generate_function(sys2; expression = Val{false}) ps = zeros(8) setp(sys2, x)(ps, 2ones(2)) @@ -1396,7 +1396,7 @@ end o[2] ~ sum(p) * sum(x)] @named sys = ODESystem(eqs, t, [u..., x..., o], [p...]) - sys1, = structural_simplify(sys, ([x...], [o...]), split = false) + sys1 = structural_simplify(sys, inputs = [x...], outputs = [o...], split = false) @test_nowarn ModelingToolkit.build_explicit_observed_function(sys1, u; inputs = [x...]) diff --git a/test/reduction.jl b/test/reduction.jl index fa9029a652..11992d29d6 100644 --- a/test/reduction.jl +++ b/test/reduction.jl @@ -233,7 +233,7 @@ eqs = [D(x) ~ σ * (y - x) u ~ z + a] lorenz1 = ODESystem(eqs, t, name = :lorenz1) -lorenz1_reduced, _ = structural_simplify(lorenz1, ([z], [])) +lorenz1_reduced, _ = structural_simplify(lorenz1, inputs = [z], outputs = []) @test z in Set(parameters(lorenz1_reduced)) # #2064 From 70e25e83ac05e49b21b56bad2498d1e5567d68d0 Mon Sep 17 00:00:00 2001 From: vyudu Date: Thu, 24 Apr 2025 17:35:48 -0400 Subject: [PATCH 114/122] fix input output tests --- src/inputoutput.jl | 13 +++++++------ src/linearization.jl | 9 ++++++++- src/systems/systemstructure.jl | 4 ++-- src/variables.jl | 2 ++ test/input_output_handling.jl | 30 +++++++++++++++++------------- test/reduction.jl | 2 +- 6 files changed, 37 insertions(+), 23 deletions(-) diff --git a/src/inputoutput.jl b/src/inputoutput.jl index 68c6cf7ff9..c73d151a9f 100644 --- a/src/inputoutput.jl +++ b/src/inputoutput.jl @@ -197,10 +197,10 @@ function generate_control_function(sys::AbstractODESystem, inputs = unbound_inpu simplify = false, eval_expression = false, eval_module = @__MODULE__, + check_simplified = true, kwargs...) - # Remove this when the ControlFunction gets merged. - if !iscomplete(sys) + if check_simplified && !iscomplete(sys) error("A completed `ODESystem` is required. Call `complete` or `structural_simplify` on the system before creating the control function.") end isempty(inputs) && @warn("No unbound inputs were found in system.") @@ -257,7 +257,7 @@ end """ Turn input variables into parameters of the system. """ -function inputs_to_parameters!(state::TransformationState, inputsyms) +function inputs_to_parameters!(state::TransformationState, inputsyms; is_disturbance = false) check_bound = inputsyms === nothing @unpack structure, fullvars, sys = state @unpack var_to_diff, graph, solvable_graph = structure @@ -412,7 +412,7 @@ function add_input_disturbance(sys, dist::DisturbanceModel, inputs = Any[]; kwar @variables u(t)=0 [input = true] # New system input dsys = get_disturbance_system(dist) - if inputs === nothing + if isempty(inputs) all_inputs = [u] else i = findfirst(isequal(dist.input), inputs) @@ -427,8 +427,9 @@ function add_input_disturbance(sys, dist::DisturbanceModel, inputs = Any[]; kwar dist.input ~ u + dsys.output.u[1]] augmented_sys = ODESystem(eqs, t, systems = [dsys], name = gensym(:outer)) augmented_sys = extend(augmented_sys, sys) + ssys = structural_simplify(augmented_sys, inputs = all_inputs, disturbance_inputs = [d]) - f, dvs, p, io_sys = generate_control_function(augmented_sys, all_inputs, - [d]; kwargs...) + f, dvs, p, io_sys = generate_control_function(ssys, all_inputs, + [d]; check_simplified = false, kwargs...) f, augmented_sys, dvs, p, io_sys end diff --git a/src/linearization.jl b/src/linearization.jl index 451f262476..efbb3c3ab4 100644 --- a/src/linearization.jl +++ b/src/linearization.jl @@ -556,10 +556,11 @@ function linearize_symbolic(sys::AbstractSystem, inputs, (; A, B, C, D, f_x, f_z, g_x, g_z, f_u, g_u, h_x, h_z, h_u), sys end -function markio!(state, orig_inputs, inputs, outputs; check = true) +function markio!(state, orig_inputs, inputs, outputs, disturbances; check = true) fullvars = get_fullvars(state) inputset = Dict{Any, Bool}(i => false for i in inputs) outputset = Dict{Any, Bool}(o => false for o in outputs) + disturbanceset = Dict{Any, Bool}(d => false for d in disturbances) for (i, v) in enumerate(fullvars) if v in keys(inputset) if v in keys(outputset) @@ -581,6 +582,12 @@ function markio!(state, orig_inputs, inputs, outputs; check = true) v = setio(v, false, false) fullvars[i] = v end + + if v in keys(disturbanceset) + v = setio(v, true, false) + v = setdisturbance(v, true) + fullvars[i] = v + end end if check ikeys = keys(filter(!last, inputset)) diff --git a/src/systems/systemstructure.jl b/src/systems/systemstructure.jl index 9f848314cd..4daf4a29cc 100644 --- a/src/systems/systemstructure.jl +++ b/src/systems/systemstructure.jl @@ -727,8 +727,8 @@ function _structural_simplify!(state::TearingState; simplify = false, has_io = inputs !== nothing || outputs !== nothing orig_inputs = Set() if has_io - ModelingToolkit.markio!(state, orig_inputs, inputs, outputs) - state = ModelingToolkit.inputs_to_parameters!(state, inputs) + ModelingToolkit.markio!(state, orig_inputs, inputs, outputs, disturbance_inputs) + state = ModelingToolkit.inputs_to_parameters!(state, [inputs; disturbance_inputs]) end sys, mm = ModelingToolkit.alias_elimination!(state; kwargs...) if check_consistency diff --git a/src/variables.jl b/src/variables.jl index 510bd5c28d..eb2e54a268 100644 --- a/src/variables.jl +++ b/src/variables.jl @@ -354,6 +354,8 @@ function isdisturbance(x) Symbolics.getmetadata(x, VariableDisturbance, false) end +setdisturbance(x, v) = setmetadata(x, VariableDisturbance, v) + function disturbances(sys) [filter(isdisturbance, unknowns(sys)); filter(isdisturbance, parameters(sys))] end diff --git a/test/input_output_handling.jl b/test/input_output_handling.jl index bb1e6156f2..732fde5e3f 100644 --- a/test/input_output_handling.jl +++ b/test/input_output_handling.jl @@ -7,10 +7,10 @@ using ModelingToolkit: t_nounits as t, D_nounits as D @variables xx(t) some_input(t) [input = true] eqs = [D(xx) ~ some_input] @named model = ODESystem(eqs, t) -@test_throws ExtraVariablesSystemException structural_simplify(model, ((), ())) +@test_throws ExtraVariablesSystemException structural_simplify(model) if VERSION >= v"1.8" err = "In particular, the unset input(s) are:\n some_input(t)" - @test_throws err structural_simplify(model, ((), ())) + @test_throws err structural_simplify(model) end # Test input handling @@ -88,7 +88,7 @@ fsys4 = flatten(sys4) @variables x(t) y(t) [output = true] @test isoutput(y) @named sys = ODESystem([D(x) ~ -x, y ~ x], t) # both y and x are unbound -syss = structural_simplify(sys) # This makes y an observed variable +syss = structural_simplify(sys, outputs = [y]) # This makes y an observed variable @named sys2 = ODESystem([D(x) ~ -sys.x, y ~ sys.y], t, systems = [sys]) @@ -106,7 +106,7 @@ syss = structural_simplify(sys) # This makes y an observed variable @test isequal(unbound_outputs(sys2), [y]) @test isequal(bound_outputs(sys2), [sys.y]) -syss = structural_simplify(sys2) +syss = structural_simplify(sys2, outputs = [sys.y]) @test !is_bound(syss, y) @test !is_bound(syss, x) @@ -165,6 +165,7 @@ end ] @named sys = ODESystem(eqs, t) + sys = structural_simplify(sys, inputs = [u]) f, dvs, ps, io_sys = ModelingToolkit.generate_control_function(sys; simplify, split) @test isequal(dvs[], x) @@ -182,8 +183,8 @@ end ] @named sys = ODESystem(eqs, t) - f, dvs, ps, io_sys = ModelingToolkit.generate_control_function( - sys, [u], [d]; simplify, split) + sys = structural_simplify(sys, inputs = [u], disturbance_inputs = [d]) + f, dvs, ps, io_sys = ModelingToolkit.generate_control_function(sys; simplify, split) @test isequal(dvs[], x) @test isempty(ps) @@ -200,8 +201,9 @@ end ] @named sys = ODESystem(eqs, t) + sys = structural_simplify(sys, inputs = [u], disturbance_inputs = [d]) f, dvs, ps, io_sys = ModelingToolkit.generate_control_function( - sys, [u], [d]; simplify, split, disturbance_argument = true) + sys; simplify, split, disturbance_argument = true) @test isequal(dvs[], x) @test isempty(ps) @@ -265,9 +267,9 @@ eqs = [connect_sd(sd, mass1, mass2) @named _model = ODESystem(eqs, t) @named model = compose(_model, mass1, mass2, sd); +model = structural_simplify(model, inputs = [u]) f, dvs, ps, io_sys = ModelingToolkit.generate_control_function(model, simplify = true) @test length(dvs) == 4 -@test length(ps) == length(parameters(model)) p = MTKParameters(io_sys, [io_sys.u => NaN]) x = ModelingToolkit.varmap_to_vars( merge(ModelingToolkit.defaults(model), @@ -389,7 +391,7 @@ sys = structural_simplify(model) ## Disturbance models when plant has multiple inputs using ModelingToolkit, LinearAlgebra -using ModelingToolkit: DisturbanceModel, io_preprocessing, get_iv, get_disturbance_system +using ModelingToolkit: DisturbanceModel, get_iv, get_disturbance_system using ModelingToolkitStandardLibrary.Blocks A, C = [randn(2, 2) for i in 1:2] B = [1.0 0; 0 1.0] @@ -433,6 +435,7 @@ matrices = ModelingToolkit.reorder_unknowns( ] @named sys = ODESystem(eqs, t) + sys = structural_simplify(sys, inputs = [u]) (; io_sys,) = ModelingToolkit.generate_control_function(sys, simplify = true) obsfn = ModelingToolkit.build_explicit_observed_function( io_sys, [x + u * t]; inputs = [u]) @@ -444,10 +447,10 @@ end @constants c = 2.0 @variables x(t) eqs = [D(x) ~ c * x] - @named sys = ODESystem(eqs, t, [x], []) + @mtkbuild sys = ODESystem(eqs, t, [x], []) - f, dvs, ps, io_sys = ModelingToolkit.generate_control_function(sys, simplify = true) - @test f([0.5], nothing, MTKParameters(io_sys, []), 0.0) ≈ [1.0] + f, dvs, ps, io_sys = ModelingToolkit.generate_control_function(sys) + @test f[1]([0.5], nothing, MTKParameters(io_sys, []), 0.0) ≈ [1.0] end @testset "With callable symbolic" begin @@ -455,7 +458,8 @@ end @parameters p(::Real) = (x -> 2x) eqs = [D(x) ~ -x + p(u)] @named sys = ODESystem(eqs, t) - f, dvs, ps, io_sys = ModelingToolkit.generate_control_function(sys, simplify = true) + sys = structural_simplify(sys, inputs = [u]) + f, dvs, ps, io_sys = ModelingToolkit.generate_control_function(sys) p = MTKParameters(io_sys, []) u = [1.0] x = [1.0] diff --git a/test/reduction.jl b/test/reduction.jl index 11992d29d6..9a515614d5 100644 --- a/test/reduction.jl +++ b/test/reduction.jl @@ -233,7 +233,7 @@ eqs = [D(x) ~ σ * (y - x) u ~ z + a] lorenz1 = ODESystem(eqs, t, name = :lorenz1) -lorenz1_reduced, _ = structural_simplify(lorenz1, inputs = [z], outputs = []) +lorenz1_reduced = structural_simplify(lorenz1, inputs = [z], outputs = []) @test z in Set(parameters(lorenz1_reduced)) # #2064 From 48ae093598055e9ea1ba1176d86342cc0ef5b3f6 Mon Sep 17 00:00:00 2001 From: vyudu Date: Fri, 25 Apr 2025 11:20:18 -0400 Subject: [PATCH 115/122] more test fixes --- src/inputoutput.jl | 2 +- test/code_generation.jl | 2 +- test/extensions/test_infiniteopt.jl | 3 +-- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/inputoutput.jl b/src/inputoutput.jl index c73d151a9f..c4037c20ca 100644 --- a/src/inputoutput.jl +++ b/src/inputoutput.jl @@ -163,7 +163,7 @@ has_var(ex, x) = x ∈ Set(get_variables(ex)) (f_oop, f_ip), x_sym, p_sym, io_sys = generate_control_function( sys::AbstractODESystem, inputs = unbound_inputs(sys), - disturbance_inputs = Any[]; + disturbance_inputs = disturbances(sys); implicit_dae = false, simplify = false, ) diff --git a/test/code_generation.jl b/test/code_generation.jl index 3ef5ac3e11..2fbf8f13d7 100644 --- a/test/code_generation.jl +++ b/test/code_generation.jl @@ -70,7 +70,7 @@ end @parameters p[1:2] (f::Function)(..) @named sys = ODESystem( [D(x[0]) ~ p[1] * x[0] + x[2], D(x[1]) ~ p[2] * f(x) + x[2]], t) - sys, = structural_simplify(sys, inputs = [x[2]], outputs = []) + sys = structural_simplify(sys, inputs = [x[2]], outputs = []) @test is_parameter(sys, x[2]) prob = ODEProblem(sys, [x[0] => 1.0, x[1] => 1.0], (0.0, 1.0), [p => ones(2), f => sum, x[2] => 2.0]) diff --git a/test/extensions/test_infiniteopt.jl b/test/extensions/test_infiniteopt.jl index 833b9f3275..a891311b46 100644 --- a/test/extensions/test_infiniteopt.jl +++ b/test/extensions/test_infiniteopt.jl @@ -24,8 +24,7 @@ end model = complete(model) inputs = [model.τ] outputs = [model.y] -model, _ = structural_simplify(model, (inputs, outputs)) - +model = structural_simplify(model; inputs, outputs) f, dvs, psym, io_sys = ModelingToolkit.generate_control_function( model, split = false) From 02343152984378ec1560c88003b62674171cc606 Mon Sep 17 00:00:00 2001 From: vyudu Date: Mon, 28 Apr 2025 16:12:50 -0400 Subject: [PATCH 116/122] fix: fix sort_eqs and check distrubances in markio --- src/inputoutput.jl | 15 ++------------- src/linearization.jl | 28 ++++++++++++++++++---------- src/systems/systems.jl | 1 + src/systems/systemstructure.jl | 3 ++- 4 files changed, 23 insertions(+), 24 deletions(-) diff --git a/src/inputoutput.jl b/src/inputoutput.jl index c4037c20ca..18d7830cba 100644 --- a/src/inputoutput.jl +++ b/src/inputoutput.jl @@ -257,7 +257,7 @@ end """ Turn input variables into parameters of the system. """ -function inputs_to_parameters!(state::TransformationState, inputsyms; is_disturbance = false) +function inputs_to_parameters!(state::TransformationState, inputsyms) check_bound = inputsyms === nothing @unpack structure, fullvars, sys = state @unpack var_to_diff, graph, solvable_graph = structure @@ -316,18 +316,7 @@ function inputs_to_parameters!(state::TransformationState, inputsyms; is_disturb @set! sys.unknowns = setdiff(unknowns(sys), keys(input_to_parameters)) ps = parameters(sys) - if inputsyms !== nothing - # Change order of new parameters to correspond to user-provided order in argument `inputs` - d = Dict{Any, Int}() - for (i, inp) in enumerate(new_parameters) - d[inp] = i - end - permutation = [d[i] for i in inputsyms] - new_parameters = new_parameters[permutation] - end - @set! sys.ps = [ps; new_parameters] - @set! state.sys = sys @set! state.fullvars = new_fullvars @set! state.structure = structure @@ -430,6 +419,6 @@ function add_input_disturbance(sys, dist::DisturbanceModel, inputs = Any[]; kwar ssys = structural_simplify(augmented_sys, inputs = all_inputs, disturbance_inputs = [d]) f, dvs, p, io_sys = generate_control_function(ssys, all_inputs, - [d]; check_simplified = false, kwargs...) + [d]; kwargs...) f, augmented_sys, dvs, p, io_sys end diff --git a/src/linearization.jl b/src/linearization.jl index efbb3c3ab4..12340435b8 100644 --- a/src/linearization.jl +++ b/src/linearization.jl @@ -132,14 +132,13 @@ function linearization_function(sys::AbstractSystem, inputs, return lin_fun, sys end +""" +Return the set of indexes of differential equations and algebraic equations in the simplified system. +""" function eq_idxs(sys::AbstractSystem) eqs = equations(sys) - alg_start_idx = findfirst(!isdiffeq, eqs) - if alg_start_idx === nothing - alg_start_idx = length(eqs) + 1 - end - diff_idxs = 1:(alg_start_idx - 1) - alge_idxs = alg_start_idx:length(eqs) + alge_idxs = findall(!isdiffeq, eqs) + diff_idxs = setdiff(1:length(eqs), alge_idxs) diff_idxs, alge_idxs end @@ -556,6 +555,9 @@ function linearize_symbolic(sys::AbstractSystem, inputs, (; A, B, C, D, f_x, f_z, g_x, g_z, f_u, g_u, h_x, h_z, h_u), sys end +""" +Modify the variable metadata of system variables to indicate which ones are inputs, outputs, and disturbances. Needed for `inputs`, `outputs`, `disturbances`, `unbound_inputs`, `unbound_outputs` to return the proper subsets. +""" function markio!(state, orig_inputs, inputs, outputs, disturbances; check = true) fullvars = get_fullvars(state) inputset = Dict{Any, Bool}(i => false for i in inputs) @@ -586,6 +588,7 @@ function markio!(state, orig_inputs, inputs, outputs, disturbances; check = true if v in keys(disturbanceset) v = setio(v, true, false) v = setdisturbance(v, true) + disturbanceset[v] = true fullvars[i] = v end end @@ -596,11 +599,16 @@ function markio!(state, orig_inputs, inputs, outputs, disturbances; check = true "Some specified inputs were not found in system. The following variables were not found ", ikeys) end - end - check && (all(values(outputset)) || - error( - "Some specified outputs were not found in system. The following Dict indicates the found variables ", + dkeys = keys(filter(!last, disturbanceset)) + if !isempty(dkeys) + error( + "Specified disturbance inputs were not found in system. The following variables were not found ", + ikeys) + end + (all(values(outputset)) || error( + "Some specified outputs were not found in system. The following Dict indicates the found variables ", outputset)) + end state, orig_inputs end diff --git a/src/systems/systems.jl b/src/systems/systems.jl index a326703262..e417337a95 100644 --- a/src/systems/systems.jl +++ b/src/systems/systems.jl @@ -74,6 +74,7 @@ end function __structural_simplify(sys::AbstractSystem; simplify = false, inputs = Any[], outputs = Any[], disturbance_inputs = Any[], + sort_eqs = true, kwargs...) sys = expand_connections(sys) state = TearingState(sys; sort_eqs) diff --git a/src/systems/systemstructure.jl b/src/systems/systemstructure.jl index 4daf4a29cc..1b561f97b4 100644 --- a/src/systems/systemstructure.jl +++ b/src/systems/systemstructure.jl @@ -724,7 +724,8 @@ function _structural_simplify!(state::TearingState; simplify = false, else check_consistency = true end - has_io = inputs !== nothing || outputs !== nothing + has_io = !isempty(inputs) || !isempty(outputs) !== nothing || + !isempty(disturbance_inputs) orig_inputs = Set() if has_io ModelingToolkit.markio!(state, orig_inputs, inputs, outputs, disturbance_inputs) From 90aba86b635875e885d0ff1f14dfbcf57799f1ac Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Wed, 7 May 2025 12:45:29 +0530 Subject: [PATCH 117/122] test: fix input output test --- test/input_output_handling.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/input_output_handling.jl b/test/input_output_handling.jl index 732fde5e3f..70ac772ca6 100644 --- a/test/input_output_handling.jl +++ b/test/input_output_handling.jl @@ -450,7 +450,7 @@ end @mtkbuild sys = ODESystem(eqs, t, [x], []) f, dvs, ps, io_sys = ModelingToolkit.generate_control_function(sys) - @test f[1]([0.5], nothing, MTKParameters(io_sys, []), 0.0) ≈ [1.0] + @test f([0.5], nothing, MTKParameters(io_sys, []), 0.0) ≈ [1.0] end @testset "With callable symbolic" begin From 0d7943d70d84ef1ae2c4aec1a6d8547632a77d0a Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Wed, 7 May 2025 12:46:02 +0530 Subject: [PATCH 118/122] test: fix usage of old `structural_simplify` io syntax --- test/downstream/jump_control.jl | 12 ++++++------ test/odesystem.jl | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/test/downstream/jump_control.jl b/test/downstream/jump_control.jl index a93146be10..dd0edb080d 100644 --- a/test/downstream/jump_control.jl +++ b/test/downstream/jump_control.jl @@ -98,7 +98,7 @@ end cost = [-x(1.0)] # Maximize the final distance. @named block = ODESystem( [D(x(t)) ~ v(t), D(v(t)) ~ u(t)], t; costs = cost, constraints = constr) - block, input_idxs = structural_simplify(block, ([u(t)], [])) + block = structural_simplify(block; inputs = [u(t)]) u0map = [x(t) => 0.0, v(t) => 0.0] tspan = (0.0, 1.0) @@ -137,7 +137,7 @@ end costs = [-q(tspan[2])] @named beesys = ODESystem(eqs, t; costs) - beesys, input_idxs = structural_simplify(beesys, ([α], [])) + beesys = structural_simplify(beesys; inputs = [α]) u0map = [w(t) => 40, q(t) => 2] pmap = [b => 1, c => 1, μ => 1, s => 1, ν => 1, α => 1] @@ -180,7 +180,7 @@ end costs = [-h(te)] cons = [T(te) ~ 0, m(te) ~ m_c] @named rocket = ODESystem(eqs, t; costs, constraints = cons) - rocket, input_idxs = structural_simplify(rocket, ([T(t)], [])) + rocket = structural_simplify(rocket; inputs = [T(t)]) u0map = [h(t) => h₀, m(t) => m₀, v(t) => 0] pmap = [ @@ -223,7 +223,7 @@ end costs = [-Symbolics.Integral(t in (0, tf))(x(t) - u(t)), -x(tf)] consolidate(u) = u[1] + u[2] @named rocket = ODESystem(eqs, t; costs, consolidate) - rocket, input_idxs = structural_simplify(rocket, ([u(t)], [])) + rocket = structural_simplify(rocket; inputs = [u(t)]) u0map = [x(t) => 17.5] pmap = [u(t) => 0.0, tf => 8] @@ -243,7 +243,7 @@ end @named block = ODESystem( [D(x(t)) ~ v(t), D(v(t)) ~ u(t)], t; costs = cost, constraints = constr) - block, input_idxs = structural_simplify(block, ([u(t)], [])) + block = structural_simplify(block, inputs = [u(t)]) u0map = [x(t) => 1.0, v(t) => 0.0] tspan = (0.0, tf) @@ -282,7 +282,7 @@ end tspan = (0, tf) @named cartpole = ODESystem(eqs, t; costs, constraints = cons) - cartpole, input_idxs = structural_simplify(cartpole, ([u], [])) + cartpole, input_idxs = structural_simplify(cartpole; inputs = [u]) u0map = [D(x(t)) => 0.0, D(θ(t)) => 0.0, θ(t) => 0.0, x(t) => 0.0] pmap = [mₖ => 1.0, mₚ => 0.2, l => 0.5, g => 9.81, u => 0] diff --git a/test/odesystem.jl b/test/odesystem.jl index c80efa3157..b8f92dbc31 100644 --- a/test/odesystem.jl +++ b/test/odesystem.jl @@ -466,7 +466,7 @@ let eqs = [D(x) ~ ẋ, D(ẋ) ~ f - k * x - d * ẋ] @named sys = ODESystem(eqs, t, [x, ẋ], [d, k]) - sys, _ = structural_simplify(sys, ([f], [])) + sys = structural_simplify(sys; inputs = [f]) @test isequal(calculate_control_jacobian(sys), reshape(Num[0, 1], 2, 1)) From bdbf29a25c9cc10e6932b7ec02068b59b8ec57bc Mon Sep 17 00:00:00 2001 From: Vincent Du <54586336+vyudu@users.noreply.github.com> Date: Wed, 7 May 2025 12:41:18 -0400 Subject: [PATCH 119/122] Update src/parameters.jl Co-authored-by: Aayush Sabharwal --- src/parameters.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/parameters.jl b/src/parameters.jl index 8c0f9f1b00..de7a722af3 100644 --- a/src/parameters.jl +++ b/src/parameters.jl @@ -63,7 +63,7 @@ toparam(s::Num) = wrap(toparam(value(s))) Maps the variable to an unknown. """ tovar(s::Symbolic) = setmetadata(s, MTKVariableTypeCtx, VARIABLE) -tovar(s::Union{Num, Symbolics.Arr}) = Num(tovar(value(s))) +tovar(s::Union{Num, Symbolics.Arr}) = wrap(tovar(unwrap(s))) """ $(SIGNATURES) From d6c266652504ef2be3c95e32576d691b482fe5ca Mon Sep 17 00:00:00 2001 From: Vincent Du <54586336+vyudu@users.noreply.github.com> Date: Wed, 7 May 2025 12:41:31 -0400 Subject: [PATCH 120/122] Update src/systems/callbacks.jl Co-authored-by: Aayush Sabharwal --- src/systems/callbacks.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/systems/callbacks.jl b/src/systems/callbacks.jl index 92dbfddda6..11a6c9754b 100644 --- a/src/systems/callbacks.jl +++ b/src/systems/callbacks.jl @@ -618,7 +618,7 @@ function compile_condition( condit, u, p..., t; expression, kwargs...) - if expression == Val{true} + if expression == Val{false} fs = eval_or_rgf.(fs; eval_expression, eval_module) end f_oop, f_iip = is_discrete(cbs) ? (fs, nothing) : fs # no iip function for discrete condition. From ca507dfd8532f31f4081f7661a347071c6451c25 Mon Sep 17 00:00:00 2001 From: vyudu Date: Thu, 8 May 2025 10:49:26 -0400 Subject: [PATCH 121/122] fix compile condition error --- src/systems/callbacks.jl | 29 +++++------------------------ 1 file changed, 5 insertions(+), 24 deletions(-) diff --git a/src/systems/callbacks.jl b/src/systems/callbacks.jl index 8987f55bfb..5d56c50277 100644 --- a/src/systems/callbacks.jl +++ b/src/systems/callbacks.jl @@ -588,15 +588,9 @@ Base.isempty(cb::AbstractCallback) = isempty(cb.conditions) compile_condition(cb::AbstractCallback, sys, dvs, ps; expression, kwargs...) Returns a function `condition(u,t,integrator)`, condition(out,u,t,integrator)` returning the `condition(cb)`. - -Notes - - - `expression = Val{true}`, causes the generated function to be returned as an expression. - If set to `Val{false}` a `RuntimeGeneratedFunction` will be returned. - - `kwargs` are passed through to `Symbolics.build_function`. """ -function compile_condition(cb::SymbolicDiscreteCallback, sys, dvs, ps; - expression = Val{true}, eval_expression = false, eval_module = @__MODULE__, kwargs...) +function compile_condition(cbs::Union{AbstractCallback, Vector{<:AbstractCallback}}, sys, dvs, ps; + eval_expression = false, eval_module = @__MODULE__, kwargs...) u = map(value, dvs) p = map.(value, reorder_parameters(sys, ps)) t = get_iv(sys) @@ -613,14 +607,8 @@ function compile_condition(cb::SymbolicDiscreteCallback, sys, dvs, ps; [condit.lhs - condit.rhs] end - fs = build_function_wrapper(sys, - condit, u, p..., t; expression, - kwargs...) - - if expression == Val{false} - fs = eval_or_rgf.(fs; eval_expression, eval_module) - end - f_oop, f_iip = is_discrete(cbs) ? (fs, nothing) : fs # no iip function for discrete condition. + fs = build_function_wrapper(sys, condit, u, p..., t; kwargs..., expression = Val{false}) + (f_oop, f_iip) = is_discrete(cbs) ? (fs, nothing) : fs cond = if cbs isa AbstractVector (out, u, t, integ) -> f_iip(out, u, parameter_values(integ), t) @@ -628,7 +616,7 @@ function compile_condition(cb::SymbolicDiscreteCallback, sys, dvs, ps; (u, t, integ) -> f_oop(u, parameter_values(integ), t) else function (u, t, integ) - if DiffEqBase.isinplace(integ.sol.prob) + if DiffEqBase.isinplace(SciMLBase.get_sol(integ).prob) tmp, = DiffEqBase.get_tmp_cache(integ) f_iip(tmp, u, parameter_values(integ), t) tmp[1] @@ -808,13 +796,6 @@ Returns a function that takes an integrator as argument and modifies the state w affect. The generated function has the signature `affect!(integrator)`. Notes - - - `expression = Val{true}`, causes the generated function to be returned as an expression. - If set to `Val{false}` a `RuntimeGeneratedFunction` will be returned. - - `outputidxs`, a vector of indices of the output variables which should correspond to - `unknowns(sys)`. If provided, checks that the LHS of affect equations are variables are - dropped, i.e. it is assumed these indices are correct and affect equations are - well-formed. - `kwargs` are passed through to `Symbolics.build_function`. """ function compile_affect( From 426e6bbc9021cbfcf546e9b976715e7a73690d5d Mon Sep 17 00:00:00 2001 From: vyudu Date: Thu, 8 May 2025 10:51:37 -0400 Subject: [PATCH 122/122] disable cse --- src/systems/callbacks.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/systems/callbacks.jl b/src/systems/callbacks.jl index 5d56c50277..785dc49338 100644 --- a/src/systems/callbacks.jl +++ b/src/systems/callbacks.jl @@ -607,7 +607,7 @@ function compile_condition(cbs::Union{AbstractCallback, Vector{<:AbstractCallbac [condit.lhs - condit.rhs] end - fs = build_function_wrapper(sys, condit, u, p..., t; kwargs..., expression = Val{false}) + fs = build_function_wrapper(sys, condit, u, p..., t; kwargs..., expression = Val{false}, cse = false) (f_oop, f_iip) = is_discrete(cbs) ? (fs, nothing) : fs cond = if cbs isa AbstractVector