From 452727e62743d7b19bdea31b3e93903d4011e868 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Mon, 14 Apr 2025 18:57:00 +0530 Subject: [PATCH 001/235] chore!: bump MAJOR version --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 305f29c691..74d5424533 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.80.1" +version = "10.0.0" [deps] ADTypes = "47edcb42-4c32-4615-8424-f2b9edc5f35b" From 08075cec9af518371591a86607cee4010a2dd3b0 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Mon, 14 Apr 2025 18:57:09 +0530 Subject: [PATCH 002/235] 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 970b2f349e985d82b8c97a919b1cb2044716f9ad Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Mon, 14 Apr 2025 19:10:06 +0530 Subject: [PATCH 003/235] 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 1b22715292d458eb20ce77943af8542486e4cc2c Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Mon, 14 Apr 2025 19:15:37 +0530 Subject: [PATCH 004/235] 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 74d5424533..ca4e79255a 100644 --- a/Project.toml +++ b/Project.toml @@ -176,6 +176,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" @@ -195,5 +196,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 3f9f5b7c5cbb83322708205139022f7dcc1ae0dd Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Thu, 3 Apr 2025 17:07:53 +0530 Subject: [PATCH 005/235] 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 3e665745fbd93aad332e665ff5d76a615ec7fb3d Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Mon, 14 Apr 2025 21:38:27 +0530 Subject: [PATCH 006/235] 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 3220555e62..b824d18a24 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 σ ρ β @@ -1755,3 +1756,26 @@ end sol = solve(prob, Tsit5()) @test SciMLBase.successful_retcode(sol) 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 94f1c051324455b7c373aecdc2f4b77181ae7cca Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Mon, 14 Apr 2025 19:47:53 +0530 Subject: [PATCH 007/235] 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 9ed6b9994a..049c1815e4 100644 --- a/src/ModelingToolkit.jl +++ b/src/ModelingToolkit.jl @@ -301,7 +301,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 90431a7749..a2034ec58a 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -646,8 +646,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 9bf44a78b5bcd22f1e30efcf61807dd1255c7eb9 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Mon, 14 Apr 2025 20:15:04 +0530 Subject: [PATCH 008/235] 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 739df14ae0..8ee71077f8 100644 --- a/src/inputoutput.jl +++ b/src/inputoutput.jl @@ -221,7 +221,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 6e902e0da9d93d3c72e3d2678aa61034313a2dec Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Mon, 14 Apr 2025 20:37:56 +0530 Subject: [PATCH 009/235] 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 b824d18a24..469b77611a 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 e050e5e48bcad02ba315e8fa9e155611a5a2ef4d Mon Sep 17 00:00:00 2001 From: vyudu Date: Fri, 18 Apr 2025 18:02:18 -0400 Subject: [PATCH 010/235] 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 8ee71077f8..7e40fe4e26 100644 --- a/src/inputoutput.jl +++ b/src/inputoutput.jl @@ -180,9 +180,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 ``` @@ -202,17 +199,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) @@ -259,8 +256,11 @@ function generate_control_function(sys::AbstractODESystem, inputs = unbound_inpu (; f = (f, 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 8c0fa25e9bdfea15b90af41374b1d4c459e5ad3b Mon Sep 17 00:00:00 2001 From: vyudu Date: Mon, 21 Apr 2025 14:42:16 -0400 Subject: [PATCH 011/235] 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 b30d275818..199d51b823 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], @@ -487,9 +499,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 082cd5d90b3a946fd934eca25464c0df8cb17273 Mon Sep 17 00:00:00 2001 From: vyudu Date: Mon, 21 Apr 2025 23:44:47 -0400 Subject: [PATCH 012/235] 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 7e40fe4e26..5d1c106fe2 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 199d51b823..35b905e569 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 3d988620abbe9be932f41d5eb77c713822a23565 Mon Sep 17 00:00:00 2001 From: vyudu Date: Thu, 24 Apr 2025 15:37:58 -0400 Subject: [PATCH 013/235] 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 67f60a04cd..5ebfe1ee3b 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 469b77611a..2dbf4b2161 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 adeb4005d7..81cfe8473b 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 c66e604829889dc57ea537d3e289518bde482c62 Mon Sep 17 00:00:00 2001 From: vyudu Date: Thu, 24 Apr 2025 17:35:48 -0400 Subject: [PATCH 014/235] 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 | 28 ++++++++++++++++------------ test/reduction.jl | 2 +- 6 files changed, 36 insertions(+), 22 deletions(-) diff --git a/src/inputoutput.jl b/src/inputoutput.jl index 5d1c106fe2..2093277fe4 100644 --- a/src/inputoutput.jl +++ b/src/inputoutput.jl @@ -198,10 +198,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, 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 35b905e569..ddc71623c1 100644 --- a/src/linearization.jl +++ b/src/linearization.jl @@ -561,10 +561,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) @@ -586,6 +587,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 5ebfe1ee3b..68936b52bd 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,9 +447,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 +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 81cfe8473b..a692dbff75 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 6ca9b12e92b8263da15906f4185a1b99a2cf801b Mon Sep 17 00:00:00 2001 From: vyudu Date: Fri, 25 Apr 2025 11:20:18 -0400 Subject: [PATCH 015/235] 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 2093277fe4..cecc2d36d4 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 eb734358c5..1a811359d3 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 1edc7f192572f3c7404411edaadc2c267622f9b4 Mon Sep 17 00:00:00 2001 From: vyudu Date: Mon, 28 Apr 2025 16:12:50 -0400 Subject: [PATCH 016/235] 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 cecc2d36d4..283be6ed92 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, 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 ddc71623c1..dd474ea1a0 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 @@ -561,6 +560,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) @@ -591,6 +593,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 @@ -601,11 +604,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 07b8cde9c67824f157326d3e604b0999c8118276 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Wed, 7 May 2025 12:46:02 +0530 Subject: [PATCH 017/235] test: fix usage of old `structural_simplify` io syntax --- test/extensions/dynamic_optimization.jl | 12 ++++++------ test/odesystem.jl | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/test/extensions/dynamic_optimization.jl b/test/extensions/dynamic_optimization.jl index 6e037746a3..14008bdbf7 100644 --- a/test/extensions/dynamic_optimization.jl +++ b/test/extensions/dynamic_optimization.jl @@ -118,7 +118,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) @@ -166,7 +166,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] @@ -213,7 +213,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 = [ @@ -261,7 +261,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] @@ -285,7 +285,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) @@ -328,7 +328,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 2dbf4b2161..e113e5d2c2 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 8a7259e0fc473db414ccb8756aafea09fd4489ea Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Fri, 9 May 2025 15:07:25 +0530 Subject: [PATCH 018/235] fix: fix usage of `structural_simplify` with inputs --- test/extensions/dynamic_optimization.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/extensions/dynamic_optimization.jl b/test/extensions/dynamic_optimization.jl index 14008bdbf7..79810d2476 100644 --- a/test/extensions/dynamic_optimization.jl +++ b/test/extensions/dynamic_optimization.jl @@ -328,7 +328,7 @@ end tspan = (0, tf) @named cartpole = ODESystem(eqs, t; costs, constraints = cons) - cartpole, input_idxs = structural_simplify(cartpole; inputs = [u]) + cartpole = 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] From c477a332b9e9595ba0e0f55c4a3835b2cd7c7dc3 Mon Sep 17 00:00:00 2001 From: vyudu Date: Sat, 1 Mar 2025 17:35:49 -0500 Subject: [PATCH 019/235] feat: add new affect semantics, reorganize callback code --- src/systems/callbacks.jl | 1618 ++++++++++++++---------------- src/systems/imperative_affect.jl | 59 +- 2 files changed, 822 insertions(+), 855 deletions(-) diff --git a/src/systems/callbacks.jl b/src/systems/callbacks.jl index 7d542d9bd0..8a5c6131f3 100644 --- a/src/systems/callbacks.jl +++ b/src/systems/callbacks.jl @@ -1,15 +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 AbstractCallback end struct FunctionalAffect f::Any @@ -38,7 +27,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 @@ -62,33 +51,118 @@ 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) +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 +end + +system(a::AffectSystem) = a.system +discretes(a::AffectSystem) = a.discretes +unknowns(a::AffectSystem) = a.unknowns +parameters(a::AffectSystem) = a.parameters +aff_to_sys(a::AffectSystem) = a.aff_to_sys +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 = all_equations(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 + +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) + hash(aff_to_sys(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 - return vars + vars end -#################################### continuous events ##################################### +""" + Pre(x) -const NULL_AFFECT = Equation[] +The `Pre` operator. Used by the callback system to indicate the value of a parameter or variable +before the callback is triggered. """ - SymbolicContinuousCallback(eqs::Vector{Equation}, affect, affect_neg, rootfind) +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() +unPre(x::Num) = unPre(unwrap(x)) +unPre(x::BasicSymbolic) = (iscall(x) && operation(x) isa Pre) ? only(arguments(x)) : x + +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, 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(arr), arguments(x)[2:end]...) + else + term(p, 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 ###### +############################### +const Affect = Union{AffectSystem, FunctionalAffect, ImperativeAffect} + +""" + SymbolicContinuousCallback(eqs::Vector{Equation}, affect = nothing, iv = nothing; + 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. @@ -128,995 +202,766 @@ 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. +`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 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 - 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 +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} reinitializealg::SciMLBase.DAEInitializationAlgorithm - function SymbolicContinuousCallback(; - eqs::Vector{Equation}, - affect = NULL_AFFECT, + + function SymbolicContinuousCallback( + conditions::Union{Equation, Vector{Equation}}, + affect = nothing; affect_neg = affect, - initialize = NULL_AFFECT, - finalize = NULL_AFFECT, + initialize = nothing, + finalize = nothing, rootfind = SciMLBase.LeftRootFind, - reinitializealg = SciMLBase.CheckInit()) - new(eqs, initialize, finalize, make_affect(affect), - make_affect(affect_neg), rootfind, reinitializealg) + reinitializealg = nothing, + kwargs...) + conditions = (conditions isa AbstractVector) ? conditions : [conditions] + + if isnothing(reinitializealg) + 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...), + make_affect(affect_neg; kwargs...), + make_affect(initialize; kwargs...), make_affect( + finalize; kwargs...), + rootfind, reinitializealg) 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) + +function SymbolicContinuousCallback(p::Pair, args...; kwargs...) + SymbolicContinuousCallback(p[1], p[2], args...; kwargs...) 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) +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 -function Base.show(io::IO, cb::SymbolicContinuousCallback) +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}; discrete_parameters = Any[], + iv = nothing, alg_eqs::Vector{Equation} = Equation[], warn_no_algebraic = true, kwargs...) + isempty(affect) && return nothing + if isnothing(iv) + iv = t_nounits + @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).") + end + + dvs = OrderedSet() + params = OrderedSet() + 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 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) + union!(dvs, diffvs) + end + for eq in alg_eqs + collect_vars!(dvs, params, eq, iv) + end + + 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)) + subs = merge(rev_map, Dict(zip(dvs, _dvs))) + affect = Symbolics.fast_substitute(affect, subs) + alg_eqs = Symbolics.fast_substitute(alg_eqs, subs) + + @named affectsys = ImplicitDiscreteSystem( + vcat(affect, alg_eqs), iv, collect(union(_dvs, discretes)), + collect(union(pre_params, sys_params))) + 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) + + # add scalarized unknowns to the map. + _dvs = reduce(vcat, map(scalarize, _dvs), init = Any[]) + for u in _dvs + aff_map[u] = u + end + + AffectSystem(affectsys, collect(_dvs), collect(accessed_params), + collect(discrete_parameters), aff_map) +end + +function make_affect(affect; kwargs...) + error("Malformed affect $(affect). This should be a vector of equations or a tuple specifying a functional affect.") +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(") - print(iio, "Equations:") + print(iio, "Conditions:") 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 !is_discrete(cb) && 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 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) + is_discrete(cb) ? println(io, "SymbolicDiscreteCallback:") : println(io, "SymbolicContinuousCallback:") - println(iio, "Equations:") + println(iio, "Conditions:") 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 !is_discrete(cb) && 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") 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 - -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 - -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[] - -equations(cb::SymbolicContinuousCallback) = cb.eqs -function equations(cbs::Vector{<:SymbolicContinuousCallback}) - mapreduce(equations, vcat, cbs, init = Equation[]) -end - -affects(cb::SymbolicContinuousCallback) = cb.affect -function affects(cbs::Vector{SymbolicContinuousCallback}) - mapreduce(affects, vcat, cbs, init = Equation[]) -end - -affect_negs(cb::SymbolicContinuousCallback) = cb.affect_neg -function affect_negs(cbs::Vector{SymbolicContinuousCallback}) - mapreduce(affect_negs, vcat, cbs, init = Equation[]) -end - -reinitialization_alg(cb::SymbolicContinuousCallback) = cb.reinitializealg -function reinitialization_algs(cbs::Vector{SymbolicContinuousCallback}) - mapreduce( - reinitialization_alg, vcat, cbs, init = SciMLBase.DAEInitializationAlgorithm[]) -end - -initialize_affects(cb::SymbolicContinuousCallback) = cb.initialize -function initialize_affects(cbs::Vector{SymbolicContinuousCallback}) - mapreduce(initialize_affects, vcat, cbs, init = Equation[]) -end - -finalize_affects(cb::SymbolicContinuousCallback) = cb.finalize -function finalize_affects(cbs::Vector{SymbolicContinuousCallback}) - mapreduce(finalize_affects, vcat, cbs, init = Equation[]) -end - -namespace_affects(af::Vector, s) = Equation[namespace_affect(a, s) for a in af] -namespace_affects(af::FunctionalAffect, s) = namespace_affect(af, s) -namespace_affects(::Nothing, s) = nothing - -function namespace_callback(cb::SymbolicContinuousCallback, s)::SymbolicContinuousCallback - SymbolicContinuousCallback(; - eqs = namespace_equation.(equations(cb), (s,)), - affect = namespace_affects(affects(cb), s), - affect_neg = namespace_affects(affect_negs(cb), s), - initialize = namespace_affects(initialize_affects(cb), s), - finalize = namespace_affects(finalize_affects(cb), s), - rootfind = cb.rootfind) -end - -""" - 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`. -""" -function continuous_events(sys::AbstractSystem) - cbs = get_continuous_events(sys) - filter(!isempty, cbs) - - 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) -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 +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 +################################ +######## Discrete events ####### +################################ """ - continuous_events_toplevel(sys::AbstractSystem) + SymbolicDiscreteCallback(conditions::Vector{Equation}, affect = nothing, iv = nothing; + initialize = nothing, finalize = nothing, alg_eqs = Equation[]) -Replicates the behaviour of `continuous_events`, but ignores events of subsystems. +A callback that triggers at the first timestep that the conditions are satisfied. -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 +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{Symbolic} - events trigger when the condition evaluates to true -#################################### discrete events ##################################### - -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 +Arguments: +- 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 + conditions::Union{Number, Vector{<:Number}, Symbolic{Bool}} + affect::Union{Affect, Nothing} + initialize::Union{Affect, Nothing} + finalize::Union{Affect, Nothing} reinitializealg::SciMLBase.DAEInitializationAlgorithm 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) + 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)) + + if isnothing(reinitializealg) + reinitializealg = SciMLBase.CheckInit() + else + reinitializealg = SciMLBase.NoInit() + end + new(c, make_affect(affect; kwargs...), + make_affect(initialize; kwargs...), + make_affect(finalize; kwargs...), reinitializealg) end # Default affect to nothing 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) +function SymbolicDiscreteCallback(p::Pair, args...; kwargs...) + SymbolicDiscreteCallback(p[1], p[2], args...; kwargs...) 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) +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 - for affect in db.affects - println(io, " ", affect) - end + error("Malformed tuple specifying callback. Should be a condition => affect pair, followed by a vector of kwargs.") 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 +function is_timed_condition(condition::T) where {T} + if T === Num + false + elseif T <: Real + true + elseif T <: AbstractVector + eltype(condition) <: Real + else + false + end end -condition(cb::SymbolicDiscreteCallback) = cb.condition -function conditions(cbs::Vector{<:SymbolicDiscreteCallback}) - reduce(vcat, condition(cb) for cb in cbs) +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 + [CB_TYPE(cbs; kwargs...)] + else + Vector{CB_TYPE}([CB_TYPE(cb; kwargs...) for cb in cbs]) + end end -affects(cb::SymbolicDiscreteCallback) = cb.affects - -function affects(cbs::Vector{SymbolicDiscreteCallback}) - reduce(vcat, affects(cb) for cb in cbs; init = []) +############################################ +########## Namespacing Utilities ########### +############################################ +function namespace_affects(affect::FunctionalAffect, s) + FunctionalAffect(func(affect), + renamespace.((s,), unknowns(affect)), + unknowns_syms(affect), + renamespace.((s,), parameters(affect)), + parameters_syms(affect), + renamespace.((s,), discretes(affect)), + context(affect)) end -reinitialization_alg(cb::SymbolicDiscreteCallback) = cb.reinitializealg -function reinitialization_algs(cbs::Vector{SymbolicDiscreteCallback}) - mapreduce( - reinitialization_alg, vcat, cbs, init = SciMLBase.DAEInitializationAlgorithm[]) +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)])) end +namespace_affects(af::Nothing, s) = nothing -initialize_affects(cb::SymbolicDiscreteCallback) = cb.initialize -function initialize_affects(cbs::Vector{SymbolicDiscreteCallback}) - mapreduce(initialize_affects, vcat, cbs, init = Equation[]) +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), + rootfind = cb.rootfind, reinitializealg = cb.reinitializealg) end -finalize_affects(cb::SymbolicDiscreteCallback) = cb.finalize -function finalize_affects(cbs::Vector{SymbolicDiscreteCallback}) - mapreduce(finalize_affects, vcat, cbs, init = Equation[]) +function namespace_conditions(condition, s) + is_timed_condition(condition) ? condition : namespace_expr(condition, s) 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))) + namespace_conditions(conditions(cb), s), + namespace_affects(affects(cb), s), + initialize = namespace_affects(initialize_affects(cb), s), + finalize = namespace_affects(finalize_affects(cb), s), reinitializealg = cb.reinitializealg) end -SymbolicDiscreteCallbacks(cb::Pair) = SymbolicDiscreteCallback[SymbolicDiscreteCallback(cb)] -SymbolicDiscreteCallbacks(cbs::Vector) = SymbolicDiscreteCallback.(cbs) -SymbolicDiscreteCallbacks(cb::SymbolicDiscreteCallback) = [cb] -SymbolicDiscreteCallbacks(cbs::Vector{<:SymbolicDiscreteCallback}) = cbs -SymbolicDiscreteCallbacks(::Nothing) = SymbolicDiscreteCallback[] +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) && (s = hash(cb.rootfind, s)) + hash(cb.reinitializealg, s) +end -""" - discrete_events(sys::AbstractSystem) :: Vector{SymbolicDiscreteCallback} +########################### +######### Helpers ######### +########################### -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 +conditions(cb::AbstractCallback) = cb.conditions +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) -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 +affects(cb::AbstractCallback) = cb.affect +function affects(cbs::Vector{<:AbstractCallback}) + reduce(vcat, affects(cb) for cb in cbs; init = []) 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) +affect_negs(cb::SymbolicContinuousCallback) = cb.affect_neg +function affect_negs(cbs::Vector{SymbolicContinuousCallback}) + reduce(vcat, affect_negs(cb) for cb in cbs; init = []) 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) +initialize_affects(cb::AbstractCallback) = cb.initialize +function initialize_affects(cbs::Vector{<:AbstractCallback}) + reduce(initialize_affects, vcat, cbs; init = []) end -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) +finalize_affects(cb::AbstractCallback) = cb.finalize +function finalize_affects(cbs::Vector{<:AbstractCallback}) + reduce(finalize_affects, vcat, cbs; init = []) 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 +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) || + return false + is_discrete(e1) || + (isequal(e1.affect_neg, e2.affect_neg) && isequal(e1.rootfind, e2.rootfind)) end -""" - compile_condition(cb::SymbolicDiscreteCallback, sys, dvs, ps; expression, kwargs...) - -Returns a function `condition(u,t,integrator)` returning the `condition(cb)`. +Base.isempty(cb::AbstractCallback) = isempty(cb.conditions) -Notes +#################################### +####### Compilation functions ###### +#################################### +""" + compile_condition(cb::AbstractCallback, sys, dvs, ps; expression, kwargs...) - - `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`. +Returns a function `condition(u,t,integrator)`, condition(out,u,t,integrator)` returning the `condition(cb)`. """ -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) - condit = condition(cb) + condit = conditions(cbs) cs = collect_constants(condit) if !isempty(cs) cmap = map(x -> x => getdefault(x), cs) - condit = substitute(condit, cmap) + condit = substitute(condit, Dict(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 - end - 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)`. + 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] + end -Notes + 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 - - `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 + cond = if cbs isa AbstractVector + (out, u, t, integ) -> f_iip(out, u, parameter_values(integ), t) + elseif is_discrete(cbs) + (u, t, integ) -> f_oop(u, parameter_values(integ), t) 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 + function (u, t, integ) + if DiffEqBase.isinplace(SciMLBase.get_sol(integ).prob) + tmp, = DiffEqBase.get_tmp_cache(integ) + f_iip(tmp, u, parameter_values(integ), t) + tmp[1] 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.") + f_oop(u, parameter_values(integ), t) 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. +Compile user-defined functional affect. """ -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 +function compile_functional_affect(affect::FunctionalAffect, sys; kwargs...) + dvs = unknowns(sys) + ps = parameters(sys) + dvs_ind = Dict(reverse(en) for en in enumerate(dvs)) + v_inds = map(sym -> dvs_ind[sym], unknowns(affect)) - 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 + if has_index_cache(sys) && (ic = get_index_cache(sys)) !== nothing + p_inds = [(pind = parameter_index(sys, sym)) === nothing ? sym : pind + for sym in parameters(affect)] else - initfn = user_initfun + ps_ind = Dict(reverse(en) for en in enumerate(ps)) + p_inds = map(sym -> get(ps_ind, sym, sym), parameters(affect)) end + # HACK: filter out eliminated symbols. Not clear this is the right thing to do + # (MTK should keep these symbols) + u = filter(x -> !isnothing(x[2]), collect(zip(unknowns_syms(affect), v_inds))) |> + NamedTuple + p = filter(x -> !isnothing(x[2]), collect(zip(parameters_syms(affect), p_inds))) |> + NamedTuple - 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)) + let u = u, p = p, user_affect = func(affect), ctx = context(affect) + (integ) -> begin + user_affect(integ, u, p, ctx) + end + end 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 +is_discrete(cb::AbstractCallback) = cb isa SymbolicDiscreteCallback +is_discrete(cb::Vector{<:AbstractCallback}) = eltype(cb) isa SymbolicDiscreteCallback - 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 +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}}() - # 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 + # Sort the callbacks by their rootfinding method + for cb in cbs + _cbs = get!(() -> SymbolicContinuousCallback[], + cb_classes, (cb.rootfind, cb.reinitializealg)) + push!(_cbs, cb) 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) - + 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) else - initialize = handle_optional_setup_fn( - map(fn -> fn.initialize, affect_functions), SciMLBase.INITIALIZE_DEFAULT) + return CallbackSet(compiled_callbacks...) end +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) +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 +EMPTY_AFFECT(args...) = nothing + """ -Compile a single continuous callback affect function(s). +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 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 - 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) +function generate_callback(cbs::Vector{SymbolicContinuousCallback}, sys; kwargs...) + eqs = map(cb -> flatten_equations(equations(cb)), 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 + (isempty(eqs) || sum(num_eqs) == 0) && return nothing + if sum(num_eqs) == 1 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...) + return generate_callback(cbs[cb_ind], sys; 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}}() + trigger = compile_condition( + cbs, sys, unknowns(sys), parameters(sys; initial_parameters = true); kwargs...) + affects = [] + affect_negs = [] + inits = [] + finals = [] for cb in cbs - push!( - get!(() -> SymbolicContinuousCallback[], cb_classes, - ( - rootfind = cb.rootfind, - reinitialization = reinitialization_alg(cb))), - cb) + 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...) + 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...)) 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...) + # Since there may be different number of conditions and affects, + # we build a map that translates the condition eq. number to the affect number + eq2affect = reduce(vcat, + [fill(i, num_eqs[i]) for i in eachindex(affects)]) + eqs = reduce(vcat, eqs) + + affect = let eq2affect = eq2affect, affects = affects + function (integ, idx) + affects[eq2affect[idx]](integ) + end end - if length(compiled_callbacks) == 1 - return compiled_callbacks[] - else - return CallbackSet(compiled_callbacks...) + affect_neg = let eq2affect = eq2affect, affect_negs = affect_negs + function (integ, idx) + f = affect_negs[eq2affect[idx]] + isnothing(f) && return + f(integ) + end end -end + initialize = wrap_vector_optional_affect(inits, SciMLBase.INITIALIZE_DEFAULT) + finalize = wrap_vector_optional_affect(finals, SciMLBase.FINALIZE_DEFAULT) -function compile_user_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)) + return VectorContinuousCallback( + trigger, affect, affect_neg, length(eqs); initialize, finalize, + rootfind = cbs[1].rootfind, initializealg = cbs[1].reinitializealg) +end - 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)] - else - ps_ind = Dict(reverse(en) for en in enumerate(ps)) - p_inds = map(sym -> get(ps_ind, sym, sym), parameters(affect)) - end - # HACK: filter out eliminated symbols. Not clear this is the right thing to do - # (MTK should keep these symbols) - u = filter(x -> !isnothing(x[2]), collect(zip(unknowns_syms(affect), v_inds))) |> - NamedTuple - p = filter(x -> !isnothing(x[2]), collect(zip(parameters_syms(affect), p_inds))) |> - NamedTuple +function generate_callback(cb, sys; kwargs...) + is_timed = is_timed_condition(conditions(cb)) + dvs = unknowns(sys) + ps = parameters(sys; initial_parameters = true) - if has_index_cache(sys) && (ic = get_index_cache(sys)) !== nothing - save_idxs = get(ic.callback_to_clocks, cb, Int[]) + trigger = is_timed ? conditions(cb) : compile_condition(cb, sys, dvs, ps; kwargs...) + affect = compile_affect(cb.affect, cb, sys; default = EMPTY_AFFECT, kwargs...) + affect_neg = if is_discrete(cb) + nothing else - save_idxs = Int[] + (cb.affect === cb.affect_neg) ? affect : + compile_affect(cb.affect_neg, cb, sys; default = EMPTY_AFFECT, kwargs...) end - 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 - SciMLBase.save_discretes!(integ, idx) - 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...) + + 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 + return PresetTimeCallback(trigger, affect; initialize, + finalize, initializealg = cb.reinitializealg) + elseif is_timed + return PeriodicCallback( + affect, trigger; initialize, finalize, initializealg = cb.reinitializealg) + else + return DiscreteCallback(trigger, affect; initialize, + finalize, initializealg = cb.reinitializealg) end + else + return ContinuousCallback(trigger, affect, affect_neg; initialize, finalize, + rootfind = cb.rootfind, initializealg = cb.reinitializealg) 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 +""" + compile_affect(cb::AbstractCallback, sys::AbstractSystem, dvs, ps; expression, outputidxs, kwargs...) -@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 +Returns a function that takes an integrator as argument and modifies the state with the +affect. The generated function has the signature `affect!(integrator)`. -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) +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...) + save_idxs = if !(has_index_cache(sys) && (ic = get_index_cache(sys)) !== nothing) + Int[] else - false + get(ic.callback_to_clocks, cb, Int[]) end -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 - else - return compile_affect(aff, cb, sys, dvs, ps; expression = Val{false}, kwargs...) + if isnothing(aff) + 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) end end -function generate_timed_callback(cb, sys, dvs, ps; postprocess_affect_expr! = nothing, - kwargs...) - cond = condition(cb) - as = compile_affect(affects(cb), cb, sys, dvs, ps; expression = Val{false}, - postprocess_affect_expr!, kwargs...) - - user_initfun = _compile_optional_affect( - NULL_AFFECT, initialize_affects(cb), cb, sys, dvs, ps; kwargs...) - user_finfun = _compile_optional_affect( - NULL_AFFECT, finalize_affects(cb), cb, sys, dvs, ps; kwargs...) - if has_index_cache(sys) && (ic = get_index_cache(sys)) !== nothing && - (save_idxs = get(ic.callback_to_clocks, cb, nothing)) !== nothing - initfn = let - save_idxs = save_idxs - initfun = user_initfun - function (cb, u, t, integrator) - if !isnothing(initfun) - initfun(integrator) + +function wrap_save_discretes(f, save_idxs) + let save_idxs = save_idxs, f = f + if f === SciMLBase.INITIALIZE_DEFAULT + (c, u, t, i) -> begin + f(c, u, t, i) + for idx in save_idxs + SciMLBase.save_discretes!(i, idx) end + end + else + (i) -> begin + isnothing(f) || f(i) for idx in save_idxs - SciMLBase.save_discretes!(integrator, idx) + SciMLBase.save_discretes!(i, idx) end end 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)) 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...) - 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 +""" +Initialize and finalize for VectorContinuousCallback. +""" +function wrap_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 - 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)) 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 +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::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) + end + affsys = system(aff) + 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 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_u]), dvs) + + 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_u]), dvs, _ps..., t; + wrap_code = add_integrator_header(sys, integ, :u), + 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, 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! - dbs = map(symcbs) do cb - generate_discrete_callback(cb, sys, dvs, ps; kwargs...) + 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, sys = sys + + dvs_to_access = [aff_map[u] for u in unknowns(affsys)] + ps_to_access = [unPre(p) for p in parameters(affsys)] + + affu_getter = getsym(sys, dvs_to_access) + affp_getter = getsym(sys, ps_to_access) + 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]) + p_getter = getsym(affsys, [sys_map[p] for p in ps_to_update]) + + affprob = ImplicitDiscreteProblem(affsys, [dv => 0 for dv in unknowns(affsys)], + (0, 0), [p => 0.0 for p in parameters(affsys)]; + build_initializeprob = false, check_length = false) + + function implicit_affect!(integ) + new_u0 = affu_getter(integ) + affu_setter!(affprob, new_u0) + new_ps = affp_getter(integ) + affp_setter!(affprob, new_ps) + + affprob = remake( + affprob, tspan = (integ.t, integ.t)) + affsol = init(affprob, IDSolve()) + (check_error(affsol) === ReturnCode.InitialFailure) && + throw(UnsolvableCallbackError(all_equations(aff))) + + u_setter!(integ, u_getter(affsol)) + p_setter!(integ, p_getter(affsol)) + end + end end +end - dbs +struct UnsolvableCallbackError + eqs::Vector{Equation} +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`.") end merge_cb(::Nothing, ::Nothing) = nothing @@ -1124,18 +969,109 @@ 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_rootfinding_callback(sys; kwargs...) - else - contin_cb = nothing + 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 + +""" + 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(cb -> namespace_callback(cb, 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 + +""" + 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 - if has_discrete_events(sys) && !isempty(discrete_events(sys)) - discrete_cb = generate_discrete_callbacks(sys; kwargs...) - else - discrete_cb = nothing + return get_discrete_events(sys) +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 + +""" + 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 - cb = merge_cb(contin_cb, callback) - (discrete_cb === nothing) ? cb : CallbackSet(contin_cb, discrete_cb...) +""" +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/imperative_affect.jl b/src/systems/imperative_affect.jl index 9be9536c93..7b05a3ead2 100644 --- a/src/systems/imperative_affect.jl +++ b/src/systems/imperative_affect.jl @@ -122,11 +122,49 @@ function namespace_affect(affect::ImperativeAffect, s) affect.skip_checks) end -function compile_affect(affect::ImperativeAffect, cb, sys, dvs, ps; kwargs...) - compile_user_affect(affect, cb, sys, dvs, ps; kwargs...) +function invalid_variables(sys, expr) + filter(x -> !any(isequal(x), all_symbols(sys)), reduce(vcat, vars(expr); init = [])) end -function compile_user_affect(affect::ImperativeAffect, cb, sys, dvs, ps; kwargs...) +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_functional_affect(affect::ImperativeAffect, sys; kwargs...) #= Implementation sketch: generate observed function (oop), should save to a component array under obs_syms @@ -150,6 +188,9 @@ function compile_user_affect(affect::ImperativeAffect, cb, sys, dvs, ps; kwargs. 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 @@ -203,14 +244,8 @@ function compile_user_affect(affect::ImperativeAffect, cb, sys, dvs, ps; 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) @@ -224,10 +259,6 @@ function compile_user_affect(affect::ImperativeAffect, cb, sys, dvs, ps; 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 From f504824f84e6bb63d1657c7ef13829c45bbea847 Mon Sep 17 00:00:00 2001 From: vyudu Date: Mon, 17 Mar 2025 10:06:00 -0400 Subject: [PATCH 020/235] build: update Project.toml --- Project.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Project.toml b/Project.toml index ca4e79255a..ca03a35ba0 100644 --- a/Project.toml +++ b/Project.toml @@ -31,6 +31,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" @@ -43,6 +44,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" @@ -115,6 +117,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" From e1f2776d5d8e17ed79da55b3b5bc2f19b273908b Mon Sep 17 00:00:00 2001 From: vyudu Date: Mon, 3 Mar 2025 17:07:52 -0500 Subject: [PATCH 021/235] refactor: reorganize `include`s, export `Pre` --- src/ModelingToolkit.jl | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/ModelingToolkit.jl b/src/ModelingToolkit.jl index 049c1815e4..b8bbebfbbb 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 @@ -160,7 +161,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") @@ -170,19 +170,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") @@ -307,6 +308,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 From 0bc41666f4fd2efcac8716bf8a4483bc2af75d70 Mon Sep 17 00:00:00 2001 From: vyudu Date: Mon, 21 Apr 2025 13:58:49 -0400 Subject: [PATCH 022/235] docs: update NEWS.md --- NEWS.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/NEWS.md b/NEWS.md index 038b1d79f6..d316ac23fb 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 From b507cfca91aeb0e5977938b0683a06d3842ced20 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Fri, 9 May 2025 12:38:53 +0530 Subject: [PATCH 023/235] docs: document new `AffectSystem`/`Pre` semantics --- docs/src/basics/Events.md | 61 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/docs/src/basics/Events.md b/docs/src/basics/Events.md index 3a76f478f1..12721038ab 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 + +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 From 7fc26d0f10f817055bebd5744c6d464739a48d64 Mon Sep 17 00:00:00 2001 From: vyudu Date: Tue, 1 Apr 2025 15:10:38 -0400 Subject: [PATCH 024/235] docs: update docs to account for new `Pre` semantics --- docs/src/basics/Events.md | 47 ++++++++++++---------- docs/src/basics/MTKLanguage.md | 10 +++-- docs/src/systems/DiscreteSystem.md | 1 - docs/src/systems/ImplicitDiscreteSystem.md | 1 - docs/src/tutorials/fmi.md | 6 ++- 5 files changed, 37 insertions(+), 28 deletions(-) diff --git a/docs/src/basics/Events.md b/docs/src/basics/Events.md index 12721038ab..2808204d61 100644 --- a/docs/src/basics/Events.md +++ b/docs/src/basics/Events.md @@ -152,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 @@ -171,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( [ @@ -265,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 @@ -288,13 +288,13 @@ 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] # 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) @@ -316,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) @@ -330,16 +330,18 @@ 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 # 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] +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] @@ -359,7 +361,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`. @@ -367,8 +369,9 @@ 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] -killing = [20.0] => [α ~ 0.0] +injection = [10.0] => [N ~ Pre(N) + M] +killing = ModelingToolkit.SymbolicDiscreteCallback( + [20.0] => [α ~ 0.0], discrete_parameters = α, iv = t) p = [α => 100.0, M => 50] @mtkbuild osys = ODESystem(eqs, t, [N], [α, M]; @@ -386,7 +389,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`, ... @@ -395,10 +398,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, @@ -409,8 +412,10 @@ example: @variables x(t) @parameters c(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 = [1.0 => [c ~ 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,15 +428,15 @@ 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 ~ 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/docs/src/basics/MTKLanguage.md b/docs/src/basics/MTKLanguage.md index e91f2bcb67..ba6d2c34b5 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,6 +224,7 @@ 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 ``` @@ -231,13 +233,14 @@ end - 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 +251,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 ``` 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/docs/src/tutorials/fmi.md b/docs/src/tutorials/fmi.md index ef00477c78..0e01393652 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 = BrownFullBasicInit()); isinput(adder.a) isinput(adder.b) isoutput(adder.out) From bacfd6c16635112e5001ca62c29f3d79141262c8 Mon Sep 17 00:00:00 2001 From: vyudu Date: Sat, 22 Mar 2025 07:08:56 -0400 Subject: [PATCH 025/235] fix: fix `reinitializealg` usage in FMIExt --- ext/MTKFMIExt.jl | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/ext/MTKFMIExt.jl b/ext/MTKFMIExt.jl index 5cfe9a82ef..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, reinitializealg = SciMLBase.NoInit(), type, name) 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 @@ -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, reinitializealg) 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, reinitializealg) # guarded in case there are no outputs/states and the variable is `[]`. symbolic_type(__mtk_internal_o) == NotSymbolic() || push!(params, __mtk_internal_o) From e28af63a18a482ededa8a8f3a56ea05d374d5fd2 Mon Sep 17 00:00:00 2001 From: vyudu Date: Thu, 20 Mar 2025 14:55:28 -0400 Subject: [PATCH 026/235] fix: do not distribute shifts inside `Pre` and `Initial` --- src/structural_transformation/utils.jl | 1 + 1 file changed, 1 insertion(+) diff --git a/src/structural_transformation/utils.jl b/src/structural_transformation/utils.jl index fa02f0b3cd..191c25ab68 100644 --- a/src/structural_transformation/utils.jl +++ b/src/structural_transformation/utils.jl @@ -557,6 +557,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) From 7ff1dda61034a6efacb1206bb84437bd060525b5 Mon Sep 17 00:00:00 2001 From: vyudu Date: Tue, 11 Mar 2025 18:19:58 -0400 Subject: [PATCH 027/235] test: update tests to account for new `Pre` semantics --- test/accessor_functions.jl | 23 +- test/discrete_system.jl | 1 - test/extensions/ad.jl | 3 +- test/fmi/fmi.jl | 4 +- test/funcaffect.jl | 5 +- test/initializationsystem.jl | 4 +- test/jumpsystem.jl | 41 +- test/model_parsing.jl | 2 +- test/mtkparameters.jl | 3 +- test/odesystem.jl | 63 +- test/parameter_dependencies.jl | 30 +- test/symbolic_events.jl | 909 ++++++++++++---------------- test/symbolic_indexing_interface.jl | 6 +- test/symbolic_parameters.jl | 6 +- 14 files changed, 494 insertions(+), 606 deletions(-) diff --git a/test/accessor_functions.jl b/test/accessor_functions.jl index 7ce477155b..4136736a8b 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( @@ -149,20 +149,19 @@ 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], alg_eqs = [O ~ (d + p_bot) * X_bot + Y]) + mid_dev = ModelingToolkit.SymbolicDiscreteCallback( + 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, 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/discrete_system.jl b/test/discrete_system.jl index 3897d17984..43ee771b2b 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/extensions/ad.jl b/test/extensions/ad.jl index 14649b6bb6..73b1710812 100644 --- a/test/extensions/ad.jl +++ b/test/extensions/ad.jl @@ -60,7 +60,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/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) 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/initializationsystem.jl b/test/initializationsystem.jl index 7c512d37af..2cd8499e69 100644 --- a/test/initializationsystem.jl +++ b/test/initializationsystem.jl @@ -1317,9 +1317,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]) diff --git a/test/jumpsystem.jl b/test/jumpsystem.jl index 6c96055270..c5dbe1c56c 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) @@ -11,9 +12,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 +60,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) @@ -247,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)) @@ -274,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 @@ -284,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] @@ -304,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) @@ -345,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() @@ -387,9 +388,9 @@ 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]) + j3 = VariableRateJump(p3, [x3 ~ Pre(x3) + 1, x4 ~ Pre(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]) @@ -427,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) @@ -469,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], [α, β]) @@ -537,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) @@ -555,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/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 diff --git a/test/mtkparameters.jl b/test/mtkparameters.jl index b7acbb84a8..809da4df94 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], diff --git a/test/odesystem.jl b/test/odesystem.jl index e113e5d2c2..b707d119fc 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 @@ -1015,24 +1016,27 @@ 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()) +@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()) + # 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 + @test sol1.u ≈ sol2.u[2:end] +end # Requires fix in symbolics for `linear_expansion(p * x, D(y))` @test_skip begin @@ -1179,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 @@ -1520,8 +1526,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 = [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 @@ -1581,15 +1591,16 @@ 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)) diff --git a/test/parameter_dependencies.jl b/test/parameter_dependencies.jl index 31881e1ca8..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 +using ModelingToolkit: t_nounits as t, D_nounits as D, SymbolicDiscreteCallback, + SymbolicContinuousCallback using OrdinaryDiffEq using StochasticDiffEq using JumpProcesses @@ -10,14 +11,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 +204,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 +226,8 @@ 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 +247,7 @@ end end @testset "SDESystem" begin - @parameters σ ρ β + @parameters σ(t) ρ β @variables x(t) y(t) z(t) eqs = [D(x) ~ σ * (y - x), @@ -269,7 +271,8 @@ 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,17 +286,17 @@ end @testset "JumpSystem" begin rng = StableRNG(12345) - @parameters β γ + @parameters β γ(t) @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) @@ -308,7 +311,8 @@ 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 e72626849a..48faa76aa3 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, + SymbolicDiscreteCallback, 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,74 @@ 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 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 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 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 - - e = SymbolicContinuousCallbacks(eqs) - @test e isa Vector{SymbolicContinuousCallback} - @test isequal(e[].eqs, eqs) - @test e[].affect == NULL_AFFECT - - e = SymbolicContinuousCallbacks(eqs[] => affect) - @test e isa Vector{SymbolicContinuousCallback} - @test isequal(e[].eqs, eqs) - @test e[].affect == affect - - e = SymbolicContinuousCallbacks(eqs => affect) - @test e isa Vector{SymbolicContinuousCallback} - @test isequal(e[].eqs, eqs) - @test e[].affect == affect - - e = SymbolicContinuousCallbacks([eqs[] => affect]) - @test e isa Vector{SymbolicContinuousCallback} - @test isequal(e[].eqs, eqs) - @test e[].affect == affect - - 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 end @testset "ImperativeAffect constructors" begin @@ -341,409 +201,357 @@ 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] - -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 - -# 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. -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) - -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) - -# 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 - -## https://github.com/SciML/ModelingToolkit.jl/issues/1528 -Dₜ = D +@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 -@parameters u(t) [input = true] # Indicate that this is a controlled input -@parameters y(t) [output = true] # Indicate that this is a measured output + sol = solve(prob, Tsit5()) + @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 -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) + @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-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 -model = Model(sin(30t)) -sys = structural_simplify(model) -@test isempty(ModelingToolkit.continuous_events(sys)) -let - 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 +@testset "Bouncing Ball" begin + ###### 1D Bounce + @variables x(t)=1 v(t)=0 - @parameters k t1 t2 - @variables A(t) B(t) + root_eqs = [x ~ 0] + affect = [v ~ -Pre(v)] - cond1 = (t == t1) - affect1 = [A ~ A + 1] - cb1 = cond1 => affect1 - cond2 = (t == t2) - affect2 = [k ~ 1.0] - cb2 = cond2 => affect2 + @named ball = ODESystem( + [D(x) ~ v + D(v) ~ -9.8], t, continuous_events = root_eqs => affect) - ∂ₜ = 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) + @test only(continuous_events(ball)) == + SymbolicContinuousCallback(Equation[x ~ 0], Equation[v ~ -Pre(v)]) + ball = structural_simplify(ball) - cond1a = (t == t1) - affect1a = [A ~ 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 + @test length(ModelingToolkit.continuous_events(ball)) == 1 - # 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) + 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.0 + t0 = 0.0 + cond.f_iip(out, [0, 0, 0, 0], p0, t0) + @test out ≈ [0, 1.5, -1.5] - # 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) + 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 - # 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) +# 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 - eps()])[vmeasured][] == sol([0.23])[vmeasured][] # test the hold property +end - # 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) +## https://github.com/SciML/ModelingToolkit.jl/issues/1528 +@testset "Handle Empty Events" begin + Dₜ = D - # 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) + @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 -let - function testsol(ssys, u0, p, tspan; tstops = Float64[], paramtotest = nothing, +@testset "SDE/ODESystem Discrete Callbacks" begin + function testsol( + sys, probtype, solver, 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) + 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.0, 1.0]) + @test isapprox(sol(4.0)[1], 2 * exp(-2.0); rtol = 1e-6) sol end - @parameters k t1 t2 + @parameters k(t) t1 t2 @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] cb2 = cond2 => affect2 + cb2 = SymbolicDiscreteCallback(cb2, discrete_parameters = [k], iv = t) ∂ₜ = D eqs = [∂ₜ(A) ~ -k * A] - @named ssys = SDESystem(eqs, [0.0], t, [A], [k, t1, t2], - discrete_events = [cb1, cb2]) + @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(ssys, u0, p, tspan; tstops = [1.0, 2.0], paramtotest = k) + 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 ~ 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]) @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 + 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‵ = [2.0] => affect2 + 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(ssys‵, u0, p, tspan; paramtotest = k) + 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(ssys3, u0, p, tspan; tstops = [1.0], paramtotest = k) + 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) - setp(integrator, p.k)(integrator, 1.0) + 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‵‵]) - testsol(ssys4, u0, p, tspan; tstops = [1.0], paramtotest = k) + 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(ssys5, u0, p, tspan; tstops = [1.0, 2.0], paramtotest = k) + 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(ssys6, u0, p, tspan; tstops = [1.0, 2.0], paramtotest = k) + 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 = cond3 => affect3 + 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(ssys7, u0, p, (0.0, 10.0); tstops = [1.0, 2.0]) + + 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 -let rng = rng +@testset "JumpSystem Discrete Callbacks" begin function testsol(jsys, u0, p, tspan; tstops = Float64[], paramtotest = nothing, N = 40000, kwargs...) jsys = complete(jsys) @@ -751,22 +559,23 @@ let rng = rng 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) + paramtotest === nothing || (@test sol.ps[paramtotest] == [0.0, 1.0]) @test sol(40.0)[1] == 0 sol end - @parameters k t1 t2 + @parameters k(t) t1 t2 @variables A(t) B(t) + eqs = [MassActionJump(k, [A => 1], [A => -1])] cond1 = (t == t1) - affect1 = [A ~ A + 1] + 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) - 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] @@ -774,7 +583,7 @@ let rng = rng 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 ~ A] cb1a = cond1a => affect1a @named jsys1 = JumpSystem(eqs, t, [A, B], [k, t1, t2], discrete_events = [cb1a, cb2]) u0′ = [A => 1, B => 0] @@ -784,7 +593,7 @@ let rng = rng # 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 + 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) @@ -810,7 +619,7 @@ let rng = rng testsol(jsys6, u0, p, tspan; tstops = [1.0, 2.0], rng, paramtotest = k) end -let +@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 @@ -829,8 +638,8 @@ let 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(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 @@ -1066,12 +875,12 @@ end @testset "Discrete variable timeseries" begin @variables x(t) @parameters a(t) b(t) c(t) - cb1 = [x ~ 1.0] => [a ~ -a] + 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 = 1.0 => [c ~ t] + 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]) @@ -1083,6 +892,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 @@ -1257,7 +1067,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) @@ -1270,7 +1080,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( @@ -1278,7 +1088,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()) @@ -1344,7 +1154,7 @@ end 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_broken !SciMLBase.successful_retcode(solve(prob, Rodas5())) cb = [x ~ 0.0] => [y ~ 1] @mtkbuild pend = ODESystem(eqs, t; continuous_events = [cb]) @@ -1361,7 +1171,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 @@ -1370,7 +1180,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() @@ -1378,7 +1188,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 @@ -1435,6 +1245,67 @@ end @test 100.0 ∈ sol2[sys2.wd2.θ] 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 ~ 5.0] => [x ~ Pre(x) + 0.1] + @mtkbuild pend = ODESystem(eqs, t, continuous_events = c_evt) + 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.0] => [x ~ Pre(x) + y^2] + @mtkbuild pend = ODESystem(eqs, t, continuous_events = c_evt) + 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 = x)^2 + sol(5.000001, idxs = y)^2, 1, rtol = 1e-4) + + # Impossible affect errors + 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.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) + @mtkbuild pend = ODESystem(eqs, t, continuous_events = c_evt) + 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), 0.1, rtol = 1e-4) + + # Proper re-initialization after parameter change + 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) + prob = ODEProblem(sys, [x => 1.0], (0.0, 10.0), [g => 2]) + 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, 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.0, 10.0), [g => 2], guesses = [y => 0]) + sol = solve(prob, FBDF()) + @test prob.ps[g] == sol.ps[g] +end + @testset "Array parameter updates of parent components in ImperativeEffect" begin function child(vals; name, max_time = 0.1) vars = @variables begin diff --git a/test/symbolic_indexing_interface.jl b/test/symbolic_indexing_interface.jl index 8b3da5fd72..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 @@ -230,7 +231,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 = [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)) 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 1c1907163190cc243dcb24d58ceb5529644bbf1e Mon Sep 17 00:00:00 2001 From: vyudu Date: Wed, 5 Mar 2025 15:22:28 -0500 Subject: [PATCH 028/235] refactor: update symbolic event parsing in system constructors --- src/systems/diffeqs/odesystem.jl | 4 ++-- src/systems/diffeqs/sdesystem.jl | 6 ++++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/systems/diffeqs/odesystem.jl b/src/systems/diffeqs/odesystem.jl index 18208cb3af..e061706416 100644 --- a/src/systems/diffeqs/odesystem.jl +++ b/src/systems/diffeqs/odesystem.jl @@ -335,9 +335,9 @@ 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) + 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 d743143e46..016154e1cc 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) + + 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 From f96227ed9ad2806a79cff175d7721c75305807ea Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Fri, 9 May 2025 12:44:23 +0530 Subject: [PATCH 029/235] feat: implement `==` for `DiscreteSystem` Co-authored-by: vyudu --- src/systems/discrete_system/discrete_system.jl | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/systems/discrete_system/discrete_system.jl b/src/systems/discrete_system/discrete_system.jl index 64c0c2c8e0..8b392e6145 100644 --- a/src/systems/discrete_system/discrete_system.jl +++ b/src/systems/discrete_system/discrete_system.jl @@ -429,4 +429,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 From 9de14e8591f92f8b8f14cad0ab78eb3e033557c2 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Fri, 9 May 2025 12:44:36 +0530 Subject: [PATCH 030/235] feat: implement `==` for `ImplicitDiscreteSystem` Co-authored-by: vyudu --- .../discrete_system/implicit_discrete_system.jl | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/systems/discrete_system/implicit_discrete_system.jl b/src/systems/discrete_system/implicit_discrete_system.jl index 3956c089d4..71f2112a61 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 From 540f3fbc3faa18d1c889e783e4712c960ebb1416 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Fri, 9 May 2025 12:45:18 +0530 Subject: [PATCH 031/235] feat: allow passing `cachesyms` to `ImplicitDiscreteFunction` codegen Co-authored-by: vyudu --- src/systems/discrete_system/implicit_discrete_system.jl | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/systems/discrete_system/implicit_discrete_system.jl b/src/systems/discrete_system/implicit_discrete_system.jl index 71f2112a61..401b03f571 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, 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 +287,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) From 57716f025e4abea028841005026e02079d0d691b Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Fri, 9 May 2025 12:45:46 +0530 Subject: [PATCH 032/235] fix: update error checking for `ImplicitDiscreteProblem` initial conditions Co-authored-by: vyudu --- src/systems/discrete_system/implicit_discrete_system.jl | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/systems/discrete_system/implicit_discrete_system.jl b/src/systems/discrete_system/implicit_discrete_system.jl index 401b03f571..8fc5f7127f 100644 --- a/src/systems/discrete_system/implicit_discrete_system.jl +++ b/src/systems/discrete_system/implicit_discrete_system.jl @@ -298,12 +298,9 @@ 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)) && - 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)).") - 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 104450f4c2e30e6e7484a33e58bcac842280d733 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Fri, 9 May 2025 12:46:00 +0530 Subject: [PATCH 033/235] feat: add `resid_prototype` for `ImplicitDiscreteFunction` Co-authored-by: vyudu --- src/systems/discrete_system/implicit_discrete_system.jl | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/systems/discrete_system/implicit_discrete_system.jl b/src/systems/discrete_system/implicit_discrete_system.jl index 8fc5f7127f..b818ffcc8d 100644 --- a/src/systems/discrete_system/implicit_discrete_system.jl +++ b/src/systems/discrete_system/implicit_discrete_system.jl @@ -379,6 +379,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.") @@ -393,6 +399,7 @@ function SciMLBase.ImplicitDiscreteFunction{iip, specialize}( sys = sys, observed = observedfun, analytic = analytic, + resid_prototype = resid_prototype, kwargs...) end From aa2b5dfafdb4ec14c208a3656886dc49931b7d0d Mon Sep 17 00:00:00 2001 From: vyudu Date: Tue, 11 Mar 2025 18:19:58 -0400 Subject: [PATCH 034/235] fix: fix discrete variable detection in `IndexCache` --- src/systems/index_cache.jl | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/systems/index_cache.jl b/src/systems/index_cache.jl index d0b687c212..948af4dfa5 100644 --- a/src/systems/index_cache.jl +++ b/src/systems/index_cache.jl @@ -117,10 +117,11 @@ 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 else error("Unhandled affect type $(typeof(affect))") end From d4e4d4d3efdd68cfded1b642eff1ffa2f05a2410 Mon Sep 17 00:00:00 2001 From: vyudu Date: Wed, 12 Mar 2025 11:37:33 -0400 Subject: [PATCH 035/235] fix: update callback and jump codegen in JumpProblem --- src/systems/jumps/jumpsystem.jl | 41 +++++++++------------------------ 1 file changed, 11 insertions(+), 30 deletions(-) diff --git a/src/systems/jumps/jumpsystem.jl b/src/systems/jumps/jumpsystem.jl index 34b6df3bf5..348c103511 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) @@ -230,8 +216,10 @@ function JumpSystem(eqs, iv, unknowns, ps; end end - cont_callbacks = SymbolicContinuousCallbacks(continuous_events) - disc_callbacks = SymbolicDiscreteCallbacks(discrete_events) + 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, @@ -282,15 +270,13 @@ 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 +285,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 = generate_affect_function(js, vrj.affect!) VariableRateJump(rate, affect; save_positions = vrj.save_positions) end @@ -308,10 +293,9 @@ 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 - affect = $affect VariableRateJump(rate, affect) end @@ -323,8 +307,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 = generate_affect_function(js, crj.affect!) ConstantRateJump(rate, affect) end @@ -332,10 +315,9 @@ 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 - affect = $affect ConstantRateJump(rate, affect) end @@ -574,8 +556,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, From 07f16a3d1f52ee6d2f102bd824b80682a069666a Mon Sep 17 00:00:00 2001 From: vyudu Date: Mon, 24 Mar 2025 15:46:15 -0400 Subject: [PATCH 036/235] refactor: update `@mtkmodel` to account for new callback syntax --- src/systems/model_parsing.jl | 49 ++++++++++++++++++++++++------------ 1 file changed, 33 insertions(+), 16 deletions(-) diff --git a/src/systems/model_parsing.jl b/src/systems/model_parsing.jl index 024c249363..2a852813a2 100644 --- a/src/systems/model_parsing.jl +++ b/src/systems/model_parsing.jl @@ -129,8 +129,9 @@ function _model_macro(mod, fullname::Union{Expr, Symbol}, expr, isconnector) Ref(dict), [:constants, :defaults, :kwargs, :structural_parameters]) sys = :($type($(flatten_equations)(equations), $iv, variables, parameters; - name, description = $description, systems, gui_metadata = $gui_metadata, defaults, - costs = [$(costs...)], constraints = [$(cons...)], consolidate = $consolidate)) + name, description = $description, systems, gui_metadata = $gui_metadata, + continuous_events = [$(c_evts...)], discrete_events = [$(d_evts...)], + defaults, costs = [$(costs...)], constraints = [$(cons...)], consolidate = $consolidate)) if length(ext) == 0 push!(exprs.args, :(var"#___sys___" = $sys)) @@ -141,16 +142,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 @@ -1144,8 +1135,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 @@ -1153,12 +1152,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, :($(QuoteNode(arg.args[1])) => $(arg.args[2]))) + end + kwargs +end + function parse_constraints!(cons, dict, body) dict[:constraints] = [] Base.remove_linenums!(body) From d12abee6f7d0acf3ae4d948f71b9729e151ca5da Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Fri, 9 May 2025 13:02:57 +0530 Subject: [PATCH 037/235] fix: do not error when simplifying implicit `DiscreteSystem`s Co-authored-by: vyudu --- src/systems/systems.jl | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/systems/systems.jl b/src/systems/systems.jl index e417337a95..cb8bbde15c 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) From 512029662d4aa88f6f857f3e9ae3ba5260bf92f2 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Fri, 9 May 2025 13:03:10 +0530 Subject: [PATCH 038/235] fix: propagate events when simplifying `SDESystem` Co-authored-by: vyudu --- src/systems/systems.jl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/systems/systems.jl b/src/systems/systems.jl index cb8bbde15c..4e4a7f7ba4 100644 --- a/src/systems/systems.jl +++ b/src/systems/systems.jl @@ -157,9 +157,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)) - @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 1e9c8f2209b7d9eddd36bb191ca05d429d00326c Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Fri, 9 May 2025 15:04:36 +0530 Subject: [PATCH 039/235] fix: unwrap `discrete_parameters` in `make_affect` --- src/systems/callbacks.jl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/systems/callbacks.jl b/src/systems/callbacks.jl index 8a5c6131f3..3ed6fed268 100644 --- a/src/systems/callbacks.jl +++ b/src/systems/callbacks.jl @@ -273,6 +273,8 @@ function make_affect(affect::Vector{Equation}; discrete_parameters = Any[], end discrete_parameters isa AbstractVector || (discrete_parameters = [discrete_parameters]) + discrete_parameters = unwrap.(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).") From 43bc65949aa841fd8354b9c43d9bc57108abb03c Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Fri, 9 May 2025 15:05:36 +0530 Subject: [PATCH 040/235] fix: implement `tovar(::Arr)` --- src/parameters.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/parameters.jl b/src/parameters.jl index 91121b7cbb..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::Num) = Num(tovar(value(s))) +tovar(s::Union{Num, Symbolics.Arr}) = wrap(tovar(unwrap(s))) """ $(SIGNATURES) From 805b23dac73dd3eb9a58b443a5c2f64d7a6a1ca1 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Mon, 12 May 2025 12:23:56 +0530 Subject: [PATCH 041/235] fix: fix propagation of `reinitializealg` in `SymbolicDiscreteCallback` --- src/systems/callbacks.jl | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/systems/callbacks.jl b/src/systems/callbacks.jl index 3ed6fed268..4e24f1b765 100644 --- a/src/systems/callbacks.jl +++ b/src/systems/callbacks.jl @@ -438,9 +438,12 @@ struct SymbolicDiscreteCallback <: AbstractCallback c = is_timed_condition(condition) ? condition : value(scalarize(condition)) if isnothing(reinitializealg) - reinitializealg = SciMLBase.CheckInit() - else - reinitializealg = SciMLBase.NoInit() + if any(a -> (a isa FunctionalAffect || a isa ImperativeAffect), + [affect, initialize, finalize]) + reinitializealg = SciMLBase.CheckInit() + else + reinitializealg = SciMLBase.NoInit() + end end new(c, make_affect(affect; kwargs...), make_affect(initialize; kwargs...), From dff62b0fe73fc34ebc2ed9ae5059f5bd6ee6a42a Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Tue, 13 May 2025 00:30:06 +0530 Subject: [PATCH 042/235] fix: FMI ME state management callback shouldn't ever trigger or reinitialize --- ext/MTKFMIExt.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/MTKFMIExt.jl b/ext/MTKFMIExt.jl index 32023d0749..d155544ce9 100644 --- a/ext/MTKFMIExt.jl +++ b/ext/MTKFMIExt.jl @@ -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) + (t == t - 1), step_affect; finalize = finalize_affect, reinitializealg = SciMLBase.NoInit()) push!(params, wrapper) append!(observed, der_observed) From 48be180d66e652e4f191710d7e414bf8576f7150 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Mon, 14 Apr 2025 23:13:05 +0530 Subject: [PATCH 043/235] refactor: remove old `System` function --- src/systems/systems.jl | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/systems/systems.jl b/src/systems/systems.jl index 4e4a7f7ba4..f606b6b63b 100644 --- a/src/systems/systems.jl +++ b/src/systems/systems.jl @@ -1,8 +1,3 @@ -function System(eqs::AbstractVector{<:Equation}, iv, args...; name = nothing, - kw...) - ODESystem(eqs, iv, args...; name, kw..., checks = false) -end - const REPEATED_SIMPLIFICATION_MESSAGE = "Structural simplification cannot be applied to a completed system. Double simplification is not allowed." struct RepeatedStructuralSimplificationError <: Exception end From 509643c2e04bfa9d4aa02993cbbf4ae8cf66df49 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Wed, 14 May 2025 12:11:39 +0530 Subject: [PATCH 044/235] refactor: remove `odesystem.jl` --- src/ModelingToolkit.jl | 8 +- src/systems/diffeqs/odesystem.jl | 860 ------------------------------- 2 files changed, 1 insertion(+), 867 deletions(-) delete mode 100644 src/systems/diffeqs/odesystem.jl diff --git a/src/ModelingToolkit.jl b/src/ModelingToolkit.jl index b8bbebfbbb..d959c6fabc 100644 --- a/src/ModelingToolkit.jl +++ b/src/ModelingToolkit.jl @@ -170,11 +170,6 @@ include("systems/optimization/optimizationsystem.jl") include("systems/optimization/modelingtoolkitize.jl") include("systems/nonlinear/nonlinearsystem.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") @@ -267,8 +262,7 @@ export AbstractTimeDependentSystem, AbstractTimeIndependentSystem, AbstractMultivariateSystem -export ODESystem, - ODEFunction, ODEFunctionExpr, ODEProblemExpr, convert_system, +export ODEFunction, ODEFunctionExpr, ODEProblemExpr, convert_system, add_accumulations, System export DAEFunctionExpr, DAEProblemExpr export SDESystem, SDEFunction, SDEFunctionExpr, SDEProblemExpr diff --git a/src/systems/diffeqs/odesystem.jl b/src/systems/diffeqs/odesystem.jl deleted file mode 100644 index e061706416..0000000000 --- a/src/systems/diffeqs/odesystem.jl +++ /dev/null @@ -1,860 +0,0 @@ -""" -$(TYPEDEF) - -A system of ordinary differential equations. - -# Fields -$(FIELDS) - -# Example - -```julia -using ModelingToolkit -using ModelingToolkit: t_nounits as t, D_nounits as D - -@parameters σ ρ β -@variables x(t) y(t) z(t) - -eqs = [D(x) ~ σ*(y-x), - D(y) ~ x*(ρ-z)-y, - D(z) ~ x*y - β*z] - -@named de = ODESystem(eqs,t,[x,y,z],[σ,ρ,β],tspan=(0, 1000.0)) -``` -""" -struct ODESystem <: AbstractODESystem - """ - A tag for the system. If two systems have the same tag, then they are - structurally identical. - """ - tag::UInt - """The ODEs defining the system.""" - eqs::Vector{Equation} - """Independent variable.""" - iv::BasicSymbolic{Real} - """ - Dependent (unknown) variables. Must not contain the independent variable. - - N.B.: If `torn_matching !== nothing`, this includes all variables. Actual - ODE unknowns are determined by the `SelectedState()` entries in `torn_matching`. - """ - unknowns::Vector - """Parameter variables. Must not contain the independent variable.""" - ps::Vector - """Time span.""" - tspan::Union{NTuple{2, Any}, Nothing} - """Array variables.""" - var_to_name::Any - """Control parameters (some subset of `ps`).""" - ctrls::Vector - """Observed equations.""" - observed::Vector{Equation} - """System of constraints that must be satisfied by the solution to the system.""" - constraintsystem::Union{Nothing, ConstraintsSystem} - """A set of expressions defining the costs of the system for optimal control.""" - costs::Vector - """Takes the cost vector and returns a scalar for optimization.""" - consolidate::Union{Nothing, Function} - """ - Time-derivative matrix. Note: this field will not be defined until - [`calculate_tgrad`](@ref) is called on the system. - """ - tgrad::RefValue{Vector{Num}} - """ - Jacobian matrix. Note: this field will not be defined until - [`calculate_jacobian`](@ref) is called on the system. - """ - jac::RefValue{Any} - """ - Control Jacobian matrix. Note: this field will not be defined until - [`calculate_control_jacobian`](@ref) is called on the system. - """ - ctrl_jac::RefValue{Any} - """ - Note: this field will not be defined until - [`generate_factorized_W`](@ref) is called on the system. - """ - Wfact::RefValue{Matrix{Num}} - """ - Note: this field will not be defined until - [`generate_factorized_W`](@ref) is called on the system. - """ - Wfact_t::RefValue{Matrix{Num}} - """ - The name of the system. - """ - name::Symbol - """ - A description of the system. - """ - description::String - """ - The internal systems. These are required to have unique names. - """ - systems::Vector{ODESystem} - """ - The default values to use when initial conditions and/or - parameters are not supplied in `ODEProblem`. - """ - defaults::Dict - """ - The guesses to use as the initial conditions for the - initialization system. - """ - guesses::Dict - """ - Tearing result specifying how to solve the system. - """ - torn_matching::Union{Matching, Nothing} - """ - The system for performing the initialization. - """ - initializesystem::Union{Nothing, NonlinearSystem} - """ - Extra equations to be enforced during the initialization sequence. - """ - initialization_eqs::Vector{Equation} - """ - The schedule for the code generation process. - """ - schedule::Any - """ - Type of the system. - """ - connector_type::Any - """ - Inject assignment statements before the evaluation of the RHS function. - """ - preface::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. - """ - discrete_events::Vector{SymbolicDiscreteCallback} - """ - Topologically sorted parameter dependency equations, where all symbols are parameters and - the LHS is a single parameter. - """ - parameter_dependencies::Vector{Equation} - """ - Mapping of conditions which should be true throughout the solution process to corresponding error - messages. These will be added to the equations when calling `debug_system`. - """ - assertions::Dict{BasicSymbolic, String} - """ - Metadata for the system, to be used by downstream packages. - """ - metadata::Any - """ - Metadata for MTK GUI. - """ - gui_metadata::Union{Nothing, GUIMetadata} - """ - A boolean indicating if the given `ODESystem` represents a system of DDEs. - """ - is_dde::Bool - """ - A list of points to provide to the solver as tstops. Uses the same syntax as discrete - events. - """ - tstops::Vector{Any} - """ - Cache for intermediate tearing state. - """ - tearing_state::Any - """ - Substitutions generated by tearing. - """ - substitutions::Any - """ - If false, then `sys.x` no longer performs namespacing. - """ - namespacing::Bool - """ - If true, denotes the model will not be modified any further. - """ - complete::Bool - """ - Cached data for fast symbolic indexing. - """ - index_cache::Union{Nothing, IndexCache} - """ - A list of discrete subsystems. - """ - discrete_subsystems::Any - """ - A list of actual unknowns needed to be solved by solvers. - """ - solved_unknowns::Union{Nothing, Vector{Any}} - """ - A vector of vectors of indices for the split parameters. - """ - split_idxs::Union{Nothing, Vector{Vector{Int}}} - """ - The analysis points removed by transformations, representing connections to be - ignored. The first element of the tuple analysis points connecting systems and - the second are ones connecting variables (for the trivial form of `connect`). - """ - ignored_connections::Union{ - Nothing, Tuple{Vector{IgnoredAnalysisPoint}, Vector{IgnoredAnalysisPoint}}} - """ - The hierarchical parent system before simplification. - """ - parent::Any - - function ODESystem( - tag, deqs, iv, dvs, ps, tspan, var_to_name, ctrls, - observed, constraints, costs, consolidate, tgrad, - jac, ctrl_jac, Wfact, Wfact_t, name, description, systems, defaults, guesses, - torn_matching, initializesystem, initialization_eqs, schedule, - connector_type, preface, cevents, - devents, parameter_dependencies, assertions = Dict{BasicSymbolic, String}(), - metadata = nothing, gui_metadata = nothing, is_dde = false, - tstops = [], tearing_state = nothing, substitutions = nothing, - namespacing = true, complete = false, index_cache = nothing, - discrete_subsystems = nothing, solved_unknowns = nothing, - split_idxs = nothing, ignored_connections = nothing, parent = nothing; - checks::Union{Bool, Int} = true) - if checks == true || (checks & CheckComponents) > 0 - check_independent_variables([iv]) - check_variables(dvs, iv) - check_parameters(ps, iv) - check_equations(deqs, iv) - check_equations(equations(cevents), iv) - check_subsystems(systems) - end - if checks == true || (checks & CheckUnits) > 0 - u = __get_unit_type(dvs, ps, iv) - check_units(u, deqs) - end - new(tag, deqs, iv, dvs, ps, tspan, var_to_name, - ctrls, observed, constraints, costs, consolidate, tgrad, jac, - ctrl_jac, Wfact, Wfact_t, name, description, systems, defaults, guesses, torn_matching, - initializesystem, initialization_eqs, schedule, connector_type, preface, - cevents, devents, parameter_dependencies, assertions, metadata, - gui_metadata, is_dde, tstops, tearing_state, substitutions, namespacing, - complete, index_cache, - discrete_subsystems, solved_unknowns, split_idxs, ignored_connections, parent) - end -end - -function ODESystem(deqs::AbstractVector{<:Equation}, iv, dvs, ps; - controls = Num[], - observed = Equation[], - constraints = Any[], - costs = Num[], - consolidate = nothing, - systems = ODESystem[], - tspan = nothing, - name = nothing, - description = "", - default_u0 = Dict(), - default_p = Dict(), - defaults = _merge(Dict(default_u0), Dict(default_p)), - guesses = Dict(), - initializesystem = nothing, - initialization_eqs = Equation[], - schedule = nothing, - connector_type = nothing, - preface = nothing, - continuous_events = nothing, - discrete_events = nothing, - parameter_dependencies = Equation[], - assertions = Dict(), - checks = true, - metadata = nothing, - gui_metadata = nothing, - is_dde = nothing, - tstops = [], - discover_from_metadata = true) - name === nothing && - throw(ArgumentError("The `name` keyword must be provided. Please consider using the `@named` macro")) - @assert all(control -> any(isequal.(control, ps)), controls) "All controls must also be parameters." - - constraintsystem = nothing - if !isempty(constraints) - constraintsystem = process_constraint_system(constraints, dvs, ps, iv) - for p in parameters(constraintsystem) - !in(p, Set(ps)) && push!(ps, p) - end - end - - if !isempty(costs) - coststs, costps = process_costs(costs, dvs, ps, iv) - for p in costps - !in(p, Set(ps)) && push!(ps, p) - end - end - costs = wrap.(costs) - - iv′ = value(iv) - ps′ = value.(ps) - ctrl′ = value.(controls) - dvs′ = value.(dvs) - dvs′ = filter(x -> !isdelay(x, iv), dvs′) - - parameter_dependencies, ps′ = process_parameter_dependencies( - parameter_dependencies, ps′) - if !(isempty(default_u0) && isempty(default_p)) - Base.depwarn( - "`default_u0` and `default_p` are deprecated. Use `defaults` instead.", - :ODESystem, force = true) - end - defaults = Dict{Any, Any}(todict(defaults)) - guesses = Dict{Any, Any}(todict(guesses)) - var_to_name = Dict() - let defaults = discover_from_metadata ? defaults : Dict(), - guesses = discover_from_metadata ? guesses : Dict() - - process_variables!(var_to_name, defaults, guesses, dvs′) - process_variables!(var_to_name, defaults, guesses, ps′) - process_variables!( - var_to_name, defaults, guesses, [eq.lhs for eq in parameter_dependencies]) - process_variables!( - var_to_name, defaults, guesses, [eq.rhs for eq in parameter_dependencies]) - end - defaults = Dict{Any, Any}(value(k) => value(v) - for (k, v) in pairs(defaults) if v !== nothing) - guesses = Dict{Any, Any}(value(k) => value(v) - for (k, v) in pairs(guesses) if v !== nothing) - - isempty(observed) || collect_var_to_name!(var_to_name, (eq.lhs for eq in observed)) - - tgrad = RefValue(EMPTY_TGRAD) - jac = RefValue{Any}(EMPTY_JAC) - ctrl_jac = RefValue{Any}(EMPTY_JAC) - Wfact = RefValue(EMPTY_JAC) - Wfact_t = RefValue(EMPTY_JAC) - sysnames = nameof.(systems) - if length(unique(sysnames)) != length(sysnames) - throw(ArgumentError("System names must be unique.")) - end - - 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 - - if !isempty(systems) && !isnothing(constraintsystem) - conssystems = ConstraintsSystem[] - for sys in systems - cons = get_constraintsystem(sys) - cons !== nothing && push!(conssystems, cons) - end - @set! constraintsystem.systems = conssystems - end - costs = wrap.(costs) - - if length(costs) > 1 && isnothing(consolidate) - error("Must specify a consolidation function for the costs vector.") - elseif length(costs) == 1 && isnothing(consolidate) - consolidate = u -> u[1] - end - - assertions = Dict{BasicSymbolic, Any}(unwrap(k) => v for (k, v) in assertions) - - ODESystem(Threads.atomic_add!(SYSTEM_COUNT, UInt(1)), - deqs, iv′, dvs′, ps′, tspan, var_to_name, ctrl′, observed, - constraintsystem, costs, consolidate, tgrad, jac, - ctrl_jac, Wfact, Wfact_t, name, description, systems, - defaults, guesses, nothing, initializesystem, - initialization_eqs, schedule, connector_type, preface, cont_callbacks, - disc_callbacks, parameter_dependencies, assertions, - metadata, gui_metadata, is_dde, tstops, checks = checks) -end - -function ODESystem(eqs, iv; kwargs...) - diffvars, allunknowns, ps, eqs = process_equations(eqs, iv) - - for eq in get(kwargs, :parameter_dependencies, Equation[]) - collect_vars!(allunknowns, ps, eq, iv) - end - - for ssys in get(kwargs, :systems, ODESystem[]) - collect_scoped_vars!(allunknowns, ps, ssys, iv) - end - - for v in allunknowns - isdelay(v, iv) || continue - collect_vars!(allunknowns, ps, arguments(v)[1], iv) - end - - new_ps = OrderedSet() - for p in ps - if iscall(p) && operation(p) === getindex - par = arguments(p)[begin] - if Symbolics.shape(Symbolics.unwrap(par)) !== Symbolics.Unknown() && - all(par[i] in ps for i in eachindex(par)) - push!(new_ps, par) - else - push!(new_ps, p) - end - else - push!(new_ps, p) - end - end - algevars = setdiff(allunknowns, diffvars) - - return ODESystem(eqs, iv, collect(Iterators.flatten((diffvars, algevars))), - collect(new_ps); kwargs...) -end - -# NOTE: equality does not check cached Jacobian -function Base.:(==)(sys1::ODESystem, sys2::ODESystem) - sys1 === sys2 && return true - iv1 = get_iv(sys1) - iv2 = get_iv(sys2) - isequal(iv1, iv2) && - isequal(nameof(sys1), nameof(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)) && - _eq_unordered(continuous_events(sys1), continuous_events(sys2)) && - _eq_unordered(discrete_events(sys1), discrete_events(sys2)) && - all(s1 == s2 for (s1, s2) in zip(get_systems(sys1), get_systems(sys2))) && - isequal(get_constraintsystem(sys1), get_constraintsystem(sys2)) && - _eq_unordered(get_costs(sys1), get_costs(sys2)) -end - -function flatten(sys::ODESystem, noeqs = false) - systems = get_systems(sys) - if isempty(systems) - return sys - else - return ODESystem(noeqs ? Equation[] : equations(sys), - get_iv(sys), - unknowns(sys), - parameters(sys; initial_parameters = true), - parameter_dependencies = parameter_dependencies(sys), - guesses = guesses(sys), - observed = observed(sys), - continuous_events = continuous_events(sys), - discrete_events = discrete_events(sys), - defaults = defaults(sys), - name = nameof(sys), - description = description(sys), - initialization_eqs = initialization_equations(sys), - assertions = assertions(sys), - is_dde = is_dde(sys), - tstops = symbolic_tstops(sys), - metadata = get_metadata(sys), - checks = false, - # without this, any defaults/guesses obtained from metadata that were - # later removed by the user will be re-added. Right now, we just want to - # retain `defaults(sys)` as-is. - discover_from_metadata = false) - end -end - -ODESystem(eq::Equation, args...; kwargs...) = ODESystem([eq], args...; kwargs...) - -""" - build_explicit_observed_function(sys, ts; kwargs...) -> Function(s) - -Generates a function that computes the observed value(s) `ts` in the system `sys`, while making the assumption that there are no cycles in the equations. - -## Arguments -- `sys`: The system for which to generate the function -- `ts`: The symbolic observed values whose value should be computed - -## Keywords -- `return_inplace = false`: If true and the observed value is a vector, then return both the in place and out of place methods. -- `expression = false`: Generates a Julia `Expr`` computing the observed value if `expression` is true -- `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 = 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. -- `mkarray`: only used if the output is an array (that is, `!isscalar(ts)` and `ts` is not a tuple, in which case the result will always be a tuple). Called as `mkarray(ts, output_type)` where `ts` are the expressions to put in the array and `output_type` is the argument of the same name passed to build_explicit_observed_function. -- `cse = true`: Whether to use Common Subexpression Elimination (CSE) to generate a more efficient function. - -## Returns - -The return value will be either: -* a single function `f_oop` if the input is a scalar or if the input is a Vector but `return_inplace` is false -* the out of place and in-place functions `(f_ip, f_oop)` if `return_inplace` is true and the input is a `Vector` - -The function(s) `f_oop` (and potentially `f_ip`) will be: -* `RuntimeGeneratedFunction`s by default, -* A Julia `Expr` if `expression` is true, -* A directly evaluated Julia function in the module `eval_module` if `eval_expression` is true and `expression` is false. - -The signatures will be of the form `g(...)` with arguments: - -- `output` for in-place functions -- `unknowns` if `param_only` is `false` -- `inputs` if `inputs` is an array of symbolic inputs that should be available in `ts` -- `p...` unconditionally; note that in the case of `MTKParameters` more than one parameters argument may be present, so it must be splatted -- `t` if the system is time-dependent; for example `NonlinearSystem` will not have `t` - -For example, a function `g(op, unknowns, p..., inputs, t)` will be the in-place function generated if `return_inplace` is true, `ts` is a vector, -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 = Any[], - disturbance_inputs = Any[], - disturbance_argument = false, - expression = false, - eval_expression = false, - eval_module = @__MODULE__, - output_type = Array, - checkbounds = true, - ps = parameters(sys; initial_parameters = true), - return_inplace = false, - param_only = false, - op = Operator, - throw = true, - cse = true, - mkarray = nothing) - is_tuple = ts isa Tuple - if is_tuple - ts = collect(ts) - output_type = Tuple - end - - allsyms = all_symbols(sys) - if symbolic_type(ts) == NotSymbolic() && ts isa AbstractArray - ts = map(x -> symbol_to_symbolic(sys, x; allsyms), ts) - else - ts = symbol_to_symbolic(sys, ts; allsyms) - end - - vs = ModelingToolkit.vars(ts; op) - namespace_subs = Dict() - ns_map = Dict{Any, Any}(renamespace(sys, obs) => obs for obs in observables(sys)) - for sym in unknowns(sys) - ns_map[renamespace(sys, sym)] = sym - if iscall(sym) && operation(sym) === getindex - ns_map[renamespace(sys, arguments(sym)[1])] = arguments(sym)[1] - end - end - for sym in full_parameters(sys) - ns_map[renamespace(sys, sym)] = sym - if iscall(sym) && operation(sym) === getindex - ns_map[renamespace(sys, arguments(sym)[1])] = arguments(sym)[1] - end - end - allsyms = Set(all_symbols(sys)) - iv = has_iv(sys) ? get_iv(sys) : nothing - for var in vs - var = unwrap(var) - newvar = get(ns_map, var, nothing) - if newvar !== nothing - namespace_subs[var] = newvar - var = newvar - end - if throw && !var_in_varlist(var, allsyms, iv) - Base.throw(ArgumentError("Symbol $var is not present in the system.")) - end - end - ts = fast_substitute(ts, namespace_subs) - - obsfilter = if param_only - if is_split(sys) - let ic = get_index_cache(sys) - eq -> !(ContinuousTimeseries() in ic.observed_syms_to_timeseries[eq.lhs]) - end - else - Returns(false) - end - else - Returns(true) - end - dvs = if param_only - () - else - (unknowns(sys),) - end - 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 !isempty(disturbance_inputs) - # Disturbance inputs may or may not be included as inputs, depending on disturbance_argument - ps = setdiff(ps, disturbance_inputs) - end - if disturbance_argument - disturbance_inputs = (disturbance_inputs,) - else - disturbance_inputs = () - end - ps = reorder_parameters(sys, ps) - iv = if is_time_dependent(sys) - (get_iv(sys),) - else - () - end - args = (dvs..., inputs..., ps..., iv..., disturbance_inputs...) - p_start = length(dvs) + length(inputs) + 1 - p_end = length(dvs) + length(inputs) + length(ps) - fns = build_function_wrapper( - sys, ts, args...; p_start, p_end, filter_observed = obsfilter, - output_type, mkarray, try_namespaced = true, expression = Val{true}, cse) - if fns isa Tuple - if expression - return return_inplace ? fns : fns[1] - end - oop, iip = eval_or_rgf.(fns; eval_expression, eval_module) - f = GeneratedFunctionWrapper{( - p_start + is_dde(sys), length(args) - length(ps) + 1 + is_dde(sys), is_split(sys))}( - oop, iip) - return return_inplace ? (f, f) : f - else - if expression - return fns - end - f = eval_or_rgf(fns; eval_expression, eval_module) - f = GeneratedFunctionWrapper{( - p_start + is_dde(sys), length(args) - length(ps) + 1 + is_dde(sys), is_split(sys))}( - f, nothing) - return f - end -end - -function populate_delays(delays::Set, obsexprs, histfn, sys, sym) - _vars_util = vars(sym) - for v in _vars_util - v in delays && continue - iscall(v) && issym(operation(v)) && (args = arguments(v); length(args) == 1) && - iscall(only(args)) || continue - - idx = variable_index(sys, operation(v)(get_iv(sys))) - idx === nothing && error("Delay term $v is not an unknown in the system") - push!(delays, v) - push!(obsexprs, v ← histfn(only(args))[idx]) - end -end - -function _eq_unordered(a, b) - # a and b may be multidimensional - # e.g. comparing noiseeqs of SDESystem - a = vec(a) - b = vec(b) - length(a) === length(b) || return false - n = length(a) - idxs = Set(1:n) - for x in a - idx = findfirst(isequal(x), b) - # loop since there might be multiple identical entries in a/b - # and while we might have already matched the first there could - # be a second that is equal to x - while idx !== nothing && !(idx in idxs) - idx = findnext(isequal(x), b, idx + 1) - end - idx === nothing && return false - delete!(idxs, idx) - end - return true -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. -""" -$(TYPEDSIGNATURES) - -Convert a `NonlinearSystem` to an `ODESystem` or converts an `ODESystem` to a -new `ODESystem` with a different independent variable. -""" -function convert_system(::Type{<:ODESystem}, sys, t; name = nameof(sys)) - isempty(observed(sys)) || - throw(ArgumentError("`convert_system` cannot handle reduced model (i.e. observed(sys) is non-empty).")) - t = value(t) - varmap = Dict() - sts = unknowns(sys) - newsts = similar(sts, Any) - for (i, s) in enumerate(sts) - if iscall(s) - args = arguments(s) - length(args) == 1 || - throw(InvalidSystemException("Illegal unknown: $s. The unknown can have at most one argument like `x(t)`.")) - arg = args[1] - if isequal(arg, t) - newsts[i] = s - continue - end - ns = maketerm(typeof(s), operation(s), Any[t], - SymbolicUtils.metadata(s)) - newsts[i] = ns - varmap[s] = ns - else - ns = variable(getname(s); T = FnType)(t) - newsts[i] = ns - varmap[s] = ns - end - end - sub = Base.Fix2(substitute, varmap) - if sys isa AbstractODESystem - iv = only(independent_variables(sys)) - sub.x[iv] = t # otherwise the Differentials aren't fixed - end - neweqs = map(sub, equations(sys)) - defs = Dict(sub(k) => sub(v) for (k, v) in defaults(sys)) - return ODESystem(neweqs, t, newsts, parameters(sys); defaults = defs, name = name, - checks = false) -end - -""" -$(SIGNATURES) - -Add accumulation variables for `vars`. -""" -function add_accumulations(sys::ODESystem, vars = unknowns(sys)) - avars = [rename(v, Symbol(:accumulation_, getname(v))) for v in vars] - add_accumulations(sys, avars .=> vars) -end - -""" -$(SIGNATURES) - -Add accumulation variables for `vars`. `vars` is a vector of pairs in the form -of - -```julia -[cumulative_var1 => x + y, cumulative_var2 => x^2] -``` -Then, cumulative variables `cumulative_var1` and `cumulative_var2` that computes -the cumulative `x + y` and `x^2` would be added to `sys`. -""" -function add_accumulations(sys::ODESystem, vars::Vector{<:Pair}) - eqs = get_eqs(sys) - avars = map(first, vars) - if (ints = intersect(avars, unknowns(sys)); !isempty(ints)) - error("$ints already exist in the system!") - end - D = Differential(get_iv(sys)) - @set! sys.eqs = [eqs; Equation[D(a) ~ v[2] for (a, v) in zip(avars, vars)]] - @set! sys.unknowns = [get_unknowns(sys); avars] - @set! sys.defaults = merge(get_defaults(sys), Dict(a => 0.0 for a in avars)) -end - -function Base.show(io::IO, mime::MIME"text/plain", sys::ODESystem; hint = true, bold = true) - # Print general AbstractSystem information - invoke(Base.show, Tuple{typeof(io), typeof(mime), AbstractSystem}, - io, mime, sys; hint, bold) - - name = nameof(sys) - - # Print initialization equations (unique to ODESystems) - nini = length(initialization_equations(sys)) - nini > 0 && printstyled(io, "\nInitialization equations ($nini):"; bold) - nini > 0 && hint && print(io, " see initialization_equations($name)") - - return nothing -end - -""" -Build the constraint system for the ODESystem. -""" -function process_constraint_system( - constraints::Vector, sts, ps, iv; consname = :cons) - isempty(constraints) && return nothing - - constraintsts = OrderedSet() - constraintps = OrderedSet() - for cons in constraints - collect_vars!(constraintsts, constraintps, cons, iv) - union!(constraintsts, collect_applied_operators(cons, Differential)) - end - - # Validate the states. - validate_vars_and_find_ps!(constraintsts, constraintps, sts, iv) - - ConstraintsSystem( - constraints, collect(constraintsts), collect(constraintps); name = consname) -end - -""" -Process the costs for the constraint system. -""" -function process_costs(costs::Vector, sts, ps, iv) - coststs = OrderedSet() - costps = OrderedSet() - for cost in costs - collect_vars!(coststs, costps, cost, iv) - end - - validate_vars_and_find_ps!(coststs, costps, sts, iv) - coststs, costps -end - -""" -Validate that all the variables in an auxiliary system of the ODESystem (constraint or costs) are -well-formed states or parameters. - - Callable/delay variables (e.g. of the form x(0.6) should be unknowns of the system (and have one arg, etc.) - - Callable/delay parameters should be parameters of the system - -Return the set of additional parameters found in the system, e.g. in x(p) ~ 3 then p should be added as a -parameter of the system. -""" -function validate_vars_and_find_ps!(auxvars, auxps, sysvars, iv) - sts = Set(sysvars) - - for var in auxvars - if !iscall(var) - var ∈ sts || - throw(ArgumentError("Time-independent variable $var is not an unknown of the system.")) - elseif length(arguments(var)) > 1 - throw(ArgumentError("Too many arguments for variable $var.")) - elseif length(arguments(var)) == 1 - if iscall(var) && operation(var) isa Differential - var = only(arguments(var)) - end - arg = only(arguments(var)) - operation(var)(iv) ∈ sts || - throw(ArgumentError("Variable $var is not a variable of the ODESystem. Called variables must be variables of the ODESystem.")) - - isequal(arg, iv) || isparameter(arg) || arg isa Integer || - arg isa AbstractFloat || - throw(ArgumentError("Invalid argument specified for variable $var. The argument of the variable should be either $iv, a parameter, or a value specifying the time that the constraint holds.")) - - (isparameter(arg) && !isequal(arg, iv)) && push!(auxps, arg) - else - var ∈ sts && - @warn "Variable $var has no argument. It will be interpreted as $var($iv), and the constraint will apply to the entire interval." - end - end -end - -""" -Generate a function that takes a solution object and computes the cost function obtained by coalescing the costs vector. -""" -function generate_cost_function(sys::ODESystem, kwargs...) - costs = get_costs(sys) - consolidate = get_consolidate(sys) - iv = get_iv(sys) - - ps = parameters(sys; initial_parameters = false) - sts = unknowns(sys) - np = length(ps) - ns = length(sts) - stidxmap = Dict([v => i for (i, v) in enumerate(sts)]) - pidxmap = Dict([v => i for (i, v) in enumerate(ps)]) - - @variables sol(..)[1:ns] - for st in vars(costs) - x = operation(st) - t = only(arguments(st)) - idx = stidxmap[x(iv)] - - costs = map(c -> Symbolics.fast_substitute(c, Dict(x(t) => sol(t)[idx])), costs) - end - - _p = reorder_parameters(sys, ps) - fs = build_function_wrapper(sys, costs, sol, _p..., t; output_type = Array, kwargs...) - vc_oop, vc_iip = eval_or_rgf.(fs) - - cost(sol, p, t) = consolidate(vc_oop(sol, p, t)) - return cost -end From a69b40324500cad9339f0261eeec8eca555bf079 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Sun, 20 Apr 2025 14:00:59 +0530 Subject: [PATCH 045/235] refactor: add `_eq_unordered` to `utils.jl` fixmeup --- src/utils.jl | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/src/utils.jl b/src/utils.jl index a2034ec58a..8505b798b1 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -1311,3 +1311,34 @@ function var_in_varlist(var, varlist::AbstractSet, iv) # delayed variables (isdelay(var, iv) && var_in_varlist(operation(var)(iv), varlist, iv)) end + +""" + $(TYPEDSIGNATURES) + +Check if `a` and `b` contain identical elements, regardless of order. This is not +equivalent to `issetequal` because the latter does not account for identical elements that +have different multiplicities in `a` and `b`. +""" +function _eq_unordered(a::AbstractArray, b::AbstractArray) + # a and b may be multidimensional + # e.g. comparing noiseeqs of SDESystem + a = vec(a) + b = vec(b) + length(a) === length(b) || return false + n = length(a) + idxs = Set(1:n) + for x in a + idx = findfirst(isequal(x), b) + # loop since there might be multiple identical entries in a/b + # and while we might have already matched the first there could + # be a second that is equal to x + while idx !== nothing && !(idx in idxs) + idx = findnext(isequal(x), b, idx + 1) + end + idx === nothing && return false + delete!(idxs, idx) + end + return true +end + +_eq_unordered(a, b) = isequal(a, b) From 66255d02343725462a4093c83fa02a10b60a50b2 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Wed, 14 May 2025 12:34:20 +0530 Subject: [PATCH 046/235] refactor: move `flatten_equations` to `utils.jl` --- src/utils.jl | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/src/utils.jl b/src/utils.jl index 8505b798b1..8f5ac311c5 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -1342,3 +1342,31 @@ function _eq_unordered(a::AbstractArray, b::AbstractArray) end _eq_unordered(a, b) = isequal(a, b) + +""" + $(TYPEDSIGNATURES) + +Given a list of equations where some may be array equations, flatten the array equations +without scalarizing occurrences of array variables and return the new list of equations. +""" +function flatten_equations(eqs::Vector{Equation}) + mapreduce(vcat, eqs; init = Equation[]) do eq + islhsarr = eq.lhs isa AbstractArray || Symbolics.isarraysymbolic(eq.lhs) + isrhsarr = eq.rhs isa AbstractArray || Symbolics.isarraysymbolic(eq.rhs) + if islhsarr || isrhsarr + islhsarr && isrhsarr || + error(""" + LHS ($(eq.lhs)) and RHS ($(eq.rhs)) must either both be array expressions \ + or both scalar + """) + size(eq.lhs) == size(eq.rhs) || + error(""" + Size of LHS ($(eq.lhs)) and RHS ($(eq.rhs)) must match: got \ + $(size(eq.lhs)) and $(size(eq.rhs)) + """) + return vec(collect(eq.lhs) .~ collect(eq.rhs)) + else + eq + end + end +end From 3e6f7f82bc62dd40da8e62bbe607d8eb498110d8 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Sun, 20 Apr 2025 15:21:23 +0530 Subject: [PATCH 047/235] refactor: remove `sdesystem.jl` --- src/ModelingToolkit.jl | 3 +- src/systems/diffeqs/sdesystem.jl | 941 ------------------------------- 2 files changed, 1 insertion(+), 943 deletions(-) delete mode 100644 src/systems/diffeqs/sdesystem.jl diff --git a/src/ModelingToolkit.jl b/src/ModelingToolkit.jl index d959c6fabc..f29df3ece3 100644 --- a/src/ModelingToolkit.jl +++ b/src/ModelingToolkit.jl @@ -170,7 +170,6 @@ include("systems/optimization/optimizationsystem.jl") include("systems/optimization/modelingtoolkitize.jl") include("systems/nonlinear/nonlinearsystem.jl") -include("systems/diffeqs/sdesystem.jl") include("systems/diffeqs/abstractodesystem.jl") include("systems/nonlinear/homotopy_continuation.jl") include("systems/nonlinear/modelingtoolkitize.jl") @@ -265,7 +264,7 @@ export AbstractTimeDependentSystem, export ODEFunction, ODEFunctionExpr, ODEProblemExpr, convert_system, add_accumulations, System export DAEFunctionExpr, DAEProblemExpr -export SDESystem, SDEFunction, SDEFunctionExpr, SDEProblemExpr +export SDEFunction, SDEFunctionExpr, SDEProblemExpr export SystemStructure export DiscreteSystem, DiscreteProblem, DiscreteFunction, DiscreteFunctionExpr export ImplicitDiscreteSystem, ImplicitDiscreteProblem, ImplicitDiscreteFunction, diff --git a/src/systems/diffeqs/sdesystem.jl b/src/systems/diffeqs/sdesystem.jl deleted file mode 100644 index 016154e1cc..0000000000 --- a/src/systems/diffeqs/sdesystem.jl +++ /dev/null @@ -1,941 +0,0 @@ -""" -$(TYPEDEF) - -A system of stochastic differential equations. - -# Fields -$(FIELDS) - -# Example - -```julia -using ModelingToolkit -using ModelingToolkit: t_nounits as t, D_nounits as D - -@parameters σ ρ β -@variables x(t) y(t) z(t) - -eqs = [D(x) ~ σ*(y-x), - D(y) ~ x*(ρ-z)-y, - D(z) ~ x*y - β*z] - -noiseeqs = [0.1*x, - 0.1*y, - 0.1*z] - -@named de = SDESystem(eqs,noiseeqs,t,[x,y,z],[σ,ρ,β]; tspan = (0, 1000.0)) -``` -""" -struct SDESystem <: AbstractODESystem - """ - A tag for the system. If two systems have the same tag, then they are - structurally identical. - """ - tag::UInt - """The expressions defining the drift term.""" - eqs::Vector{Equation} - """The expressions defining the diffusion term.""" - noiseeqs::AbstractArray - """Independent variable.""" - iv::BasicSymbolic{Real} - """Dependent variables. Must not contain the independent variable.""" - unknowns::Vector - """Parameter variables. Must not contain the independent variable.""" - ps::Vector - """Time span.""" - tspan::Union{NTuple{2, Any}, Nothing} - """Array variables.""" - var_to_name::Any - """Control parameters (some subset of `ps`).""" - ctrls::Vector - """Observed equations.""" - observed::Vector{Equation} - """ - Time-derivative matrix. Note: this field will not be defined until - [`calculate_tgrad`](@ref) is called on the system. - """ - tgrad::RefValue - """ - Jacobian matrix. Note: this field will not be defined until - [`calculate_jacobian`](@ref) is called on the system. - """ - jac::RefValue - """ - Control Jacobian matrix. Note: this field will not be defined until - [`calculate_control_jacobian`](@ref) is called on the system. - """ - ctrl_jac::RefValue{Any} - """ - Note: this field will not be defined until - [`generate_factorized_W`](@ref) is called on the system. - """ - Wfact::RefValue - """ - Note: this field will not be defined until - [`generate_factorized_W`](@ref) is called on the system. - """ - Wfact_t::RefValue - """ - The name of the system. - """ - name::Symbol - """ - A description of the system. - """ - description::String - """ - The internal systems. These are required to have unique names. - """ - systems::Vector{SDESystem} - """ - The default values to use when initial conditions and/or - parameters are not supplied in `ODEProblem`. - """ - defaults::Dict - """ - The guesses to use as the initial conditions for the - initialization system. - """ - guesses::Dict - """ - The system for performing the initialization. - """ - initializesystem::Union{Nothing, NonlinearSystem} - """ - Extra equations to be enforced during the initialization sequence. - """ - initialization_eqs::Vector{Equation} - """ - 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 - analog to `SciMLBase.DiscreteCallback` that executes an affect when a given condition is - true at the end of an integration step. - """ - discrete_events::Vector{SymbolicDiscreteCallback} - """ - Topologically sorted parameter dependency equations, where all symbols are parameters and - the LHS is a single parameter. - """ - parameter_dependencies::Vector{Equation} - """ - Mapping of conditions which should be true throughout the solution process to corresponding error - messages. These will be added to the equations when calling `debug_system`. - """ - assertions::Dict{BasicSymbolic, String} - """ - Metadata for the system, to be used by downstream packages. - """ - metadata::Any - """ - Metadata for MTK GUI. - """ - gui_metadata::Union{Nothing, GUIMetadata} - """ - If false, then `sys.x` no longer performs namespacing. - """ - namespacing::Bool - """ - If true, denotes the model will not be modified any further. - """ - complete::Bool - """ - Cached data for fast symbolic indexing. - """ - index_cache::Union{Nothing, IndexCache} - """ - The hierarchical parent system before simplification. - """ - parent::Any - """ - Signal for whether the noise equations should be treated as a scalar process. This should only - be `true` when `noiseeqs isa Vector`. - """ - is_scalar_noise::Bool - """ - A boolean indicating if the given `ODESystem` represents a system of DDEs. - """ - is_dde::Bool - isscheduled::Bool - tearing_state::Any - - function SDESystem(tag, deqs, neqs, iv, dvs, ps, tspan, var_to_name, ctrls, observed, - tgrad, jac, ctrl_jac, Wfact, Wfact_t, name, description, systems, defaults, - guesses, initializesystem, initialization_eqs, connector_type, - cevents, devents, parameter_dependencies, assertions = Dict{ - BasicSymbolic, Nothing}, - metadata = nothing, gui_metadata = nothing, namespacing = true, - complete = false, index_cache = nothing, parent = nothing, is_scalar_noise = false, - is_dde = false, - isscheduled = false, - tearing_state = nothing; - checks::Union{Bool, Int} = true) - if checks == true || (checks & CheckComponents) > 0 - check_independent_variables([iv]) - check_variables(dvs, iv) - check_parameters(ps, iv) - check_equations(deqs, iv) - check_equations(neqs, dvs) - if size(neqs, 1) != length(deqs) - throw(ArgumentError("Noise equations ill-formed. Number of rows must match number of drift equations. size(neqs,1) = $(size(neqs,1)) != length(deqs) = $(length(deqs))")) - end - check_equations(equations(cevents), iv) - if is_scalar_noise && neqs isa AbstractMatrix - throw(ArgumentError("Noise equations ill-formed. Received a matrix of noise equations of size $(size(neqs)), but `is_scalar_noise` was set to `true`. Scalar noise is only compatible with an `AbstractVector` of noise equations.")) - end - check_subsystems(systems) - end - if checks == true || (checks & CheckUnits) > 0 - u = __get_unit_type(dvs, ps, iv) - check_units(u, deqs, neqs) - end - new(tag, deqs, neqs, iv, dvs, ps, tspan, var_to_name, ctrls, observed, tgrad, jac, - ctrl_jac, Wfact, Wfact_t, name, description, systems, - defaults, guesses, initializesystem, initialization_eqs, connector_type, cevents, - devents, parameter_dependencies, assertions, metadata, gui_metadata, namespacing, - complete, index_cache, parent, is_scalar_noise, is_dde, isscheduled, tearing_state) - end -end - -function SDESystem(deqs::AbstractVector{<:Equation}, neqs::AbstractArray, iv, dvs, ps; - controls = Num[], - observed = Num[], - systems = SDESystem[], - tspan = nothing, - default_u0 = Dict(), - default_p = Dict(), - defaults = _merge(Dict(default_u0), Dict(default_p)), - guesses = Dict(), - initializesystem = nothing, - initialization_eqs = Equation[], - name = nothing, - description = "", - connector_type = nothing, - checks = true, - continuous_events = nothing, - discrete_events = nothing, - parameter_dependencies = Equation[], - assertions = Dict{BasicSymbolic, String}(), - metadata = nothing, - gui_metadata = nothing, - index_cache = nothing, - parent = nothing, - is_scalar_noise = false, - is_dde = nothing) - name === nothing && - throw(ArgumentError("The `name` keyword must be provided. Please consider using the `@named` macro")) - iv′ = value(iv) - dvs′ = value.(dvs) - ps′ = value.(ps) - ctrl′ = value.(controls) - parameter_dependencies, ps′ = process_parameter_dependencies( - parameter_dependencies, ps′) - - sysnames = nameof.(systems) - if length(unique(sysnames)) != length(sysnames) - throw(ArgumentError("System names must be unique.")) - end - if !(isempty(default_u0) && isempty(default_p)) - Base.depwarn( - "`default_u0` and `default_p` are deprecated. Use `defaults` instead.", - :SDESystem, force = true) - end - - defaults = Dict{Any, Any}(todict(defaults)) - guesses = Dict{Any, Any}(todict(guesses)) - var_to_name = Dict() - process_variables!(var_to_name, defaults, guesses, dvs′) - process_variables!(var_to_name, defaults, guesses, ps′) - process_variables!( - var_to_name, defaults, guesses, [eq.lhs for eq in parameter_dependencies]) - process_variables!( - var_to_name, defaults, guesses, [eq.rhs for eq in parameter_dependencies]) - defaults = Dict{Any, Any}(value(k) => value(v) - for (k, v) in pairs(defaults) if v !== nothing) - guesses = Dict{Any, Any}(value(k) => value(v) - for (k, v) in pairs(guesses) if v !== nothing) - - isempty(observed) || collect_var_to_name!(var_to_name, (eq.lhs for eq in observed)) - - tgrad = RefValue(EMPTY_TGRAD) - jac = RefValue{Any}(EMPTY_JAC) - ctrl_jac = RefValue{Any}(EMPTY_JAC) - Wfact = RefValue(EMPTY_JAC) - Wfact_t = RefValue(EMPTY_JAC) - - 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 - assertions = Dict{BasicSymbolic, Any}(unwrap(k) => v for (k, v) in assertions) - SDESystem(Threads.atomic_add!(SYSTEM_COUNT, UInt(1)), - deqs, neqs, iv′, dvs′, ps′, tspan, var_to_name, ctrl′, observed, tgrad, jac, - ctrl_jac, Wfact, Wfact_t, name, description, systems, defaults, guesses, - initializesystem, initialization_eqs, connector_type, - cont_callbacks, disc_callbacks, parameter_dependencies, assertions, metadata, gui_metadata, - true, false, index_cache, parent, is_scalar_noise, is_dde; checks = checks) -end - -function SDESystem(sys::ODESystem, neqs; kwargs...) - SDESystem(equations(sys), neqs, get_iv(sys), unknowns(sys), parameters(sys); kwargs...) -end - -function SDESystem(eqs::Vector{Equation}, noiseeqs::AbstractArray, iv; kwargs...) - diffvars, allunknowns, ps, eqs = process_equations(eqs, iv) - - for eq in get(kwargs, :parameter_dependencies, Equation[]) - collect_vars!(allunknowns, ps, eq, iv) - end - - for ssys in get(kwargs, :systems, ODESystem[]) - collect_scoped_vars!(allunknowns, ps, ssys, iv) - end - - for v in allunknowns - isdelay(v, iv) || continue - collect_vars!(allunknowns, ps, arguments(v)[1], iv) - end - - new_ps = OrderedSet() - for p in ps - if iscall(p) && operation(p) === getindex - par = arguments(p)[begin] - if Symbolics.shape(Symbolics.unwrap(par)) !== Symbolics.Unknown() && - all(par[i] in ps for i in eachindex(par)) - push!(new_ps, par) - else - push!(new_ps, p) - end - else - push!(new_ps, p) - end - end - - # validate noise equations - noisedvs = OrderedSet() - noiseps = OrderedSet() - collect_vars!(noisedvs, noiseps, noiseeqs, iv) - for dv in noisedvs - dv ∈ allunknowns || - throw(ArgumentError("Variable $dv in noise equations is not an unknown of the system.")) - end - algevars = setdiff(allunknowns, diffvars) - return SDESystem(eqs, noiseeqs, iv, Iterators.flatten((diffvars, algevars)), - [collect(ps); collect(noiseps)]; kwargs...) -end - -function SDESystem(eq::Equation, noiseeqs::AbstractArray, args...; kwargs...) - SDESystem([eq], noiseeqs, args...; kwargs...) -end -function SDESystem(eq::Equation, noiseeq, args...; kwargs...) - SDESystem([eq], [noiseeq], args...; kwargs...) -end - -function Base.:(==)(sys1::SDESystem, sys2::SDESystem) - sys1 === sys2 && return true - iv1 = get_iv(sys1) - iv2 = get_iv(sys2) - isequal(iv1, iv2) && - isequal(nameof(sys1), nameof(sys2)) && - _eq_unordered(get_eqs(sys1), get_eqs(sys2)) && - _eq_unordered(get_noiseeqs(sys1), get_noiseeqs(sys2)) && - isequal(get_is_scalar_noise(sys1), get_is_scalar_noise(sys2)) && - _eq_unordered(get_unknowns(sys1), get_unknowns(sys2)) && - _eq_unordered(get_ps(sys1), get_ps(sys2)) && - _eq_unordered(continuous_events(sys1), continuous_events(sys2)) && - _eq_unordered(discrete_events(sys1), discrete_events(sys2)) && - all(s1 == s2 for (s1, s2) in zip(get_systems(sys1), get_systems(sys2))) -end - -""" - function ODESystem(sys::SDESystem) - -Convert an `SDESystem` to the equivalent `ODESystem` using `@brownian` variables instead -of noise equations. The returned system will not be `iscomplete` and will not have an -index cache, regardless of `iscomplete(sys)`. -""" -function ODESystem(sys::SDESystem) - neqs = get_noiseeqs(sys) - eqs = equations(sys) - is_scalar_noise = get_is_scalar_noise(sys) - nbrownian = if is_scalar_noise - length(neqs) - else - size(neqs, 2) - end - brownvars = map(1:nbrownian) do i - name = gensym(Symbol(:brown_, i)) - only(@brownian $name) - end - if is_scalar_noise - brownterms = reduce(+, neqs .* brownvars; init = 0) - neweqs = map(eqs) do eq - eq.lhs ~ eq.rhs + brownterms - end - else - if neqs isa AbstractVector - neqs = reshape(neqs, (length(neqs), 1)) - end - brownterms = neqs * brownvars - neweqs = map(eqs, brownterms) do eq, brown - eq.lhs ~ eq.rhs + brown - end - end - newsys = ODESystem(neweqs, get_iv(sys), unknowns(sys), parameters(sys); - parameter_dependencies = parameter_dependencies(sys), defaults = defaults(sys), - continuous_events = continuous_events(sys), discrete_events = discrete_events(sys), - assertions = assertions(sys), - name = nameof(sys), description = description(sys), metadata = get_metadata(sys)) - @set newsys.parent = sys -end - -function __num_isdiag_noise(mat) - for i in axes(mat, 1) - nnz = 0 - for j in axes(mat, 2) - if !isequal(mat[i, j], 0) - nnz += 1 - end - end - if nnz > 1 - return (false) - end - end - true -end -function __get_num_diag_noise(mat) - map(axes(mat, 1)) do i - for j in axes(mat, 2) - mij = mat[i, j] - if !isequal(mij, 0) - return mij - end - end - 0 - end -end - -function generate_diffusion_function(sys::SDESystem, dvs = unknowns(sys), - ps = parameters(sys; initial_parameters = true); isdde = false, kwargs...) - eqs = get_noiseeqs(sys) - p = reorder_parameters(sys, ps) - return build_function_wrapper(sys, eqs, dvs, p..., get_iv(sys); kwargs...) -end - -""" -$(TYPEDSIGNATURES) - -Choose correction_factor=-1//2 (1//2) to convert Ito -> Stratonovich (Stratonovich->Ito). -""" -function stochastic_integral_transform(sys::SDESystem, correction_factor) - name = nameof(sys) - # use the general interface - if typeof(get_noiseeqs(sys)) <: Vector - eqs = vcat([equations(sys)[i].lhs ~ get_noiseeqs(sys)[i] - for i in eachindex(unknowns(sys))]...) - de = ODESystem(eqs, get_iv(sys), unknowns(sys), parameters(sys), name = name, - checks = false) - - jac = calculate_jacobian(de, sparse = false, simplify = false) - ∇σσ′ = simplify.(jac * get_noiseeqs(sys)) - - deqs = vcat([equations(sys)[i].lhs ~ equations(sys)[i].rhs + - correction_factor * ∇σσ′[i] - for i in eachindex(unknowns(sys))]...) - else - dimunknowns, m = size(get_noiseeqs(sys)) - eqs = vcat([equations(sys)[i].lhs ~ get_noiseeqs(sys)[i] - for i in eachindex(unknowns(sys))]...) - de = ODESystem(eqs, get_iv(sys), unknowns(sys), parameters(sys), name = name, - checks = false) - - jac = calculate_jacobian(de, sparse = false, simplify = false) - ∇σσ′ = simplify.(jac * get_noiseeqs(sys)[:, 1]) - for k in 2:m - eqs = vcat([equations(sys)[i].lhs ~ get_noiseeqs(sys)[Int(i + - (k - 1) * - dimunknowns)] - for i in eachindex(unknowns(sys))]...) - de = ODESystem(eqs, get_iv(sys), unknowns(sys), parameters(sys), name = name, - checks = false) - - jac = calculate_jacobian(de, sparse = false, simplify = false) - ∇σσ′ = ∇σσ′ + simplify.(jac * get_noiseeqs(sys)[:, k]) - end - - deqs = vcat([equations(sys)[i].lhs ~ equations(sys)[i].rhs + - correction_factor * ∇σσ′[i] - for i in eachindex(unknowns(sys))]...) - end - - SDESystem(deqs, get_noiseeqs(sys), get_iv(sys), unknowns(sys), parameters(sys), - name = name, description = description(sys), - parameter_dependencies = parameter_dependencies(sys), checks = false) -end - -""" -$(TYPEDSIGNATURES) - -Measure transformation method that allows for a reduction in the variance of an estimator `Exp(g(X_t))`. -Input: Original SDE system and symbolic function `u(t,x)` with scalar output that - defines the adjustable parameters `d` in the Girsanov transformation. Optional: initial - condition for `θ0`. -Output: Modified SDESystem with additional component `θ_t` and initial value `θ0`, as well as - the weight `θ_t/θ0` as observed equation, such that the estimator `Exp(g(X_t)θ_t/θ0)` - has a smaller variance. - -Reference: -Kloeden, P. E., Platen, E., & Schurz, H. (2012). Numerical solution of SDE through computer -experiments. Springer Science & Business Media. - -# Example - -```julia -using ModelingToolkit -using ModelingToolkit: t_nounits as t, D_nounits as D - -@parameters α β -@variables x(t) y(t) z(t) - -eqs = [D(x) ~ α*x] -noiseeqs = [β*x] - -@named de = SDESystem(eqs,noiseeqs,t,[x],[α,β]) - -# define u (user choice) -u = x -θ0 = 0.1 -g(x) = x[1]^2 -demod = ModelingToolkit.Girsanov_transform(de, u; θ0=0.1) - -u0modmap = [ - x => x0 -] - -parammap = [ - α => 1.5, - β => 1.0 -] - -probmod = SDEProblem(complete(demod),u0modmap,(0.0,1.0),parammap) -ensemble_probmod = EnsembleProblem(probmod; - output_func = (sol,i) -> (g(sol[x,end])*sol[demod.weight,end],false), - ) - -simmod = solve(ensemble_probmod,EM(),dt=dt,trajectories=numtraj) -``` - -""" -function Girsanov_transform(sys::SDESystem, u; θ0 = 1.0) - name = nameof(sys) - - # register new variable θ corresponding to 1D correction process θ(t) - t = get_iv(sys) - D = Differential(t) - @variables θ(t), weight(t) - - # determine the adjustable parameters `d` given `u` - # gradient of u with respect to unknowns - grad = Symbolics.gradient(u, unknowns(sys)) - - noiseeqs = get_noiseeqs(sys) - if noiseeqs isa Vector - d = simplify.(-(noiseeqs .* grad) / u) - drift_correction = noiseeqs .* d - else - d = simplify.(-noiseeqs * grad / u) - drift_correction = noiseeqs * d - end - - # transformation adds additional unknowns θ: newX = (X,θ) - # drift function for unknowns is modified - # θ has zero drift - deqs = vcat([equations(sys)[i].lhs ~ equations(sys)[i].rhs - drift_correction[i] - for i in eachindex(unknowns(sys))]...) - deqsθ = D(θ) ~ 0 - push!(deqs, deqsθ) - - # diffusion matrix is of size d x m (d unknowns, m noise), with diagonal noise represented as a d-dimensional vector - # for diagonal noise processes with m>1, the noise process will become non-diagonal; extra unknown component but no new noise process. - # new diffusion matrix is of size d+1 x M - # diffusion for state is unchanged - - noiseqsθ = θ * d - - if noiseeqs isa Vector - m = size(noiseeqs) - if m == 1 - push!(noiseeqs, noiseqsθ) - else - noiseeqs = [Array(Diagonal(noiseeqs)); noiseqsθ'] - end - else - noiseeqs = [Array(noiseeqs); noiseqsθ'] - end - - unknown_vars = [unknowns(sys); θ] - - # return modified SDE System - SDESystem(deqs, noiseeqs, get_iv(sys), unknown_vars, parameters(sys); - defaults = Dict(θ => θ0), observed = [weight ~ θ / θ0], - name = name, description = description(sys), - parameter_dependencies = parameter_dependencies(sys), - checks = false) -end - -function DiffEqBase.SDEFunction{iip, specialize}(sys::SDESystem, dvs = unknowns(sys), - ps = parameters(sys), - u0 = nothing; - version = nothing, tgrad = false, sparse = false, - jac = false, Wfact = false, eval_expression = false, - sparsity = false, analytic = nothing, - eval_module = @__MODULE__, - 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`") - end - dvs = scalarize.(dvs) - - 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) - g_gen = generate_diffusion_function(sys, dvs, ps; expression = Val{true}, - cse, kwargs...) - g_oop, g_iip = eval_or_rgf.(g_gen; eval_expression, eval_module) - - f = GeneratedFunctionWrapper{(2, 3, is_split(sys))}(f_oop, f_iip) - g = GeneratedFunctionWrapper{(2, 3, is_split(sys))}(g_oop, g_iip) - - if tgrad - tgrad_gen = generate_tgrad(sys, dvs, ps; expression = Val{true}, cse, - kwargs...) - tgrad_oop, tgrad_iip = eval_or_rgf.(tgrad_gen; eval_expression, eval_module) - _tgrad = GeneratedFunctionWrapper{(2, 3, is_split(sys))}(tgrad_oop, tgrad_iip) - else - _tgrad = nothing - end - - if jac - jac_gen = generate_jacobian(sys, dvs, ps; expression = Val{true}, - sparse = sparse, cse, kwargs...) - jac_oop, jac_iip = eval_or_rgf.(jac_gen; eval_expression, eval_module) - - _jac = GeneratedFunctionWrapper{(2, 3, is_split(sys))}(jac_oop, jac_iip) - else - _jac = nothing - end - - if Wfact - tmp_Wfact, tmp_Wfact_t = generate_factorized_W(sys, dvs, ps, true; - expression = Val{true}, cse, kwargs...) - Wfact_oop, Wfact_iip = eval_or_rgf.(tmp_Wfact; eval_expression, eval_module) - Wfact_oop_t, Wfact_iip_t = eval_or_rgf.(tmp_Wfact_t; eval_expression, eval_module) - - _Wfact = GeneratedFunctionWrapper{(2, 4, is_split(sys))}(Wfact_oop, Wfact_iip) - _Wfact_t = GeneratedFunctionWrapper{(2, 4, is_split(sys))}(Wfact_oop_t, Wfact_iip_t) - else - _Wfact, _Wfact_t = nothing, nothing - end - - M = calculate_massmatrix(sys) - if sparse - uElType = u0 === nothing ? Float64 : eltype(u0) - W_prototype = similar(W_sparsity(sys), uElType) - else - W_prototype = nothing - end - - _M = if sparse && !(u0 === nothing || M === I) - SparseArrays.sparse(M) - elseif u0 === nothing || M === I - M - elseif M isa Diagonal - Diagonal(ArrayInterface.restructure(u0, diag(M))) - else - ArrayInterface.restructure(u0 .* u0', M) - end - - observedfun = ObservedFunctionCache( - sys; eval_expression, eval_module, checkbounds = get(kwargs, :checkbounds, false), cse) - - SDEFunction{iip, specialize}(f, g; - sys = sys, - jac = _jac === nothing ? nothing : _jac, - tgrad = _tgrad === nothing ? nothing : _tgrad, - mass_matrix = _M, - jac_prototype = W_prototype, - observed = observedfun, - sparsity = sparsity ? W_sparsity(sys) : nothing, - analytic = analytic, - Wfact = _Wfact === nothing ? nothing : _Wfact, - Wfact_t = _Wfact_t === nothing ? nothing : _Wfact_t, - initialization_data) -end - -""" -```julia -DiffEqBase.SDEFunction{iip}(sys::SDESystem, dvs = sys.unknowns, ps = sys.ps; - version = nothing, tgrad = false, sparse = false, - jac = false, Wfact = false, kwargs...) where {iip} -``` - -Create an `SDEFunction` from the [`SDESystem`](@ref). The arguments `dvs` and `ps` -are used to set the order of the dependent variable and parameter vectors, -respectively. -""" -function DiffEqBase.SDEFunction(sys::SDESystem, args...; kwargs...) - SDEFunction{true}(sys, args...; kwargs...) -end - -function DiffEqBase.SDEFunction{true}(sys::SDESystem, args...; - kwargs...) - SDEFunction{true, SciMLBase.AutoSpecialize}(sys, args...; kwargs...) -end - -function DiffEqBase.SDEFunction{false}(sys::SDESystem, args...; - kwargs...) - SDEFunction{false, SciMLBase.FullSpecialize}(sys, args...; kwargs...) -end - -""" -```julia -DiffEqBase.SDEFunctionExpr{iip}(sys::AbstractODESystem, dvs = unknowns(sys), - ps = parameters(sys); - version = nothing, tgrad = false, - jac = false, Wfact = false, - skipzeros = true, fillzeros = true, - sparse = false, - kwargs...) where {iip} -``` - -Create a Julia expression for an `SDEFunction` from the [`SDESystem`](@ref). -The arguments `dvs` and `ps` are used to set the order of the dependent -variable and parameter vectors, respectively. -""" -struct SDEFunctionExpr{iip} end - -function SDEFunctionExpr{iip}(sys::SDESystem, dvs = unknowns(sys), - ps = parameters(sys), u0 = nothing; - version = nothing, tgrad = false, - jac = false, Wfact = false, - 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`") - end - idx = iip ? 2 : 1 - f = generate_function(sys, dvs, ps; expression = Val{true}, kwargs...)[idx] - g = generate_diffusion_function(sys, dvs, ps; expression = Val{true}, kwargs...)[idx] - if tgrad - _tgrad = generate_tgrad(sys, dvs, ps; expression = Val{true}, kwargs...)[idx] - else - _tgrad = :nothing - end - - if jac - _jac = generate_jacobian(sys, dvs, ps; sparse = sparse, expression = Val{true}, - kwargs...)[idx] - else - _jac = :nothing - end - - M = calculate_massmatrix(sys) - _M = (u0 === nothing || M == I) ? M : ArrayInterface.restructure(u0 .* u0', M) - - if sparse - uElType = u0 === nothing ? Float64 : eltype(u0) - W_prototype = similar(W_sparsity(sys), uElType) - else - W_prototype = nothing - end - - if Wfact - tmp_Wfact, tmp_Wfact_t = generate_factorized_W( - sys, dvs, ps; expression = Val{true}, - kwargs...) - _Wfact = tmp_Wfact[idx] - _Wfact_t = tmp_Wfact_t[idx] - else - _Wfact, _Wfact_t = :nothing, :nothing - end - - ex = quote - f = $f - g = $g - tgrad = $_tgrad - jac = $_jac - W_prototype = $W_prototype - Wfact = $_Wfact - Wfact_t = $_Wfact_t - M = $_M - SDEFunction{$iip}(f, g, - jac = jac, - jac_prototype = W_prototype, - tgrad = tgrad, - Wfact = Wfact, - Wfact_t = Wfact_t, - mass_matrix = M) - end - !linenumbers ? Base.remove_linenums!(ex) : ex -end - -function SDEFunctionExpr(sys::SDESystem, args...; kwargs...) - SDEFunctionExpr{true}(sys, args...; kwargs...) -end - -function DiffEqBase.SDEProblem{iip, specialize}( - sys::SDESystem, u0map = [], tspan = get_tspan(sys), - parammap = DiffEqBase.NullParameters(); - 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`") - end - - f, u0, p = process_SciMLProblem( - SDEFunction{iip, specialize}, sys, u0map, parammap; check_length, - t = tspan === nothing ? nothing : tspan[1], kwargs...) - cbs = process_events(sys; callback, kwargs...) - sparsenoise === nothing && (sparsenoise = get(kwargs, :sparse, false)) - - noiseeqs = get_noiseeqs(sys) - is_scalar_noise = get_is_scalar_noise(sys) - if noiseeqs isa AbstractVector - noise_rate_prototype = nothing - if is_scalar_noise - noise = WienerProcess(0.0, 0.0, 0.0) - else - noise = nothing - end - elseif sparsenoise - I, J, V = findnz(SparseArrays.sparse(noiseeqs)) - noise_rate_prototype = SparseArrays.sparse(I, J, zero(eltype(u0))) - noise = nothing - else - noise_rate_prototype = zeros(eltype(u0), size(noiseeqs)) - noise = nothing - end - - kwargs = filter_kwargs(kwargs) - - # Call `remake` so it runs initialization if it is trivial - return remake(SDEProblem{iip}(f, u0, tspan, p; callback = cbs, noise, - noise_rate_prototype = noise_rate_prototype, kwargs...)) -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.") - else - error("Cannot construct SDEProblem from a normal ODESystem.") - end -end - -""" -```julia -DiffEqBase.SDEProblem{iip}(sys::SDESystem, u0map, tspan, p = parammap; - version = nothing, tgrad = false, - jac = false, Wfact = false, - checkbounds = false, sparse = false, - sparsenoise = sparse, - skipzeros = true, fillzeros = true, - linenumbers = true, parallel = SerialForm(), - kwargs...) -``` - -Generates an SDEProblem from an SDESystem and allows for automatically -symbolically calculating numerical enhancements. -""" -function DiffEqBase.SDEProblem(sys::SDESystem, args...; kwargs...) - SDEProblem{true}(sys, args...; kwargs...) -end - -function DiffEqBase.SDEProblem(sys::SDESystem, - u0map::StaticArray, - args...; - kwargs...) - SDEProblem{false, SciMLBase.FullSpecialize}(sys, u0map, args...; kwargs...) -end - -function DiffEqBase.SDEProblem{true}(sys::SDESystem, args...; kwargs...) - SDEProblem{true, SciMLBase.AutoSpecialize}(sys, args...; kwargs...) -end - -function DiffEqBase.SDEProblem{false}(sys::SDESystem, args...; kwargs...) - SDEProblem{false, SciMLBase.FullSpecialize}(sys, args...; kwargs...) -end - -""" -```julia -DiffEqBase.SDEProblemExpr{iip}(sys::AbstractODESystem, u0map, tspan, - parammap = DiffEqBase.NullParameters(); - version = nothing, tgrad = false, - jac = false, Wfact = false, - checkbounds = false, sparse = false, - linenumbers = true, parallel = SerialForm(), - kwargs...) where {iip} -``` - -Generates a Julia expression for constructing an ODEProblem from an -ODESystem and allows for automatically symbolically calculating -numerical enhancements. -""" -struct SDEProblemExpr{iip} end - -function SDEProblemExpr{iip}(sys::SDESystem, u0map, tspan, - parammap = DiffEqBase.NullParameters(); - 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`") - end - f, u0, p = process_SciMLProblem( - SDEFunctionExpr{iip}, sys, u0map, parammap; check_length, - kwargs...) - linenumbers = get(kwargs, :linenumbers, true) - sparsenoise === nothing && (sparsenoise = get(kwargs, :sparse, false)) - - noiseeqs = get_noiseeqs(sys) - is_scalar_noise = get_is_scalar_noise(sys) - if noiseeqs isa AbstractVector - noise_rate_prototype = nothing - if is_scalar_noise - noise = WienerProcess(0.0, 0.0, 0.0) - else - noise = nothing - end - elseif sparsenoise - I, J, V = findnz(SparseArrays.sparse(noiseeqs)) - noise_rate_prototype = SparseArrays.sparse(I, J, zero(eltype(u0))) - noise = nothing - else - T = u0 === nothing ? Float64 : eltype(u0) - noise_rate_prototype = zeros(T, size(get_noiseeqs(sys))) - noise = nothing - end - ex = quote - f = $f - u0 = $u0 - tspan = $tspan - p = $p - noise_rate_prototype = $noise_rate_prototype - noise = $noise - SDEProblem( - f, u0, tspan, p; noise_rate_prototype = noise_rate_prototype, noise = noise, - $(kwargs...)) - end - !linenumbers ? Base.remove_linenums!(ex) : ex -end - -function SDEProblemExpr(sys::SDESystem, args...; kwargs...) - SDEProblemExpr{true}(sys, args...; kwargs...) -end From e486b27de96e6e1f06115a9e13863c7a3f6d80ea Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Sun, 20 Apr 2025 20:18:52 +0530 Subject: [PATCH 048/235] refactor: move `__num_isdiag_noise`, `get_num_diag_noise` to location of use --- src/systems/systems.jl | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/src/systems/systems.jl b/src/systems/systems.jl index f606b6b63b..399b263178 100644 --- a/src/systems/systems.jl +++ b/src/systems/systems.jl @@ -158,6 +158,33 @@ function __structural_simplify(sys::AbstractSystem; simplify = false, end end +function __num_isdiag_noise(mat) + for i in axes(mat, 1) + nnz = 0 + for j in axes(mat, 2) + if !isequal(mat[i, j], 0) + nnz += 1 + end + end + if nnz > 1 + return (false) + end + end + true +end + +function __get_num_diag_noise(mat) + map(axes(mat, 1)) do i + for j in axes(mat, 2) + mij = mat[i, j] + if !isequal(mij, 0) + return mij + end + end + 0 + end +end + """ $(TYPEDSIGNATURES) From 11135247c8a0ffbce7bc7d35b6e3acd013c6aab5 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Sun, 20 Apr 2025 20:22:55 +0530 Subject: [PATCH 049/235] refactor: remove `discrete_system.jl` --- src/ModelingToolkit.jl | 4 +- .../discrete_system/discrete_system.jl | 442 ------------------ 2 files changed, 3 insertions(+), 443 deletions(-) delete mode 100644 src/systems/discrete_system/discrete_system.jl diff --git a/src/ModelingToolkit.jl b/src/ModelingToolkit.jl index f29df3ece3..24ffb10ee7 100644 --- a/src/ModelingToolkit.jl +++ b/src/ModelingToolkit.jl @@ -178,6 +178,8 @@ include("systems/diffeqs/first_order_transform.jl") include("systems/diffeqs/modelingtoolkitize.jl") include("systems/diffeqs/basic_transformations.jl") +include("systems/discrete_system/implicit_discrete_system.jl") + include("systems/jumps/jumpsystem.jl") include("systems/pde/pdesystem.jl") @@ -266,7 +268,7 @@ export ODEFunction, ODEFunctionExpr, ODEProblemExpr, convert_system, export DAEFunctionExpr, DAEProblemExpr export SDEFunction, SDEFunctionExpr, SDEProblemExpr export SystemStructure -export DiscreteSystem, DiscreteProblem, DiscreteFunction, DiscreteFunctionExpr +export DiscreteProblem, DiscreteFunction, DiscreteFunctionExpr export ImplicitDiscreteSystem, ImplicitDiscreteProblem, ImplicitDiscreteFunction, ImplicitDiscreteFunctionExpr export JumpSystem diff --git a/src/systems/discrete_system/discrete_system.jl b/src/systems/discrete_system/discrete_system.jl deleted file mode 100644 index 8b392e6145..0000000000 --- a/src/systems/discrete_system/discrete_system.jl +++ /dev/null @@ -1,442 +0,0 @@ -""" -$(TYPEDEF) -A system of difference equations. -# Fields -$(FIELDS) -# Example -``` -using ModelingToolkit -using ModelingToolkit: t_nounits as t -@parameters σ=28.0 ρ=10.0 β=8/3 δt=0.1 -@variables x(t)=1.0 y(t)=0.0 z(t)=0.0 -k = ShiftIndex(t) -eqs = [x(k+1) ~ σ*(y-x), - y(k+1) ~ x*(ρ-z)-y, - z(k+1) ~ x*y - β*z] -@named de = DiscreteSystem(eqs,t,[x,y,z],[σ,ρ,β]; tspan = (0, 1000.0)) # or -@named de = DiscreteSystem(eqs) -``` -""" -struct DiscreteSystem <: AbstractDiscreteSystem - """ - A tag for the system. If two systems have the same tag, then they are - structurally identical. - """ - tag::UInt - """The differential equations defining the discrete system.""" - eqs::Vector{Equation} - """Independent variable.""" - iv::BasicSymbolic{Real} - """Dependent (state) variables. Must not contain the independent variable.""" - unknowns::Vector - """Parameter variables. Must not contain the independent variable.""" - ps::Vector - """Time span.""" - tspan::Union{NTuple{2, Any}, Nothing} - """Array variables.""" - var_to_name::Any - """Observed states.""" - observed::Vector{Equation} - """ - The name of the system - """ - name::Symbol - """ - A description of the system. - """ - description::String - """ - The internal systems. These are required to have unique names. - """ - systems::Vector{DiscreteSystem} - """ - The default values to use when initial conditions and/or - parameters are not supplied in `DiscreteProblem`. - """ - defaults::Dict - """ - The guesses to use as the initial conditions for the - initialization system. - """ - guesses::Dict - """ - The system for performing the initialization. - """ - initializesystem::Union{Nothing, NonlinearSystem} - """ - Extra equations to be enforced during the initialization sequence. - """ - initialization_eqs::Vector{Equation} - """ - Inject assignment statements before the evaluation of the RHS function. - """ - preface::Any - """ - Type of the system. - """ - connector_type::Any - """ - Topologically sorted parameter dependency equations, where all symbols are parameters and - the LHS is a single parameter. - """ - parameter_dependencies::Vector{Equation} - """ - Metadata for the system, to be used by downstream packages. - """ - metadata::Any - """ - Metadata for MTK GUI. - """ - gui_metadata::Union{Nothing, GUIMetadata} - """ - Cache for intermediate tearing state. - """ - tearing_state::Any - """ - Substitutions generated by tearing. - """ - substitutions::Any - """ - If false, then `sys.x` no longer performs namespacing. - """ - namespacing::Bool - """ - If true, denotes the model will not be modified any further. - """ - complete::Bool - """ - Cached data for fast symbolic indexing. - """ - index_cache::Union{Nothing, IndexCache} - """ - The hierarchical parent system before simplification. - """ - parent::Any - isscheduled::Bool - - function DiscreteSystem(tag, discreteEqs, iv, dvs, ps, tspan, var_to_name, - observed, name, description, systems, defaults, guesses, initializesystem, - initialization_eqs, preface, connector_type, parameter_dependencies = Equation[], - metadata = nothing, gui_metadata = nothing, - tearing_state = nothing, substitutions = nothing, namespacing = true, - complete = false, index_cache = nothing, parent = nothing, - isscheduled = false; - checks::Union{Bool, Int} = true, kwargs...) - if checks == true || (checks & CheckComponents) > 0 - check_independent_variables([iv]) - check_variables(dvs, iv) - check_parameters(ps, iv) - check_subsystems(systems) - end - if checks == true || (checks & CheckUnits) > 0 - u = __get_unit_type(dvs, ps, iv) - check_units(u, discreteEqs) - end - new(tag, discreteEqs, iv, dvs, ps, tspan, var_to_name, observed, name, description, - systems, defaults, guesses, initializesystem, initialization_eqs, - preface, connector_type, parameter_dependencies, metadata, gui_metadata, - tearing_state, substitutions, namespacing, complete, index_cache, parent, - isscheduled) - end -end - -""" - $(TYPEDSIGNATURES) -Constructs a DiscreteSystem. -""" -function DiscreteSystem(eqs::AbstractVector{<:Equation}, iv, dvs, ps; - observed = Num[], - systems = DiscreteSystem[], - tspan = nothing, - name = nothing, - description = "", - default_u0 = Dict(), - default_p = Dict(), - guesses = Dict(), - initializesystem = nothing, - initialization_eqs = Equation[], - defaults = _merge(Dict(default_u0), Dict(default_p)), - preface = nothing, - connector_type = nothing, - parameter_dependencies = Equation[], - metadata = nothing, - gui_metadata = nothing, - kwargs...) - name === nothing && - throw(ArgumentError("The `name` keyword must be provided. Please consider using the `@named` macro")) - iv′ = value(iv) - dvs′ = value.(dvs) - ps′ = value.(ps) - if any(hasderiv, eqs) || any(hashold, eqs) || any(hassample, eqs) || any(hasdiff, eqs) - error("Equations in a `DiscreteSystem` can only have `Shift` operators.") - end - if !(isempty(default_u0) && isempty(default_p)) - Base.depwarn( - "`default_u0` and `default_p` are deprecated. Use `defaults` instead.", - :DiscreteSystem, force = true) - end - - defaults = Dict{Any, Any}(todict(defaults)) - guesses = Dict{Any, Any}(todict(guesses)) - var_to_name = Dict() - process_variables!(var_to_name, defaults, guesses, dvs′) - process_variables!(var_to_name, defaults, guesses, ps′) - process_variables!( - var_to_name, defaults, guesses, [eq.lhs for eq in parameter_dependencies]) - process_variables!( - var_to_name, defaults, guesses, [eq.rhs for eq in parameter_dependencies]) - defaults = Dict{Any, Any}(value(k) => value(v) - for (k, v) in pairs(defaults) if v !== nothing) - guesses = Dict{Any, Any}(value(k) => value(v) - for (k, v) in pairs(guesses) if v !== nothing) - - isempty(observed) || collect_var_to_name!(var_to_name, (eq.lhs for eq in observed)) - - sysnames = nameof.(systems) - if length(unique(sysnames)) != length(sysnames) - throw(ArgumentError("System names must be unique.")) - end - DiscreteSystem(Threads.atomic_add!(SYSTEM_COUNT, UInt(1)), - eqs, iv′, dvs′, ps′, tspan, var_to_name, observed, name, description, systems, - defaults, guesses, initializesystem, initialization_eqs, preface, connector_type, - parameter_dependencies, metadata, gui_metadata) -end - -function DiscreteSystem(eqs, iv; kwargs...) - eqs = collect(eqs) - diffvars = OrderedSet() - allunknowns = OrderedSet() - ps = OrderedSet() - iv = value(iv) - for eq in eqs - collect_vars!(allunknowns, ps, eq, iv; op = Shift) - if iscall(eq.lhs) && operation(eq.lhs) isa Shift - isequal(iv, operation(eq.lhs).t) || - throw(ArgumentError("A DiscreteSystem can only have one independent variable.")) - eq.lhs in diffvars && - throw(ArgumentError("The shift variable $(eq.lhs) is not unique in the system of equations.")) - push!(diffvars, eq.lhs) - end - end - for eq in get(kwargs, :parameter_dependencies, Equation[]) - if eq isa Pair - collect_vars!(allunknowns, ps, eq, iv) - else - collect_vars!(allunknowns, ps, eq, iv) - end - end - new_ps = OrderedSet() - for p in ps - if iscall(p) && operation(p) === getindex - par = arguments(p)[begin] - if Symbolics.shape(Symbolics.unwrap(par)) !== Symbolics.Unknown() && - all(par[i] in ps for i in eachindex(par)) - push!(new_ps, par) - else - push!(new_ps, p) - end - else - push!(new_ps, p) - end - end - return DiscreteSystem(eqs, iv, - collect(allunknowns), collect(new_ps); kwargs...) -end - -DiscreteSystem(eq::Equation, args...; kwargs...) = DiscreteSystem([eq], args...; kwargs...) - -function flatten(sys::DiscreteSystem, noeqs = false) - systems = get_systems(sys) - if isempty(systems) - return sys - else - return DiscreteSystem(noeqs ? Equation[] : equations(sys), - get_iv(sys), - unknowns(sys), - parameters(sys), - observed = observed(sys), - defaults = defaults(sys), - guesses = guesses(sys), - initialization_eqs = initialization_equations(sys), - name = nameof(sys), - description = description(sys), - metadata = get_metadata(sys), - checks = false) - end -end - -function generate_function( - sys::DiscreteSystem, dvs = unknowns(sys), ps = parameters(sys); wrap_code = identity, kwargs...) - exprs = [eq.rhs for eq in equations(sys)] - generate_custom_function(sys, exprs, dvs, ps; kwargs...) -end - -function shift_u0map_forward(sys::DiscreteSystem, u0map, defs) - iv = get_iv(sys) - updated = AnyDict() - for k in collect(keys(u0map)) - 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)).") - - updated[Shift(iv, 1)(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)))).") - else - updated[Shift(iv, op.steps + 1)(only(arguments(k)))] = v - end - end - for var in unknowns(sys) - op = operation(var) - root = getunshifted(var) - shift = getshift(var) - isnothing(root) && continue - (haskey(updated, Shift(iv, shift)(root)) || haskey(updated, var)) && continue - haskey(defs, root) || error("Initial condition for $var not provided.") - updated[var] = defs[root] - end - return updated -end - -""" - $(TYPEDSIGNATURES) -Generates an DiscreteProblem from an DiscreteSystem. -""" -function SciMLBase.DiscreteProblem( - sys::DiscreteSystem, u0map = [], tspan = get_tspan(sys), - parammap = SciMLBase.NullParameters(); - eval_module = @__MODULE__, - eval_expression = false, - kwargs... -) - if !iscomplete(sys) - 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) - eqs = equations(sys) - iv = get_iv(sys) - - u0map = to_varmap(u0map, dvs) - scalarize_varmap!(u0map) - u0map = shift_u0map_forward(sys, u0map, defaults(sys)) - f, u0, p = process_SciMLProblem( - DiscreteFunction, sys, u0map, parammap; eval_expression, eval_module, build_initializeprob = false) - u0 = f(u0, p, tspan[1]) - DiscreteProblem(f, u0, tspan, p; kwargs...) -end - -function SciMLBase.DiscreteFunction(sys::DiscreteSystem, args...; kwargs...) - DiscreteFunction{true}(sys, args...; kwargs...) -end - -function SciMLBase.DiscreteFunction{true}(sys::DiscreteSystem, args...; kwargs...) - DiscreteFunction{true, SciMLBase.AutoSpecialize}(sys, args...; kwargs...) -end - -function SciMLBase.DiscreteFunction{false}(sys::DiscreteSystem, args...; kwargs...) - DiscreteFunction{false, SciMLBase.FullSpecialize}(sys, args...; kwargs...) -end - -""" -```julia -SciMLBase.DiscreteFunction{iip}(sys::DiscreteSystem, - dvs = unknowns(sys), - ps = parameters(sys); - kwargs...) where {iip} -``` - -Create an `DiscreteFunction` from the [`DiscreteSystem`](@ref). The arguments `dvs` and `ps` -are used to set the order of the dependent variable and parameter vectors, -respectively. -""" -function SciMLBase.DiscreteFunction{iip, specialize}( - sys::DiscreteSystem, - dvs = unknowns(sys), - ps = parameters(sys), - u0 = nothing; - version = nothing, - p = nothing, - t = nothing, - eval_expression = false, - eval_module = @__MODULE__, - 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`") - end - f_gen = generate_function(sys, dvs, ps; expression = Val{true}, - expression_module = eval_module, cse, kwargs...) - f_oop, f_iip = eval_or_rgf.(f_gen; eval_expression, eval_module) - f = GeneratedFunctionWrapper{(2, 3, is_split(sys))}(f_oop, f_iip) - - if specialize === SciMLBase.FunctionWrapperSpecialize && iip - if u0 === nothing || p === nothing || t === nothing - error("u0, p, and t must be specified for FunctionWrapperSpecialize on DiscreteFunction.") - end - f = SciMLBase.wrapfun_iip(f, (u0, u0, p, t)) - end - - observedfun = ObservedFunctionCache( - sys; eval_expression, eval_module, checkbounds = get(kwargs, :checkbounds, false), cse) - - DiscreteFunction{iip, specialize}(f; - sys = sys, - observed = observedfun, - analytic = analytic) -end - -""" -```julia -DiscreteFunctionExpr{iip}(sys::DiscreteSystem, dvs = states(sys), - ps = parameters(sys); - version = nothing, - kwargs...) where {iip} -``` - -Create a Julia expression for an `DiscreteFunction` from the [`DiscreteSystem`](@ref). -The arguments `dvs` and `ps` are used to set the order of the dependent -variable and parameter vectors, respectively. -""" -struct DiscreteFunctionExpr{iip} end -struct DiscreteFunctionClosure{O, I} <: Function - f_oop::O - f_iip::I -end -(f::DiscreteFunctionClosure)(u, p, t) = f.f_oop(u, p, t) -(f::DiscreteFunctionClosure)(du, u, p, t) = f.f_iip(du, u, p, t) - -function DiscreteFunctionExpr{iip}(sys::DiscreteSystem, dvs = unknowns(sys), - ps = parameters(sys), u0 = nothing; - version = nothing, p = nothing, - linenumbers = false, - simplify = false, - kwargs...) where {iip} - f_oop, f_iip = generate_function(sys, dvs, ps; expression = Val{true}, kwargs...) - - fsym = gensym(:f) - _f = :($fsym = $DiscreteFunctionClosure($f_oop, $f_iip)) - - ex = quote - $_f - DiscreteFunction{$iip}($fsym) - end - !linenumbers ? Base.remove_linenums!(ex) : ex -end - -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 From 343606727caf7c65cb744d761c974b361083cc9a Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Sun, 20 Apr 2025 21:37:21 +0530 Subject: [PATCH 050/235] refactor: remove `implicit_discrete_system.jl` --- src/ModelingToolkit.jl | 4 +- .../implicit_discrete_system.jl | 459 ------------------ 2 files changed, 1 insertion(+), 462 deletions(-) delete mode 100644 src/systems/discrete_system/implicit_discrete_system.jl diff --git a/src/ModelingToolkit.jl b/src/ModelingToolkit.jl index 24ffb10ee7..0a4e8a475b 100644 --- a/src/ModelingToolkit.jl +++ b/src/ModelingToolkit.jl @@ -178,8 +178,6 @@ include("systems/diffeqs/first_order_transform.jl") include("systems/diffeqs/modelingtoolkitize.jl") include("systems/diffeqs/basic_transformations.jl") -include("systems/discrete_system/implicit_discrete_system.jl") - include("systems/jumps/jumpsystem.jl") include("systems/pde/pdesystem.jl") @@ -269,7 +267,7 @@ export DAEFunctionExpr, DAEProblemExpr export SDEFunction, SDEFunctionExpr, SDEProblemExpr export SystemStructure export DiscreteProblem, DiscreteFunction, DiscreteFunctionExpr -export ImplicitDiscreteSystem, ImplicitDiscreteProblem, ImplicitDiscreteFunction, +export ImplicitDiscreteProblem, ImplicitDiscreteFunction, ImplicitDiscreteFunctionExpr export JumpSystem export ODEProblem, SDEProblem diff --git a/src/systems/discrete_system/implicit_discrete_system.jl b/src/systems/discrete_system/implicit_discrete_system.jl deleted file mode 100644 index b818ffcc8d..0000000000 --- a/src/systems/discrete_system/implicit_discrete_system.jl +++ /dev/null @@ -1,459 +0,0 @@ -""" -$(TYPEDEF) -An implicit system of difference equations. -# Fields -$(FIELDS) -# Example -``` -using ModelingToolkit -using ModelingToolkit: t_nounits as t -@parameters σ=28.0 ρ=10.0 β=8/3 δt=0.1 -@variables x(t)=1.0 y(t)=0.0 z(t)=0.0 -k = ShiftIndex(t) -eqs = [x ~ σ*(y-x(k-1)), - y ~ x(k-1)*(ρ-z(k-1))-y, - z ~ x(k-1)*y(k-1) - β*z] -@named ide = ImplicitDiscreteSystem(eqs,t,[x,y,z],[σ,ρ,β]; tspan = (0, 1000.0)) -``` -""" -struct ImplicitDiscreteSystem <: AbstractDiscreteSystem - """ - A tag for the system. If two systems have the same tag, then they are - structurally identical. - """ - tag::UInt - """The difference equations defining the discrete system.""" - eqs::Vector{Equation} - """Independent variable.""" - iv::BasicSymbolic{Real} - """Dependent (state) variables. Must not contain the independent variable.""" - unknowns::Vector - """Parameter variables. Must not contain the independent variable.""" - ps::Vector - """Time span.""" - tspan::Union{NTuple{2, Any}, Nothing} - """Array variables.""" - var_to_name::Any - """Observed states.""" - observed::Vector{Equation} - """ - The name of the system - """ - name::Symbol - """ - A description of the system. - """ - description::String - """ - The internal systems. These are required to have unique names. - """ - systems::Vector{ImplicitDiscreteSystem} - """ - The default values to use when initial conditions and/or - parameters are not supplied in `ImplicitDiscreteProblem`. - """ - defaults::Dict - """ - The guesses to use as the initial conditions for the - initialization system. - """ - guesses::Dict - """ - The system for performing the initialization. - """ - initializesystem::Union{Nothing, NonlinearSystem} - """ - Extra equations to be enforced during the initialization sequence. - """ - initialization_eqs::Vector{Equation} - """ - Inject assignment statements before the evaluation of the RHS function. - """ - preface::Any - """ - Type of the system. - """ - connector_type::Any - """ - Topologically sorted parameter dependency equations, where all symbols are parameters and - the LHS is a single parameter. - """ - parameter_dependencies::Vector{Equation} - """ - Metadata for the system, to be used by downstream packages. - """ - metadata::Any - """ - Metadata for MTK GUI. - """ - gui_metadata::Union{Nothing, GUIMetadata} - """ - Cache for intermediate tearing state. - """ - tearing_state::Any - """ - Substitutions generated by tearing. - """ - substitutions::Any - """ - If false, then `sys.x` no longer performs namespacing. - """ - namespacing::Bool - """ - If true, denotes the model will not be modified any further. - """ - complete::Bool - """ - Cached data for fast symbolic indexing. - """ - index_cache::Union{Nothing, IndexCache} - """ - The hierarchical parent system before simplification. - """ - parent::Any - isscheduled::Bool - - function ImplicitDiscreteSystem(tag, discreteEqs, iv, dvs, ps, tspan, var_to_name, - observed, name, description, systems, defaults, guesses, initializesystem, - initialization_eqs, preface, connector_type, parameter_dependencies = Equation[], - metadata = nothing, gui_metadata = nothing, - tearing_state = nothing, substitutions = nothing, namespacing = true, - complete = false, index_cache = nothing, parent = nothing, - isscheduled = false; - checks::Union{Bool, Int} = true) - if checks == true || (checks & CheckComponents) > 0 - check_independent_variables([iv]) - check_variables(dvs, iv) - check_parameters(ps, iv) - check_subsystems(systems) - end - if checks == true || (checks & CheckUnits) > 0 - u = __get_unit_type(dvs, ps, iv) - check_units(u, discreteEqs) - end - new(tag, discreteEqs, iv, dvs, ps, tspan, var_to_name, observed, name, description, - systems, defaults, guesses, initializesystem, initialization_eqs, - preface, connector_type, parameter_dependencies, metadata, gui_metadata, - tearing_state, substitutions, namespacing, complete, index_cache, parent, - isscheduled) - end -end - -""" - $(TYPEDSIGNATURES) - -Constructs a ImplicitDiscreteSystem. -""" -function ImplicitDiscreteSystem(eqs::AbstractVector{<:Equation}, iv, dvs, ps; - observed = Num[], - systems = ImplicitDiscreteSystem[], - tspan = nothing, - name = nothing, - description = "", - default_u0 = Dict(), - default_p = Dict(), - guesses = Dict(), - initializesystem = nothing, - initialization_eqs = Equation[], - defaults = _merge(Dict(default_u0), Dict(default_p)), - preface = nothing, - connector_type = nothing, - parameter_dependencies = Equation[], - metadata = nothing, - gui_metadata = nothing, - kwargs...) - name === nothing && - throw(ArgumentError("The `name` keyword must be provided. Please consider using the `@named` macro")) - iv′ = value(iv) - dvs′ = value.(dvs) - ps′ = value.(ps) - if any(hasderiv, eqs) || any(hashold, eqs) || any(hassample, eqs) || any(hasdiff, eqs) - error("Equations in a `ImplicitDiscreteSystem` can only have `Shift` operators.") - end - if !(isempty(default_u0) && isempty(default_p)) - Base.depwarn( - "`default_u0` and `default_p` are deprecated. Use `defaults` instead.", - :ImplicitDiscreteSystem, force = true) - end - - # Copy equations to canonical form, but do not touch array expressions - eqs = [wrap(eq.lhs) isa Symbolics.Arr ? eq : 0 ~ eq.rhs - eq.lhs for eq in eqs] - defaults = Dict{Any, Any}(todict(defaults)) - guesses = Dict{Any, Any}(todict(guesses)) - var_to_name = Dict() - process_variables!(var_to_name, defaults, guesses, dvs′) - process_variables!(var_to_name, defaults, guesses, ps′) - process_variables!( - var_to_name, defaults, guesses, [eq.lhs for eq in parameter_dependencies]) - process_variables!( - var_to_name, defaults, guesses, [eq.rhs for eq in parameter_dependencies]) - defaults = Dict{Any, Any}(value(k) => value(v) - for (k, v) in pairs(defaults) if v !== nothing) - guesses = Dict{Any, Any}(value(k) => value(v) - for (k, v) in pairs(guesses) if v !== nothing) - - isempty(observed) || collect_var_to_name!(var_to_name, (eq.lhs for eq in observed)) - - sysnames = nameof.(systems) - if length(unique(sysnames)) != length(sysnames) - throw(ArgumentError("System names must be unique.")) - end - ImplicitDiscreteSystem(Threads.atomic_add!(SYSTEM_COUNT, UInt(1)), - eqs, iv′, dvs′, ps′, tspan, var_to_name, observed, name, description, systems, - defaults, guesses, initializesystem, initialization_eqs, preface, connector_type, - parameter_dependencies, metadata, gui_metadata, kwargs...) -end - -function ImplicitDiscreteSystem(eqs, iv; kwargs...) - eqs = collect(eqs) - diffvars = OrderedSet() - allunknowns = OrderedSet() - ps = OrderedSet() - iv = value(iv) - for eq in eqs - collect_vars!(allunknowns, ps, eq, iv; op = Shift) - if iscall(eq.lhs) && operation(eq.lhs) isa Shift - isequal(iv, operation(eq.lhs).t) || - throw(ArgumentError("An ImplicitDiscreteSystem can only have one independent variable.")) - eq.lhs in diffvars && - throw(ArgumentError("The shift variable $(eq.lhs) is not unique in the system of equations.")) - push!(diffvars, eq.lhs) - end - end - for eq in get(kwargs, :parameter_dependencies, Equation[]) - if eq isa Pair - collect_vars!(allunknowns, ps, eq, iv) - else - collect_vars!(allunknowns, ps, eq, iv) - end - end - new_ps = OrderedSet() - for p in ps - if iscall(p) && operation(p) === getindex - par = arguments(p)[begin] - if Symbolics.shape(Symbolics.unwrap(par)) !== Symbolics.Unknown() && - all(par[i] in ps for i in eachindex(par)) - push!(new_ps, par) - else - push!(new_ps, p) - end - else - push!(new_ps, p) - end - end - return ImplicitDiscreteSystem(eqs, iv, - collect(allunknowns), collect(new_ps); kwargs...) -end - -function ImplicitDiscreteSystem(eq::Equation, args...; kwargs...) - ImplicitDiscreteSystem([eq], args...; kwargs...) -end - -function flatten(sys::ImplicitDiscreteSystem, noeqs = false) - systems = get_systems(sys) - if isempty(systems) - return sys - else - return ImplicitDiscreteSystem(noeqs ? Equation[] : equations(sys), - get_iv(sys), - unknowns(sys), - parameters(sys), - observed = observed(sys), - defaults = defaults(sys), - guesses = guesses(sys), - initialization_eqs = initialization_equations(sys), - name = nameof(sys), - description = description(sys), - metadata = get_metadata(sys), - checks = false) - end -end - -function generate_function( - 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 - _iszero(eq.lhs) ? distribute_shift(Shift(iv, 1)(eq.rhs)) : (eq.rhs - eq.lhs) - end - - # Handle observables in algebraic equations, since they are shifted - obs = observed(sys) - shifted_obs = Symbolics.Equation[distribute_shift(Shift(iv, 1)(eq)) for eq in obs] - obsidxs = observed_equations_used_by(sys, exprs; obs = shifted_obs) - extra_assignments = [Assignment(shifted_obs[i].lhs, shifted_obs[i].rhs) - for i in obsidxs] - - u_next = map(Shift(iv, 1), dvs) - u = dvs - p = (reorder_parameters(sys, unwrap.(ps))..., cachesyms...) - build_function_wrapper( - sys, exprs, u_next, u, p..., iv; p_start = 3, extra_assignments, kwargs...) -end - -function shift_u0map_forward(sys::ImplicitDiscreteSystem, u0map, defs) - iv = get_iv(sys) - updated = AnyDict() - for k in collect(keys(u0map)) - v = u0map[k] - if !((op = operation(k)) isa Shift) - 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)))).") - else - updated[k] = v - end - end - for var in unknowns(sys) - op = operation(var) - root = getunshifted(var) - shift = getshift(var) - isnothing(root) && continue - (haskey(updated, Shift(iv, shift)(root)) || haskey(updated, var)) && continue - haskey(defs, root) || error("Initial condition for $var not provided.") - updated[var] = defs[root] - end - return updated -end - -""" - $(TYPEDSIGNATURES) -Generates an ImplicitDiscreteProblem from an ImplicitDiscreteSystem. -""" -function SciMLBase.ImplicitDiscreteProblem( - sys::ImplicitDiscreteSystem, u0map = [], tspan = get_tspan(sys), - parammap = SciMLBase.NullParameters(); - eval_module = @__MODULE__, - eval_expression = false, - kwargs... -) - if !iscomplete(sys) - 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) - eqs = equations(sys) - iv = get_iv(sys) - - u0map = to_varmap(u0map, dvs) - u0map = shift_u0map_forward(sys, u0map, defaults(sys)) - f, u0, p = process_SciMLProblem( - ImplicitDiscreteFunction, sys, u0map, parammap; eval_expression, eval_module, kwargs...) - - kwargs = filter_kwargs(kwargs) - ImplicitDiscreteProblem(f, u0, tspan, p; kwargs...) -end - -function SciMLBase.ImplicitDiscreteFunction(sys::ImplicitDiscreteSystem, args...; kwargs...) - ImplicitDiscreteFunction{true}(sys, args...; kwargs...) -end - -function SciMLBase.ImplicitDiscreteFunction{true}( - sys::ImplicitDiscreteSystem, args...; kwargs...) - ImplicitDiscreteFunction{true, SciMLBase.AutoSpecialize}(sys, args...; kwargs...) -end - -function SciMLBase.ImplicitDiscreteFunction{false}( - sys::ImplicitDiscreteSystem, args...; kwargs...) - ImplicitDiscreteFunction{false, SciMLBase.FullSpecialize}(sys, args...; kwargs...) -end -function SciMLBase.ImplicitDiscreteFunction{iip, specialize}( - sys::ImplicitDiscreteSystem, - dvs = unknowns(sys), - ps = parameters(sys), - u0 = nothing; - version = nothing, - p = nothing, - t = nothing, - eval_expression = false, - eval_module = @__MODULE__, - 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`") - end - f_gen = generate_function(sys, dvs, ps; expression = Val{true}, - expression_module = eval_module, cse, kwargs...) - f_oop, f_iip = eval_or_rgf.(f_gen; eval_expression, eval_module) - 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.") - end - f = SciMLBase.wrapfun_iip(f, (u0, u0, p, t)) - end - - observedfun = ObservedFunctionCache( - sys; eval_expression, eval_module, checkbounds = get(kwargs, :checkbounds, false), cse) - - ImplicitDiscreteFunction{iip, specialize}(f; - sys = sys, - observed = observedfun, - analytic = analytic, - resid_prototype = resid_prototype, - kwargs...) -end - -""" -```julia -ImplicitDiscreteFunctionExpr{iip}(sys::ImplicitDiscreteSystem, dvs = states(sys), - ps = parameters(sys); - version = nothing, - kwargs...) where {iip} -``` - -Create a Julia expression for an `ImplicitDiscreteFunction` from the [`ImplicitDiscreteSystem`](@ref). -The arguments `dvs` and `ps` are used to set the order of the dependent -variable and parameter vectors, respectively. -""" -struct ImplicitDiscreteFunctionExpr{iip} end -struct ImplicitDiscreteFunctionClosure{O, I} <: Function - f_oop::O - f_iip::I -end -(f::ImplicitDiscreteFunctionClosure)(u_next, u, p, t) = f.f_oop(u_next, u, p, t) -function (f::ImplicitDiscreteFunctionClosure)(resid, u_next, u, p, t) - f.f_iip(resid, u_next, u, p, t) -end - -function ImplicitDiscreteFunctionExpr{iip}( - sys::ImplicitDiscreteSystem, dvs = unknowns(sys), - ps = parameters(sys), u0 = nothing; - version = nothing, p = nothing, - linenumbers = false, - simplify = false, - kwargs...) where {iip} - f_oop, f_iip = generate_function(sys, dvs, ps; expression = Val{true}, kwargs...) - - fsym = gensym(:f) - _f = :($fsym = $ImplicitDiscreteFunctionClosure($f_oop, $f_iip)) - - ex = quote - $_f - ImplicitDiscreteFunction{$iip}($fsym) - end - !linenumbers ? Base.remove_linenums!(ex) : ex -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 From 6e5801d73499ed034726d2f2cf365b1d45d08351 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Wed, 14 May 2025 12:10:25 +0530 Subject: [PATCH 051/235] remove jumpsystem.jl refactor: remove `jumpsystem.jl` --- src/ModelingToolkit.jl | 3 - src/systems/jumps/jumpsystem.jl | 665 -------------------------------- 2 files changed, 668 deletions(-) delete mode 100644 src/systems/jumps/jumpsystem.jl diff --git a/src/ModelingToolkit.jl b/src/ModelingToolkit.jl index 0a4e8a475b..caa483a2e2 100644 --- a/src/ModelingToolkit.jl +++ b/src/ModelingToolkit.jl @@ -178,8 +178,6 @@ include("systems/diffeqs/first_order_transform.jl") include("systems/diffeqs/modelingtoolkitize.jl") include("systems/diffeqs/basic_transformations.jl") -include("systems/jumps/jumpsystem.jl") - include("systems/pde/pdesystem.jl") include("systems/sparsematrixclil.jl") @@ -269,7 +267,6 @@ export SystemStructure export DiscreteProblem, DiscreteFunction, DiscreteFunctionExpr export ImplicitDiscreteProblem, ImplicitDiscreteFunction, ImplicitDiscreteFunctionExpr -export JumpSystem export ODEProblem, SDEProblem export NonlinearFunction, NonlinearFunctionExpr export NonlinearProblem, NonlinearProblemExpr diff --git a/src/systems/jumps/jumpsystem.jl b/src/systems/jumps/jumpsystem.jl deleted file mode 100644 index 348c103511..0000000000 --- a/src/systems/jumps/jumpsystem.jl +++ /dev/null @@ -1,665 +0,0 @@ -const JumpType = Union{VariableRateJump, ConstantRateJump, MassActionJump} - -""" -$(TYPEDEF) - -A system of jump processes. - -# Fields -$(FIELDS) - -# Example - -```julia -using ModelingToolkit, JumpProcesses -using ModelingToolkit: t_nounits as t - -@parameters β γ -@variables S(t) I(t) R(t) -rate₁ = β*S*I -affect₁ = [S ~ S - 1, I ~ I + 1] -rate₂ = γ*I -affect₂ = [I ~ I - 1, R ~ R + 1] -j₁ = ConstantRateJump(rate₁,affect₁) -j₂ = ConstantRateJump(rate₂,affect₂) -j₃ = MassActionJump(2*β+γ, [R => 1], [S => 1, R => -1]) -@named js = JumpSystem([j₁,j₂,j₃], t, [S,I,R], [β,γ]) -``` -""" -struct JumpSystem{U <: ArrayPartition} <: AbstractTimeDependentSystem - """ - A tag for the system. If two systems have the same tag, then they are - structurally identical. - """ - tag::UInt - """ - The jumps of the system. Allowable types are `ConstantRateJump`, - `VariableRateJump`, `MassActionJump`. - """ - eqs::U - """The independent variable, usually time.""" - iv::Any - """The dependent variables, representing the state of the system. Must not contain the independent variable.""" - unknowns::Vector - """The parameters of the system. Must not contain the independent variable.""" - ps::Vector - """Array variables.""" - var_to_name::Any - """Observed equations.""" - observed::Vector{Equation} - """The name of the system.""" - name::Symbol - """A description of the system.""" - description::String - """The internal systems. These are required to have unique names.""" - systems::Vector{JumpSystem} - """ - The default values to use when initial conditions and/or - parameters are not supplied in `ODEProblem`. - """ - defaults::Dict - """ - The guesses to use as the initial conditions for the - initialization system. - """ - guesses::Dict - """ - The system for performing the initialization. - """ - initializesystem::Union{Nothing, NonlinearSystem} - """ - Extra equations to be enforced during the initialization sequence. - """ - initialization_eqs::Vector{Equation} - """ - 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 - 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 - `reset_aggregated_jumps!(integrator)` if using a custom affect function that changes any - unknown value or parameter. - """ - discrete_events::Vector{SymbolicDiscreteCallback} - """ - Topologically sorted parameter dependency equations, where all symbols are parameters and - the LHS is a single parameter. - """ - parameter_dependencies::Vector{Equation} - """ - Metadata for the system, to be used by downstream packages. - """ - metadata::Any - """ - Metadata for MTK GUI. - """ - gui_metadata::Union{Nothing, GUIMetadata} - """ - If false, then `sys.x` no longer performs namespacing. - """ - namespacing::Bool - """ - If true, denotes the model will not be modified any further. - """ - complete::Bool - """ - Cached data for fast symbolic indexing. - """ - index_cache::Union{Nothing, IndexCache} - isscheduled::Bool - - function JumpSystem{U}( - tag, ap::U, iv, unknowns, ps, var_to_name, observed, name, description, - systems, defaults, guesses, initializesystem, initialization_eqs, connector_type, - cevents, devents, - parameter_dependencies, metadata = nothing, gui_metadata = nothing, - namespacing = true, complete = false, index_cache = nothing, isscheduled = false; - checks::Union{Bool, Int} = true) where {U <: ArrayPartition} - if checks == true || (checks & CheckComponents) > 0 - check_independent_variables([iv]) - check_variables(unknowns, iv) - check_parameters(ps, iv) - check_subsystems(systems) - end - if checks == true || (checks & CheckUnits) > 0 - u = __get_unit_type(unknowns, ps, iv) - check_units(u, ap, iv) - end - new{U}(tag, ap, iv, unknowns, ps, var_to_name, - observed, name, description, systems, defaults, guesses, initializesystem, - initialization_eqs, - connector_type, cevents, devents, parameter_dependencies, metadata, - gui_metadata, namespacing, complete, index_cache, isscheduled) - end -end -function JumpSystem(tag, ap, iv, states, ps, var_to_name, args...; kwargs...) - JumpSystem{typeof(ap)}(tag, ap, iv, states, ps, var_to_name, args...; kwargs...) -end - -function JumpSystem(eqs, iv, unknowns, ps; - observed = Equation[], - systems = JumpSystem[], - default_u0 = Dict(), - default_p = Dict(), - defaults = _merge(Dict(default_u0), Dict(default_p)), - guesses = Dict(), - initializesystem = nothing, - initialization_eqs = Equation[], - name = nothing, - description = "", - connector_type = nothing, - checks = true, - continuous_events = nothing, - discrete_events = nothing, - parameter_dependencies = Equation[], - metadata = nothing, - gui_metadata = nothing, - kwargs...) - - # variable processing, similar to ODESystem - name === nothing && - throw(ArgumentError("The `name` keyword must be provided. Please consider using the `@named` macro")) - iv′ = value(iv) - us′ = value.(unknowns) - ps′ = value.(ps) - parameter_dependencies, ps′ = process_parameter_dependencies( - parameter_dependencies, ps′) - if !(isempty(default_u0) && isempty(default_p)) - Base.depwarn( - "`default_u0` and `default_p` are deprecated. Use `defaults` instead.", - :JumpSystem, force = true) - end - defaults = Dict{Any, Any}(todict(defaults)) - guesses = Dict{Any, Any}(todict(guesses)) - var_to_name = Dict() - process_variables!(var_to_name, defaults, guesses, us′) - process_variables!(var_to_name, defaults, guesses, ps′) - process_variables!( - var_to_name, defaults, guesses, [eq.lhs for eq in parameter_dependencies]) - process_variables!( - var_to_name, defaults, guesses, [eq.rhs for eq in parameter_dependencies]) - #! format: off - defaults = Dict{Any, Any}(value(k) => value(v) for (k, v) in pairs(defaults) if value(v) !== nothing) - guesses = Dict{Any, Any}(value(k) => value(v) for (k, v) in pairs(guesses) if v !== nothing) - #! format: on - isempty(observed) || collect_var_to_name!(var_to_name, (eq.lhs for eq in observed)) - - sysnames = nameof.(systems) - if length(unique(sysnames)) != length(sysnames) - throw(ArgumentError("System names must be unique.")) - end - - # equation processing - # this and the treatment of continuous events are the only part - # unique to JumpSystems - eqs = scalarize.(eqs) - ap = ArrayPartition( - MassActionJump[], ConstantRateJump[], VariableRateJump[], Equation[]) - for eq in eqs - if eq isa MassActionJump - push!(ap.x[1], eq) - elseif eq isa ConstantRateJump - push!(ap.x[2], eq) - elseif eq isa VariableRateJump - push!(ap.x[3], eq) - elseif eq isa Equation - push!(ap.x[4], eq) - else - error("JumpSystem equations must contain MassActionJumps, ConstantRateJumps, VariableRateJumps, or Equations.") - 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) - - 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, - parameter_dependencies, metadata, gui_metadata, checks = checks) -end - -##### MTK dispatches for JumpSystems ##### -eqtype_supports_collect_vars(j::MassActionJump) = true -function collect_vars!(unknowns, parameters, j::MassActionJump, iv; depth = 0, - op = Differential) - collect_vars!(unknowns, parameters, j.scaled_rates, iv; depth, op) - for field in (j.reactant_stoch, j.net_stoch) - for el in field - collect_vars!(unknowns, parameters, el, iv; depth, op) - end - end - return nothing -end - -eqtype_supports_collect_vars(j::Union{ConstantRateJump, VariableRateJump}) = true -function collect_vars!(unknowns, parameters, j::Union{ConstantRateJump, VariableRateJump}, - iv; depth = 0, op = Differential) - collect_vars!(unknowns, parameters, j.rate, iv; depth, op) - for eq in j.affect! - (eq isa Equation) && collect_vars!(unknowns, parameters, eq, iv; depth, op) - end - return nothing -end - -########################################## - -has_massactionjumps(js::JumpSystem) = !isempty(equations(js).x[1]) -has_constantratejumps(js::JumpSystem) = !isempty(equations(js).x[2]) -has_variableratejumps(js::JumpSystem) = !isempty(equations(js).x[3]) -has_equations(js::JumpSystem) = !isempty(equations(js).x[4]) - -function generate_rate_function(js::JumpSystem, rate) - consts = collect_constants(rate) - 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) - rate = substitute(rate, csubs) - end - p = reorder_parameters(js) - build_function_wrapper(js, rate, unknowns(js), p..., - get_iv(js), - expression = Val{true}) -end - -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_equational_affect(affect, js; expression = Val{true}, checkvars = false) -end - -function assemble_vrj( - js, vrj, unknowntoid; eval_expression = false, eval_module = @__MODULE__) - rate = eval_or_rgf(generate_rate_function(js, vrj.rate); eval_expression, eval_module) - 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 = generate_affect_function(js, vrj.affect!) - VariableRateJump(rate, affect; save_positions = vrj.save_positions) -end - -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!) - quote - rate = $rate - affect = $affect - VariableRateJump(rate, affect) - end -end - -function assemble_crj( - js, crj, unknowntoid; eval_expression = false, eval_module = @__MODULE__) - rate = eval_or_rgf(generate_rate_function(js, crj.rate); eval_expression, eval_module) - 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 = generate_affect_function(js, crj.affect!) - ConstantRateJump(rate, affect) -end - -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!) - quote - rate = $rate - affect = $affect - ConstantRateJump(rate, affect) - end -end - -function numericrstoich(mtrs::Vector{Pair{V, W}}, unknowntoid) where {V, W} - rs = Vector{Pair{Int, W}}() - for (wspec, stoich) in mtrs - spec = value(wspec) - if !iscall(spec) && _iszero(spec) - push!(rs, 0 => stoich) - else - push!(rs, unknowntoid[spec] => stoich) - end - end - sort!(rs) - rs -end - -function numericnstoich(mtrs::Vector{Pair{V, W}}, unknowntoid) where {V, W} - ns = Vector{Pair{Int, W}}() - for (wspec, stoich) in mtrs - spec = value(wspec) - !iscall(spec) && _iszero(spec) && - error("Net stoichiometry can not have a species labelled 0.") - push!(ns, unknowntoid[spec] => stoich) - end - sort!(ns) -end - -# assemble a numeric MassActionJump from a MT symbolics MassActionJumps -function assemble_maj(majv::Vector{U}, unknowntoid, pmapper) where {U <: MassActionJump} - rs = [numericrstoich(maj.reactant_stoch, unknowntoid) for maj in majv] - ns = [numericnstoich(maj.net_stoch, unknowntoid) for maj in majv] - MassActionJump(rs, ns; param_mapper = pmapper, nocopy = true) -end - -""" -```julia -DiffEqBase.DiscreteProblem(sys::JumpSystem, u0map, tspan, - parammap = DiffEqBase.NullParameters; - kwargs...) -``` - -Generates a blank DiscreteProblem for a pure jump JumpSystem to utilize as -its `prob.prob`. This is used in the case where there are no ODEs -and no SDEs associated with the system. - -Continuing the example from the [`JumpSystem`](@ref) definition: - -```julia -using DiffEqBase, JumpProcesses -u₀map = [S => 999, I => 1, R => 0] -parammap = [β => 0.1 / 1000, γ => 0.01] -tspan = (0.0, 250.0) -dprob = DiscreteProblem(complete(js), u₀map, tspan, parammap) -``` -""" -function DiffEqBase.DiscreteProblem(sys::JumpSystem, u0map, tspan::Union{Tuple, Nothing}, - parammap = DiffEqBase.NullParameters(); - eval_expression = false, - 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`") - end - - if has_equations(sys) || (!isempty(continuous_events(sys))) - error("The passed in JumpSystem contains `Equation`s or continuous events, please use a problem type that supports these features, such as ODEProblem.") - end - - _f, u0, p = process_SciMLProblem(EmptySciMLFunction{true}, sys, u0map, parammap; - t = tspan === nothing ? nothing : tspan[1], tofloat = false, check_length = false, build_initializeprob = false, cse) - f = DiffEqBase.DISCRETE_INPLACE_DEFAULT - - observedfun = ObservedFunctionCache( - sys; eval_expression, eval_module, checkbounds = get(kwargs, :checkbounds, false), cse) - - df = DiscreteFunction{true, true}(f; sys = sys, observed = observedfun, - initialization_data = get(_f.kwargs, :initialization_data, nothing)) - DiscreteProblem(df, u0, tspan, p; kwargs...) -end - -""" -```julia -DiffEqBase.DiscreteProblemExpr(sys::JumpSystem, u0map, tspan, - parammap = DiffEqBase.NullParameters; kwargs...) -``` - -Generates a blank DiscreteProblem for a JumpSystem to utilize as its -solving `prob.prob`. This is used in the case where there are no ODEs -and no SDEs associated with the system. - -Continuing the example from the [`JumpSystem`](@ref) definition: - -```julia -using DiffEqBase, JumpProcesses -u₀map = [S => 999, I => 1, R => 0] -parammap = [β => 0.1 / 1000, γ => 0.01] -tspan = (0.0, 250.0) -dprob = DiscreteProblem(complete(js), u₀map, tspan, parammap) -``` -""" -struct DiscreteProblemExpr{iip} end - -function DiscreteProblemExpr{iip}(sys::JumpSystem, u0map, tspan::Union{Tuple, Nothing}, - 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`") - end - - _, u0, p = process_SciMLProblem(EmptySciMLFunction{iip}, sys, u0map, parammap; - t = tspan === nothing ? nothing : tspan[1], tofloat = false, check_length = false) - # identity function to make syms works - quote - f = DiffEqBase.DISCRETE_INPLACE_DEFAULT - u0 = $u0 - p = $p - sys = $sys - tspan = $tspan - df = DiscreteFunction{true, true}(f; sys = sys) - DiscreteProblem(df, u0, tspan, p) - end -end - -""" -```julia -DiffEqBase.ODEProblem(sys::JumpSystem, u0map, tspan, - parammap = DiffEqBase.NullParameters; - kwargs...) -``` - -Generates a blank ODEProblem for a pure jump JumpSystem to utilize as its `prob.prob`. This -is used in the case where there are no ODEs and no SDEs associated with the system but there -are jumps with an explicit time dependency (i.e. `VariableRateJump`s). If no jumps have an -explicit time dependence, i.e. all are `ConstantRateJump`s or `MassActionJump`s then -`DiscreteProblem` should be preferred for performance reasons. - -Continuing the example from the [`JumpSystem`](@ref) definition: - -```julia -using DiffEqBase, JumpProcesses -u₀map = [S => 999, I => 1, R => 0] -parammap = [β => 0.1 / 1000, γ => 0.01] -tspan = (0.0, 250.0) -oprob = ODEProblem(complete(js), u₀map, tspan, parammap) -``` -""" -function DiffEqBase.ODEProblem(sys::JumpSystem, u0map, tspan::Union{Tuple, Nothing}, - parammap = DiffEqBase.NullParameters(); - eval_expression = false, - 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`") - end - - # forward everything to be an ODESystem but the jumps and discrete events - if has_equations(sys) - osys = ODESystem(equations(sys).x[4], get_iv(sys), unknowns(sys), parameters(sys); - observed = observed(sys), name = nameof(sys), description = description(sys), - systems = get_systems(sys), defaults = defaults(sys), guesses = guesses(sys), - parameter_dependencies = parameter_dependencies(sys), - metadata = get_metadata(sys), gui_metadata = get_gui_metadata(sys)) - osys = complete(osys; add_initial_parameters = false) - return ODEProblem(osys, u0map, tspan, parammap; check_length = false, - build_initializeprob = false, kwargs...) - else - _, u0, p = process_SciMLProblem(EmptySciMLFunction{true}, sys, u0map, parammap; - t = tspan === nothing ? nothing : tspan[1], tofloat = false, - check_length = false, build_initializeprob = false, cse) - f = (du, u, p, t) -> (du .= 0; nothing) - observedfun = ObservedFunctionCache(sys; eval_expression, eval_module, - checkbounds = get(kwargs, :checkbounds, false), cse) - df = ODEFunction(f; sys, observed = observedfun) - return ODEProblem(df, u0, tspan, p; kwargs...) - end -end - -""" -```julia -DiffEqBase.JumpProblem(js::JumpSystem, prob, aggregator; kwargs...) -``` - -Generates a JumpProblem from a JumpSystem. - -Continuing the example from the [`DiscreteProblem`](@ref) definition: - -```julia -jprob = JumpProblem(complete(js), dprob, Direct()) -sol = solve(jprob, SSAStepper()) -``` -""" -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`") - end - unknowntoid = Dict(value(unknown) => i for (i, unknown) in enumerate(unknowns(js))) - eqs = equations(js) - invttype = prob.tspan[1] === nothing ? Float64 : typeof(1 / prob.tspan[2]) - - # handling parameter substitution and empty param vecs - p = (prob.p isa DiffEqBase.NullParameters || prob.p === nothing) ? Num[] : prob.p - - majpmapper = JumpSysMajParamMapper(js, p; jseqs = eqs, rateconsttype = invttype) - majs = isempty(eqs.x[1]) ? nothing : assemble_maj(eqs.x[1], unknowntoid, majpmapper) - 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) - for j in eqs.x[3]] - if prob isa DiscreteProblem - if (!isempty(vrjs) || has_equations(js) || !isempty(continuous_events(js))) - error("Use continuous problems such as an ODEProblem or a SDEProblem with VariableRateJumps, coupled differential equations, or continuous events.") - end - end - jset = JumpSet(Tuple(vrjs), Tuple(crjs), nothing, majs) - - # dep graphs are only for constant rate jumps - nonvrjs = ArrayPartition(eqs.x[1], eqs.x[2]) - if needs_vartojumps_map(aggregator) || needs_depgraph(aggregator) || - (aggregator isa JumpProcesses.NullAggregator) - jdeps = asgraph(js; eqs = nonvrjs) - vdeps = variable_dependencies(js; eqs = nonvrjs) - vtoj = jdeps.badjlist - jtov = vdeps.badjlist - jtoj = needs_depgraph(aggregator) ? eqeq_dependencies(jdeps, vdeps).fadjlist : - nothing - else - vtoj = nothing - jtov = nothing - jtoj = nothing - end - - # handle events, making sure to reset aggregators in the generated affect functions - 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, - callback = cbs, kwargs...) -end - -### Functions to determine which unknowns a jump depends on -function get_variables!(dep, jump::Union{ConstantRateJump, VariableRateJump}, variables) - jr = value(jump.rate) - (jr isa Symbolic) && get_variables!(dep, jr, variables) - dep -end - -function get_variables!(dep, jump::MassActionJump, variables) - sr = value(jump.scaled_rates) - (sr isa Symbolic) && get_variables!(dep, sr, variables) - for varasop in jump.reactant_stoch - any(isequal(varasop[1]), variables) && push!(dep, varasop[1]) - end - dep -end - -### Functions to determine which unknowns are modified by a given jump -function modified_unknowns!(munknowns, jump::Union{ConstantRateJump, VariableRateJump}, sts) - for eq in jump.affect! - st = eq.lhs - any(isequal(st), sts) && push!(munknowns, st) - end - munknowns -end - -function modified_unknowns!(munknowns, jump::MassActionJump, sts) - for (unknown, stoich) in jump.net_stoch - any(isequal(unknown), sts) && push!(munknowns, unknown) - end - munknowns -end - -###################### parameter mapper ########################### -struct JumpSysMajParamMapper{U, V, W} - paramexprs::U # the parameter expressions to use for each jump rate constant - sympars::V # parameters(sys) from the underlying JumpSystem - subdict::Any # mapping from an element of parameters(sys) to its current numerical value -end - -function JumpSysMajParamMapper(js::JumpSystem, p; jseqs = nothing, rateconsttype = Float64) - eqs = (jseqs === nothing) ? equations(js) : jseqs - paramexprs = [maj.scaled_rates for maj in eqs.x[1]] - psyms = reduce(vcat, reorder_parameters(js); init = []) - paramdict = Dict(value(k) => value(v) for (k, v) in zip(psyms, vcat(p...))) - JumpSysMajParamMapper{typeof(paramexprs), typeof(psyms), rateconsttype}(paramexprs, - psyms, - paramdict) -end - -function updateparams!(ratemap::JumpSysMajParamMapper{U, V, W}, - params) where {U <: AbstractArray, V <: AbstractArray, W} - for (i, p) in enumerate(params) - sympar = ratemap.sympars[i] - ratemap.subdict[sympar] = p - end - nothing -end - -function updateparams!(ratemap::JumpSysMajParamMapper{U, V, W}, - params::MTKParameters) where {U <: AbstractArray, V <: AbstractArray, W} - for (i, p) in enumerate(ArrayPartition(params...)) - sympar = ratemap.sympars[i] - ratemap.subdict[sympar] = p - end - nothing -end - -function updateparams!(::JumpSysMajParamMapper{U, V, W}, - params::Nothing) where {U <: AbstractArray, V <: AbstractArray, W} - nothing -end - -# create the initial parameter vector for use in a MassActionJump -function (ratemap::JumpSysMajParamMapper{ - U, - V, - W -})(params) where {U <: AbstractArray, - V <: AbstractArray, W} - updateparams!(ratemap, params) - [convert(W, value(substitute(paramexpr, ratemap.subdict))) - for paramexpr in ratemap.paramexprs] -end - -# update a maj with parameter vectors -function (ratemap::JumpSysMajParamMapper{U, V, W})(maj::MassActionJump, newparams; - scale_rates, - kwargs...) where {U <: AbstractArray, - V <: AbstractArray, W} - updateparams!(ratemap, newparams) - for i in 1:get_num_majumps(maj) - maj.scaled_rates[i] = convert(W, - value(substitute(ratemap.paramexprs[i], - ratemap.subdict))) - end - scale_rates && JumpProcesses.scalerates!(maj.scaled_rates, maj.reactant_stoch) - nothing -end - -supports_initialization(::JumpSystem) = false From 23093ae24507156cc4f2cf1d6b6041f3447229b2 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Tue, 22 Apr 2025 16:54:28 +0530 Subject: [PATCH 052/235] refactor: move `JumpType` definition to `utils.jl` --- src/utils.jl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/utils.jl b/src/utils.jl index 8f5ac311c5..c3d736f92f 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -1370,3 +1370,5 @@ function flatten_equations(eqs::Vector{Equation}) end end end + +const JumpType = Union{VariableRateJump, ConstantRateJump, MassActionJump} From 5b4e80dd52ee32e876b1b563f99fe8964e648f61 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Sun, 20 Apr 2025 21:52:37 +0530 Subject: [PATCH 053/235] refactor: remove `nonlinearsystem.jl` --- src/ModelingToolkit.jl | 3 +- src/systems/nonlinear/nonlinearsystem.jl | 993 ----------------------- 2 files changed, 1 insertion(+), 995 deletions(-) delete mode 100644 src/systems/nonlinear/nonlinearsystem.jl diff --git a/src/ModelingToolkit.jl b/src/ModelingToolkit.jl index caa483a2e2..66fda07b8c 100644 --- a/src/ModelingToolkit.jl +++ b/src/ModelingToolkit.jl @@ -169,7 +169,6 @@ include("systems/optimization/constraints_system.jl") include("systems/optimization/optimizationsystem.jl") include("systems/optimization/modelingtoolkitize.jl") -include("systems/nonlinear/nonlinearsystem.jl") include("systems/diffeqs/abstractodesystem.jl") include("systems/nonlinear/homotopy_continuation.jl") include("systems/nonlinear/modelingtoolkitize.jl") @@ -275,7 +274,7 @@ export IntervalNonlinearProblem, IntervalNonlinearProblemExpr export OptimizationProblem, OptimizationProblemExpr, constraints export SteadyStateProblem, SteadyStateProblemExpr export JumpProblem -export NonlinearSystem, OptimizationSystem, ConstraintsSystem +export OptimizationSystem, ConstraintsSystem export alias_elimination, flatten export connect, domain_connect, @connector, Connection, AnalysisPoint, Flow, Stream, instream diff --git a/src/systems/nonlinear/nonlinearsystem.jl b/src/systems/nonlinear/nonlinearsystem.jl deleted file mode 100644 index cf1c46207f..0000000000 --- a/src/systems/nonlinear/nonlinearsystem.jl +++ /dev/null @@ -1,993 +0,0 @@ -""" -$(TYPEDEF) - -A nonlinear system of equations. - -# Fields -$(FIELDS) - -# Examples - -```julia -@variables x y z -@parameters σ ρ β - -eqs = [0 ~ σ*(y-x), - 0 ~ x*(ρ-z)-y, - 0 ~ x*y - β*z] -@named ns = NonlinearSystem(eqs, [x,y,z],[σ,ρ,β]) -``` -""" -struct NonlinearSystem <: AbstractTimeIndependentSystem - """ - A tag for the system. If two systems have the same tag, then they are - structurally identical. - """ - tag::UInt - """Vector of equations defining the system.""" - eqs::Vector{Equation} - """Unknown variables.""" - unknowns::Vector - """Parameters.""" - ps::Vector - """Array variables.""" - var_to_name::Any - """Observed equations.""" - observed::Vector{Equation} - """ - Jacobian matrix. Note: this field will not be defined until - [`calculate_jacobian`](@ref) is called on the system. - """ - jac::RefValue{Any} - """ - The name of the system. - """ - name::Symbol - """ - A description of the system. - """ - description::String - """ - The internal systems. These are required to have unique names. - """ - systems::Vector{NonlinearSystem} - """ - The default values to use when initial conditions and/or - parameters are not supplied in `ODEProblem`. - """ - defaults::Dict - """ - The guesses to use as the initial conditions for the - initialization system. - """ - guesses::Dict - """ - The system for performing the initialization. - """ - initializesystem::Union{Nothing, NonlinearSystem} - """ - Extra equations to be enforced during the initialization sequence. - """ - initialization_eqs::Vector{Equation} - """ - Type of the system. - """ - connector_type::Any - """ - Topologically sorted parameter dependency equations, where all symbols are parameters and - the LHS is a single parameter. - """ - parameter_dependencies::Vector{Equation} - """ - Metadata for the system, to be used by downstream packages. - """ - metadata::Any - """ - Metadata for MTK GUI. - """ - gui_metadata::Union{Nothing, GUIMetadata} - """ - Whether this is an initialization system. - """ - is_initializesystem::Bool - """ - Cache for intermediate tearing state. - """ - tearing_state::Any - """ - Substitutions generated by tearing. - """ - substitutions::Any - """ - If false, then `sys.x` no longer performs namespacing. - """ - namespacing::Bool - """ - If true, denotes the model will not be modified any further. - """ - complete::Bool - """ - Cached data for fast symbolic indexing. - """ - index_cache::Union{Nothing, IndexCache} - """ - The hierarchical parent system before simplification. - """ - parent::Any - isscheduled::Bool - - function NonlinearSystem( - tag, eqs, unknowns, ps, var_to_name, observed, jac, name, description, - systems, defaults, guesses, initializesystem, initialization_eqs, connector_type, - parameter_dependencies = Equation[], metadata = nothing, gui_metadata = nothing, - is_initializesystem = false, - tearing_state = nothing, substitutions = nothing, namespacing = true, - complete = false, index_cache = nothing, parent = nothing, - isscheduled = false; checks::Union{Bool, Int} = true) - if checks == true || (checks & CheckUnits) > 0 - u = __get_unit_type(unknowns, ps) - check_units(u, eqs) - check_subsystems(systems) - end - new(tag, eqs, unknowns, ps, var_to_name, observed, jac, name, description, - systems, defaults, guesses, initializesystem, initialization_eqs, - connector_type, parameter_dependencies, metadata, gui_metadata, - is_initializesystem, tearing_state, - substitutions, namespacing, complete, index_cache, parent, isscheduled) - end -end - -function NonlinearSystem(eqs, unknowns, ps; - observed = [], - name = nothing, - description = "", - default_u0 = Dict(), - default_p = Dict(), - defaults = _merge(Dict(default_u0), Dict(default_p)), - guesses = Dict(), - initializesystem = nothing, - initialization_eqs = Equation[], - systems = NonlinearSystem[], - connector_type = nothing, - continuous_events = nothing, # this argument is only required for ODESystems, but is added here for the constructor to accept it without error - discrete_events = nothing, # this argument is only required for ODESystems, but is added here for the constructor to accept it without error - checks = true, - parameter_dependencies = Equation[], - metadata = nothing, - gui_metadata = nothing, - is_initializesystem = false) - continuous_events === nothing || isempty(continuous_events) || - throw(ArgumentError("NonlinearSystem does not accept `continuous_events`, you provided $continuous_events")) - discrete_events === nothing || isempty(discrete_events) || - throw(ArgumentError("NonlinearSystem does not accept `discrete_events`, you provided $discrete_events")) - name === nothing && - throw(ArgumentError("The `name` keyword must be provided. Please consider using the `@named` macro")) - length(unique(nameof.(systems))) == length(systems) || - throw(ArgumentError("System names must be unique.")) - (isempty(default_u0) && isempty(default_p)) || - Base.depwarn( - "`default_u0` and `default_p` are deprecated. Use `defaults` instead.", - :NonlinearSystem, force = true) - - # Accept a single (scalar/vector) equation, but make array for consistent internal handling - if !(eqs isa AbstractArray) - eqs = [eqs] - end - - # Copy equations to canonical form, but do not touch array expressions - eqs = [wrap(eq.lhs) isa Symbolics.Arr ? eq : 0 ~ eq.rhs - eq.lhs for eq in eqs] - - jac = RefValue{Any}(EMPTY_JAC) - - ps′ = value.(ps) - dvs′ = value.(unknowns) - parameter_dependencies, ps′ = process_parameter_dependencies( - parameter_dependencies, ps′) - - defaults = Dict{Any, Any}(todict(defaults)) - guesses = Dict{Any, Any}(todict(guesses)) - var_to_name = Dict() - process_variables!(var_to_name, defaults, guesses, dvs′) - process_variables!(var_to_name, defaults, guesses, ps′) - process_variables!( - var_to_name, defaults, guesses, [eq.lhs for eq in parameter_dependencies]) - process_variables!( - var_to_name, defaults, guesses, [eq.rhs for eq in parameter_dependencies]) - defaults = Dict{Any, Any}(value(k) => value(v) - for (k, v) in pairs(defaults) if v !== nothing) - guesses = Dict{Any, Any}(value(k) => value(v) - for (k, v) in pairs(guesses) if v !== nothing) - - isempty(observed) || collect_var_to_name!(var_to_name, (eq.lhs for eq in observed)) - - NonlinearSystem(Threads.atomic_add!(SYSTEM_COUNT, UInt(1)), - eqs, dvs′, ps′, var_to_name, observed, jac, name, description, systems, defaults, - guesses, initializesystem, initialization_eqs, connector_type, parameter_dependencies, - metadata, gui_metadata, is_initializesystem, checks = checks) -end - -function NonlinearSystem(eqs; kwargs...) - eqs = collect(eqs) - allunknowns = OrderedSet() - ps = OrderedSet() - for eq in eqs - collect_vars!(allunknowns, ps, eq, nothing) - end - for eq in get(kwargs, :parameter_dependencies, Equation[]) - if eq isa Pair - collect_vars!(allunknowns, ps, eq, nothing) - else - collect_vars!(allunknowns, ps, eq, nothing) - end - end - new_ps = OrderedSet() - for p in ps - if iscall(p) && operation(p) === getindex - par = arguments(p)[begin] - if Symbolics.shape(Symbolics.unwrap(par)) !== Symbolics.Unknown() && - all(par[i] in ps for i in eachindex(par)) - push!(new_ps, par) - else - push!(new_ps, p) - end - else - if symbolic_type(p) == ArraySymbolic() && - Symbolics.shape(unwrap(p)) != Symbolics.Unknown() - for i in eachindex(p) - delete!(new_ps, p[i]) - end - end - push!(new_ps, p) - end - end - - return NonlinearSystem(eqs, collect(allunknowns), collect(new_ps); kwargs...) -end - -""" - $(TYPEDSIGNATURES) - -Convert an `ODESystem` to a `NonlinearSystem` solving for its steady state (where derivatives are zero). -Any differential variable `D(x) ~ f(...)` will be turned into `0 ~ f(...)`. The returned system is not -simplified. If the input system is `complete`d, then so will the returned system. -""" -function NonlinearSystem(sys::AbstractODESystem) - eqs = equations(sys) - obs = observed(sys) - subrules = Dict(D(x) => 0.0 for x in unknowns(sys)) - eqs = map(eqs) do eq - fast_substitute(eq, subrules) - end - - nsys = NonlinearSystem(eqs, unknowns(sys), [parameters(sys); get_iv(sys)]; - parameter_dependencies = parameter_dependencies(sys), - defaults = merge(defaults(sys), Dict(get_iv(sys) => Inf)), guesses = guesses(sys), - initialization_eqs = initialization_equations(sys), name = nameof(sys), - observed = obs) - if iscomplete(sys) - nsys = complete(nsys; split = is_split(sys)) - end - return nsys -end - -function calculate_jacobian(sys::NonlinearSystem; sparse = false, simplify = false) - cache = get_jac(sys)[] - if cache isa Tuple && cache[2] == (sparse, simplify) - return cache[1] - end - - # observed equations may depend on unknowns, so substitute them in first - # TODO: rather keep observed derivatives unexpanded, like "Differential(obs)(expr)"? - obs = Dict(eq.lhs => eq.rhs for eq in observed(sys)) - rhs = map(eq -> fixpoint_sub(eq.rhs, obs), equations(sys)) - vals = [dv for dv in unknowns(sys)] - - if sparse - jac = sparsejacobian(rhs, vals, simplify = simplify) - else - jac = jacobian(rhs, vals, simplify = simplify) - end - get_jac(sys)[] = jac, (sparse, simplify) - return jac -end - -function generate_jacobian( - sys::NonlinearSystem, vs = unknowns(sys), ps = parameters( - sys; initial_parameters = true); - sparse = false, simplify = false, kwargs...) - jac = calculate_jacobian(sys, sparse = sparse, simplify = simplify) - p = reorder_parameters(sys, ps) - return build_function_wrapper(sys, jac, vs, p...; kwargs...) -end - -function calculate_hessian(sys::NonlinearSystem; sparse = false, simplify = false) - obs = Dict(eq.lhs => eq.rhs for eq in observed(sys)) - rhs = map(eq -> fixpoint_sub(eq.rhs, obs), equations(sys)) - vals = [dv for dv in unknowns(sys)] - if sparse - hess = [sparsehessian(rhs[i], vals, simplify = simplify) for i in 1:length(rhs)] - else - hess = [hessian(rhs[i], vals, simplify = simplify) for i in 1:length(rhs)] - end - return hess -end - -function generate_hessian( - sys::NonlinearSystem, vs = unknowns(sys), ps = parameters( - sys; initial_parameters = true); - sparse = false, simplify = false, kwargs...) - hess = calculate_hessian(sys, sparse = sparse, simplify = simplify) - p = reorder_parameters(sys, ps) - return build_function_wrapper(sys, hess, vs, p...; kwargs...) -end - -function generate_function( - sys::NonlinearSystem, dvs = unknowns(sys), ps = parameters( - sys; initial_parameters = true); - scalar = false, kwargs...) - rhss = [deq.rhs for deq in equations(sys)] - dvs′ = value.(dvs) - if scalar - rhss = only(rhss) - dvs′ = only(dvs) - end - p = reorder_parameters(sys, value.(ps)) - return build_function_wrapper(sys, rhss, dvs′, p...; kwargs...) -end - -function jacobian_sparsity(sys::NonlinearSystem) - jacobian_sparsity([eq.rhs for eq in equations(sys)], - unknowns(sys)) -end - -function hessian_sparsity(sys::NonlinearSystem) - [hessian_sparsity(eq.rhs, - unknowns(sys)) for eq in equations(sys)] -end - -""" -```julia -SciMLBase.NonlinearFunction{iip}(sys::NonlinearSystem, dvs = unknowns(sys), - ps = parameters(sys); - version = nothing, - jac = false, - sparse = false, - kwargs...) where {iip} -``` - -Create a `NonlinearFunction` from the [`NonlinearSystem`](@ref). The arguments -`dvs` and `ps` are used to set the order of the dependent variable and parameter -vectors, respectively. -""" -function SciMLBase.NonlinearFunction(sys::NonlinearSystem, args...; kwargs...) - NonlinearFunction{true}(sys, args...; kwargs...) -end - -function SciMLBase.NonlinearFunction{iip}(sys::NonlinearSystem, dvs = unknowns(sys), - ps = parameters(sys), u0 = nothing; p = nothing, - version = nothing, - jac = false, - eval_expression = false, - eval_module = @__MODULE__, - sparse = false, simplify = false, - initialization_data = nothing, cse = true, - resid_prototype = nothing, - kwargs...) where {iip} - if !iscomplete(sys) - 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) - f = GeneratedFunctionWrapper{(2, 2, is_split(sys))}(f_oop, f_iip) - - if jac - jac_gen = generate_jacobian(sys, dvs, ps; - simplify = simplify, sparse = sparse, - expression = Val{true}, cse, kwargs...) - jac_oop, jac_iip = eval_or_rgf.(jac_gen; eval_expression, eval_module) - _jac = GeneratedFunctionWrapper{(2, 2, is_split(sys))}(jac_oop, jac_iip) - else - _jac = nothing - end - - observedfun = ObservedFunctionCache( - sys; eval_expression, eval_module, checkbounds = get(kwargs, :checkbounds, false), cse) - - NonlinearFunction{iip}(f; - sys = sys, - jac = _jac === nothing ? nothing : _jac, - resid_prototype = resid_prototype, - jac_prototype = sparse ? - similar(calculate_jacobian(sys, sparse = sparse), - Float64) : nothing, - observed = observedfun, initialization_data) -end - -""" -$(TYPEDSIGNATURES) - -Create an `IntervalNonlinearFunction` from the [`NonlinearSystem`](@ref). The arguments -`dvs` and `ps` are used to set the order of the dependent variable and parameter vectors, -respectively. -""" -function SciMLBase.IntervalNonlinearFunction( - sys::NonlinearSystem, dvs = unknowns(sys), ps = parameters(sys), u0 = nothing; - 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`") - end - if !isone(length(dvs)) || !isone(length(equations(sys))) - error("`IntervalNonlinearFunction` only supports systems with a single equation and a single unknown.") - end - - f_gen = generate_function( - sys, dvs, ps; expression = Val{true}, scalar = true, kwargs...) - f = eval_or_rgf(f_gen; eval_expression, eval_module) - f = GeneratedFunctionWrapper{(2, 2, is_split(sys))}(f, nothing) - - observedfun = ObservedFunctionCache( - sys; eval_expression, eval_module, checkbounds = get(kwargs, :checkbounds, false)) - - IntervalNonlinearFunction{false}( - f; observed = observedfun, sys = sys, initialization_data) -end - -""" -```julia -SciMLBase.NonlinearFunctionExpr{iip}(sys::NonlinearSystem, dvs = unknowns(sys), - ps = parameters(sys); - version = nothing, - jac = false, - sparse = false, - kwargs...) where {iip} -``` - -Create a Julia expression for a `NonlinearFunction` from the [`NonlinearSystem`](@ref). -The arguments `dvs` and `ps` are used to set the order of the dependent -variable and parameter vectors, respectively. -""" -struct NonlinearFunctionExpr{iip} end - -function NonlinearFunctionExpr{iip}(sys::NonlinearSystem, dvs = unknowns(sys), - ps = parameters(sys), u0 = nothing; p = nothing, - version = nothing, tgrad = false, - jac = false, - linenumbers = false, - 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`") - 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)) - - if jac - jac_oop, jac_iip = generate_jacobian(sys, dvs, ps; - sparse = sparse, simplify = simplify, - expression = Val{true}, kwargs...) - _jac = :($(GeneratedFunctionWrapper{(2, 2, is_split(sys))})($jac_oop, $jac_iip)) - else - _jac = :nothing - end - - jp_expr = sparse ? :(similar($(get_jac(sys)[]), Float64)) : :nothing - if length(dvs) == length(equations(sys)) - resid_expr = :nothing - else - u0ElType = u0 === nothing ? Float64 : eltype(u0) - if SciMLStructures.isscimlstructure(p) - u0ElType = promote_type( - eltype(SciMLStructures.canonicalize(SciMLStructures.Tunable(), p)[1]), - u0ElType) - end - - resid_expr = :(zeros($u0ElType, $(length(equations(sys))))) - end - ex = quote - f = $f - jac = $_jac - NonlinearFunction{$iip}(f, - jac = jac, - resid_prototype = resid_expr, - jac_prototype = $jp_expr) - end - !linenumbers ? Base.remove_linenums!(ex) : ex -end - -struct IntervalNonlinearFunctionExpr end - -""" -$(TYPEDSIGNATURES) - -Create a Julia expression for an `IntervalNonlinearFunction` from the -[`NonlinearSystem`](@ref). The arguments `dvs` and `ps` are used to set the order of the -dependent variable and parameter vectors, respectively. -""" -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`") - end - if !isone(length(dvs)) || !isone(length(equations(sys))) - error("`IntervalNonlinearFunctionExpr` only supports systems with a single equation and a single unknown.") - end - - f = generate_function(sys, dvs, ps; expression = Val{true}, scalar = true, kwargs...) - f = :($(GeneratedFunctionWrapper{2, 2, is_split(sys)})($f, nothing)) - - ex = quote - f = $f - NonlinearFunction{false}(f) - end - !linenumbers ? Base.remove_linenums!(ex) : ex -end - -""" -```julia -DiffEqBase.NonlinearProblem{iip}(sys::NonlinearSystem, u0map, - parammap = DiffEqBase.NullParameters(); - jac = false, sparse = false, - checkbounds = false, - linenumbers = true, parallel = SerialForm(), - kwargs...) where {iip} -``` - -Generates an NonlinearProblem from a NonlinearSystem and allows for automatically -symbolically calculating numerical enhancements. -""" -function DiffEqBase.NonlinearProblem(sys::NonlinearSystem, args...; kwargs...) - NonlinearProblem{true}(sys, args...; kwargs...) -end - -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`") - end - f, u0, p = process_SciMLProblem(NonlinearFunction{iip}, sys, u0map, parammap; - check_length, kwargs...) - pt = something(get_metadata(sys), StandardNonlinearProblem()) - # Call `remake` so it runs initialization if it is trivial - return remake(NonlinearProblem{iip}(f, u0, p, pt; filter_kwargs(kwargs)...)) -end - -function DiffEqBase.NonlinearProblem(sys::AbstractODESystem, args...; kwargs...) - NonlinearProblem(NonlinearSystem(sys), args...; kwargs...) -end - -""" -```julia -DiffEqBase.NonlinearLeastSquaresProblem{iip}(sys::NonlinearSystem, u0map, - parammap = DiffEqBase.NullParameters(); - jac = false, sparse = false, - checkbounds = false, - linenumbers = true, parallel = SerialForm(), - kwargs...) where {iip} -``` - -Generates an NonlinearProblem from a NonlinearSystem and allows for automatically -symbolically calculating numerical enhancements. -""" -function DiffEqBase.NonlinearLeastSquaresProblem(sys::NonlinearSystem, args...; kwargs...) - NonlinearLeastSquaresProblem{true}(sys, args...; kwargs...) -end - -function DiffEqBase.NonlinearLeastSquaresProblem{iip}(sys::NonlinearSystem, u0map, - 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`") - end - f, u0, p = process_SciMLProblem(NonlinearFunction{iip}, sys, u0map, parammap; - check_length, kwargs...) - pt = something(get_metadata(sys), StandardNonlinearProblem()) - # Call `remake` so it runs initialization if it is trivial - return remake(NonlinearLeastSquaresProblem{iip}(f, u0, p; filter_kwargs(kwargs)...)) -end - -const TypeT = Union{DataType, UnionAll} - -struct CacheWriter{F} - fn::F -end - -function (cw::CacheWriter)(p, sols) - cw.fn(p.caches, sols, p) -end - -function CacheWriter(sys::AbstractSystem, buffer_types::Vector{TypeT}, - exprs::Dict{TypeT, Vector{Any}}, solsyms, obseqs::Vector{Equation}; - eval_expression = false, eval_module = @__MODULE__, cse = true) - ps = parameters(sys; initial_parameters = true) - rps = reorder_parameters(sys, ps) - obs_assigns = [eq.lhs ← eq.rhs for eq in obseqs] - body = map(eachindex(buffer_types), buffer_types) do i, T - Symbol(:tmp, i) ← SetArray(true, :(out[$i]), get(exprs, T, [])) - end - - function argument_name(i::Int) - if i <= length(solsyms) - return :($(generated_argument_name(1))[$i]) - end - return generated_argument_name(i - length(solsyms)) - end - array_assignments = array_variable_assignments(solsyms...; argument_name) - fn = build_function_wrapper( - sys, nothing, :out, - DestructuredArgs(DestructuredArgs.(solsyms), generated_argument_name(1)), - rps...; p_start = 3, p_end = length(rps) + 2, - expression = Val{true}, add_observed = false, cse, - extra_assignments = [array_assignments; obs_assigns; body]) - fn = eval_or_rgf(fn; eval_expression, eval_module) - fn = GeneratedFunctionWrapper{(3, 3, is_split(sys))}(fn, nothing) - return CacheWriter(fn) -end - -struct SCCNonlinearFunction{iip} end - -function SCCNonlinearFunction{iip}( - sys::NonlinearSystem, _eqs, _dvs, _obs, cachesyms; eval_expression = false, - eval_module = @__MODULE__, cse = true, kwargs...) where {iip} - ps = parameters(sys; initial_parameters = true) - rps = reorder_parameters(sys, ps) - - obs_assignments = [eq.lhs ← eq.rhs for eq in _obs] - - rhss = [eq.rhs - eq.lhs for eq in _eqs] - f_gen = build_function_wrapper(sys, - rhss, _dvs, rps..., cachesyms...; p_start = 2, - p_end = length(rps) + length(cachesyms) + 1, add_observed = false, - extra_assignments = obs_assignments, expression = Val{true}, cse) - f_oop, f_iip = eval_or_rgf.(f_gen; eval_expression, eval_module) - f = GeneratedFunctionWrapper{(2, 2, is_split(sys))}(f_oop, f_iip) - - subsys = NonlinearSystem(_eqs, _dvs, ps; observed = _obs, - parameter_dependencies = parameter_dependencies(sys), name = nameof(sys)) - if get_index_cache(sys) !== nothing - @set! subsys.index_cache = subset_unknowns_observed( - get_index_cache(sys), sys, _dvs, getproperty.(_obs, (:lhs,))) - @set! subsys.complete = true - end - - return NonlinearFunction{iip}(f; sys = subsys) -end - -function SciMLBase.SCCNonlinearProblem(sys::NonlinearSystem, args...; kwargs...) - SCCNonlinearProblem{true}(sys, args...; kwargs...) -end - -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`.") - 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`.") - end - - ts = get_tearing_state(sys) - var_eq_matching, var_sccs = StructuralTransformations.algebraic_variables_scc(ts) - - if length(var_sccs) == 1 - return NonlinearProblem{iip}( - sys, u0map, parammap; eval_expression, eval_module, kwargs...) - end - - condensed_graph = MatchedCondensationGraph( - DiCMOBiGraph{true}(complete(ts.structure.graph), - complete(var_eq_matching)), - var_sccs) - toporder = topological_sort_by_dfs(condensed_graph) - var_sccs = var_sccs[toporder] - eq_sccs = map(Base.Fix1(getindex, var_eq_matching), var_sccs) - - dvs = unknowns(sys) - ps = parameters(sys) - eqs = equations(sys) - obs = observed(sys) - - _, u0, p = process_SciMLProblem( - EmptySciMLFunction{iip}, sys, u0map, parammap; eval_expression, eval_module, kwargs...) - - explicitfuns = [] - nlfuns = [] - prevobsidxs = BlockArray(undef_blocks, Vector{Int}, Int[]) - # Cache buffer types and corresponding sizes. Stored as a pair of arrays instead of a - # dict to maintain a consistent order of buffers across SCCs - cachetypes = TypeT[] - cachesizes = Int[] - # explicitfun! related information for each SCC - # We need to compute buffer sizes before doing any codegen - scc_cachevars = Dict{TypeT, Vector{Any}}[] - scc_cacheexprs = Dict{TypeT, Vector{Any}}[] - scc_eqs = Vector{Equation}[] - scc_obs = Vector{Equation}[] - # variables solved in previous SCCs - available_vars = Set() - for (i, (escc, vscc)) in enumerate(zip(eq_sccs, var_sccs)) - # subset unknowns and equations - _dvs = dvs[vscc] - _eqs = eqs[escc] - # get observed equations required by this SCC - union!(available_vars, _dvs) - obsidxs = observed_equations_used_by(sys, _eqs; available_vars) - # the ones used by previous SCCs can be precomputed into the cache - setdiff!(obsidxs, prevobsidxs) - _obs = obs[obsidxs] - union!(available_vars, getproperty.(_obs, (:lhs,))) - - # get all subexpressions in the RHS which we can precompute in the cache - # precomputed subexpressions should not contain `banned_vars` - banned_vars = Set{Any}(vcat(_dvs, getproperty.(_obs, (:lhs,)))) - state = Dict() - for i in eachindex(_obs) - _obs[i] = _obs[i].lhs ~ subexpressions_not_involving_vars!( - _obs[i].rhs, banned_vars, state) - end - for i in eachindex(_eqs) - _eqs[i] = _eqs[i].lhs ~ subexpressions_not_involving_vars!( - _eqs[i].rhs, banned_vars, state) - end - - # map from symtype to cached variables and their expressions - cachevars = Dict{Union{DataType, UnionAll}, Vector{Any}}() - cacheexprs = Dict{Union{DataType, UnionAll}, Vector{Any}}() - # observed of previous SCCs are in the cache - # NOTE: When we get proper CSE, we can substitute these - # and then use `subexpressions_not_involving_vars!` - for i in prevobsidxs - T = symtype(obs[i].lhs) - buf = get!(() -> Any[], cachevars, T) - push!(buf, obs[i].lhs) - - buf = get!(() -> Any[], cacheexprs, T) - push!(buf, obs[i].lhs) - end - - for (k, v) in state - k = unwrap(k) - v = unwrap(v) - T = symtype(k) - buf = get!(() -> Any[], cachevars, T) - push!(buf, v) - buf = get!(() -> Any[], cacheexprs, T) - push!(buf, k) - end - - # update the sizes of cache buffers - for (T, buf) in cachevars - idx = findfirst(isequal(T), cachetypes) - if idx === nothing - push!(cachetypes, T) - push!(cachesizes, 0) - idx = lastindex(cachetypes) - end - cachesizes[idx] = max(cachesizes[idx], length(buf)) - end - - push!(scc_cachevars, cachevars) - push!(scc_cacheexprs, cacheexprs) - push!(scc_eqs, _eqs) - push!(scc_obs, _obs) - blockpush!(prevobsidxs, obsidxs) - end - - for (i, (escc, vscc)) in enumerate(zip(eq_sccs, var_sccs)) - _dvs = dvs[vscc] - _eqs = scc_eqs[i] - _prevobsidxs = reduce(vcat, blocks(prevobsidxs)[1:(i - 1)]; init = Int[]) - _obs = scc_obs[i] - cachevars = scc_cachevars[i] - cacheexprs = scc_cacheexprs[i] - available_vars = [dvs[reduce(vcat, var_sccs[1:(i - 1)]; init = Int[])]; - getproperty.( - reduce(vcat, scc_obs[1:(i - 1)]; init = []), (:lhs,))] - _prevobsidxs = vcat(_prevobsidxs, - observed_equations_used_by( - sys, reduce(vcat, values(cacheexprs); init = []); available_vars)) - if isempty(cachevars) - push!(explicitfuns, Returns(nothing)) - else - solsyms = getindex.((dvs,), view(var_sccs, 1:(i - 1))) - push!(explicitfuns, - CacheWriter(sys, cachetypes, cacheexprs, solsyms, obs[_prevobsidxs]; - eval_expression, eval_module, cse)) - end - - cachebufsyms = Tuple(map(cachetypes) do T - get(cachevars, T, []) - end) - f = SCCNonlinearFunction{iip}( - sys, _eqs, _dvs, _obs, cachebufsyms; eval_expression, eval_module, cse, kwargs...) - push!(nlfuns, f) - end - - if !isempty(cachetypes) - templates = map(cachetypes, cachesizes) do T, n - # Real refers to `eltype(u0)` - if T == Real - T = eltype(u0) - elseif T <: Array && eltype(T) == Real - T = Array{eltype(u0), ndims(T)} - end - BufferTemplate(T, n) - end - p = rebuild_with_caches(p, templates...) - end - - subprobs = [] - for (f, vscc) in zip(nlfuns, var_sccs) - _u0 = SymbolicUtils.Code.create_array( - typeof(u0), eltype(u0), Val(1), Val(length(vscc)), u0[vscc]...) - prob = NonlinearProblem{iip}(f, _u0, p) - push!(subprobs, prob) - end - - new_dvs = dvs[reduce(vcat, var_sccs)] - new_eqs = eqs[reduce(vcat, eq_sccs)] - @set! sys.unknowns = new_dvs - @set! sys.eqs = new_eqs - @set! sys.index_cache = subset_unknowns_observed( - get_index_cache(sys), sys, new_dvs, getproperty.(obs, (:lhs,))) - return SCCNonlinearProblem(subprobs, explicitfuns, p, true; sys) -end - -""" -$(TYPEDSIGNATURES) - -Generate an `IntervalNonlinearProblem` from a `NonlinearSystem` and allow for automatically -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`") - end - if !isone(length(unknowns(sys))) || !isone(length(equations(sys))) - error("`IntervalNonlinearProblem` only supports with a single equation and a single unknown.") - end - f, u0, p = process_SciMLProblem( - IntervalNonlinearFunction, sys, unknowns(sys) .=> uspan[1], parammap; kwargs...) - - return IntervalNonlinearProblem(f, uspan, p; filter_kwargs(kwargs)...) -end - -""" -```julia -DiffEqBase.NonlinearProblemExpr{iip}(sys::NonlinearSystem, u0map, - parammap = DiffEqBase.NullParameters(); - jac = false, sparse = false, - checkbounds = false, - linenumbers = true, parallel = SerialForm(), - kwargs...) where {iip} -``` - -Generates a Julia expression for a NonlinearProblem from a -NonlinearSystem and allows for automatically symbolically calculating -numerical enhancements. -""" -struct NonlinearProblemExpr{iip} end - -function NonlinearProblemExpr(sys::NonlinearSystem, args...; kwargs...) - NonlinearProblemExpr{true}(sys, args...; kwargs...) -end - -function NonlinearProblemExpr{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 `NonlinearProblemExpr`") - end - f, u0, p = process_SciMLProblem(NonlinearFunctionExpr{iip}, sys, u0map, parammap; - check_length, kwargs...) - linenumbers = get(kwargs, :linenumbers, true) - - ex = quote - f = $f - u0 = $u0 - p = $p - NonlinearProblem(f, u0, p; $(filter_kwargs(kwargs)...)) - end - !linenumbers ? Base.remove_linenums!(ex) : ex -end - -""" -```julia -DiffEqBase.NonlinearLeastSquaresProblemExpr{iip}(sys::NonlinearSystem, u0map, - parammap = DiffEqBase.NullParameters(); - jac = false, sparse = false, - checkbounds = false, - linenumbers = true, parallel = SerialForm(), - kwargs...) where {iip} -``` - -Generates a Julia expression for a NonlinearProblem from a -NonlinearSystem and allows for automatically symbolically calculating -numerical enhancements. -""" -struct NonlinearLeastSquaresProblemExpr{iip} end - -function NonlinearLeastSquaresProblemExpr(sys::NonlinearSystem, args...; kwargs...) - NonlinearLeastSquaresProblemExpr{true}(sys, args...; kwargs...) -end - -function NonlinearLeastSquaresProblemExpr{iip}(sys::NonlinearSystem, u0map, - 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 `NonlinearProblemExpr`") - end - f, u0, p = process_SciMLProblem(NonlinearFunctionExpr{iip}, sys, u0map, parammap; - check_length, kwargs...) - linenumbers = get(kwargs, :linenumbers, true) - - ex = quote - f = $f - u0 = $u0 - p = $p - NonlinearLeastSquaresProblem(f, u0, p; $(filter_kwargs(kwargs)...)) - end - !linenumbers ? Base.remove_linenums!(ex) : ex -end - -""" -$(TYPEDSIGNATURES) - -Generates a Julia expression for an IntervalNonlinearProblem from a -NonlinearSystem and allows for automatically symbolically calculating -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`") - end - if !isone(length(unknowns(sys))) || !isone(length(equations(sys))) - error("`IntervalNonlinearProblemExpr` only supports with a single equation and a single unknown.") - end - f, u0, p = process_SciMLProblem( - IntervalNonlinearFunctionExpr, sys, unknowns(sys) .=> uspan[1], parammap; kwargs...) - linenumbers = get(kwargs, :linenumbers, true) - - ex = quote - f = $f - uspan = $uspan - p = $p - IntervalNonlinearProblem(f, uspan, p; $(filter_kwargs(kwargs)...)) - end - !linenumbers ? Base.remove_linenums!(ex) : ex -end - -function flatten(sys::NonlinearSystem, noeqs = false) - systems = get_systems(sys) - if isempty(systems) - return sys - else - return NonlinearSystem(noeqs ? Equation[] : equations(sys), - unknowns(sys), - parameters(sys), - observed = observed(sys), - defaults = defaults(sys), - guesses = guesses(sys), - initialization_eqs = initialization_equations(sys), - name = nameof(sys), - description = description(sys), - metadata = get_metadata(sys), - checks = false) - end -end - -function Base.:(==)(sys1::NonlinearSystem, sys2::NonlinearSystem) - isequal(nameof(sys1), nameof(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 From a58d3d3fa7800b803ce9dfac7a51af9db227b4f6 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Mon, 21 Apr 2025 10:50:14 +0530 Subject: [PATCH 054/235] refactor: remove `optimizationsystem.jl` --- src/ModelingToolkit.jl | 3 +- .../optimization/optimizationsystem.jl | 760 ------------------ 2 files changed, 1 insertion(+), 762 deletions(-) delete mode 100644 src/systems/optimization/optimizationsystem.jl diff --git a/src/ModelingToolkit.jl b/src/ModelingToolkit.jl index 66fda07b8c..1bdb215ff0 100644 --- a/src/ModelingToolkit.jl +++ b/src/ModelingToolkit.jl @@ -166,7 +166,6 @@ include("systems/problem_utils.jl") include("linearization.jl") include("systems/optimization/constraints_system.jl") -include("systems/optimization/optimizationsystem.jl") include("systems/optimization/modelingtoolkitize.jl") include("systems/diffeqs/abstractodesystem.jl") @@ -274,7 +273,7 @@ export IntervalNonlinearProblem, IntervalNonlinearProblemExpr export OptimizationProblem, OptimizationProblemExpr, constraints export SteadyStateProblem, SteadyStateProblemExpr export JumpProblem -export OptimizationSystem, ConstraintsSystem +export ConstraintsSystem export alias_elimination, flatten export connect, domain_connect, @connector, Connection, AnalysisPoint, Flow, Stream, instream diff --git a/src/systems/optimization/optimizationsystem.jl b/src/systems/optimization/optimizationsystem.jl deleted file mode 100644 index bfe15b62d7..0000000000 --- a/src/systems/optimization/optimizationsystem.jl +++ /dev/null @@ -1,760 +0,0 @@ -""" -$(TYPEDEF) - -A scalar equation for optimization. - -# Fields -$(FIELDS) - -# Examples - -```julia -@variables x y z -@parameters a b c - -obj = a * (y - x) + x * (b - z) - y + x * y - c * z -cons = [x^2 + y^2 ≲ 1] -@named os = OptimizationSystem(obj, [x, y, z], [a, b, c]; constraints = cons) -``` -""" -struct OptimizationSystem <: AbstractOptimizationSystem - """ - A tag for the system. If two systems have the same tag, then they are - structurally identical. - """ - tag::UInt - """Objective function of the system.""" - op::Any - """Unknown variables.""" - unknowns::Array - """Parameters.""" - ps::Vector - """Array variables.""" - var_to_name::Any - """Observed equations.""" - observed::Vector{Equation} - """List of constraint equations of the system.""" - constraints::Vector{Union{Equation, Inequality}} - """The name of the system.""" - name::Symbol - """A description of the system.""" - description::String - """The internal systems. These are required to have unique names.""" - systems::Vector{OptimizationSystem} - """ - The default values to use when initial guess and/or - parameters are not supplied in `OptimizationProblem`. - """ - defaults::Dict - """ - Metadata for the system, to be used by downstream packages. - """ - metadata::Any - """ - Metadata for MTK GUI. - """ - gui_metadata::Union{Nothing, GUIMetadata} - """ - If false, then `sys.x` no longer performs namespacing. - """ - namespacing::Bool - """ - If true, denotes the model will not be modified any further. - """ - complete::Bool - """ - Cached data for fast symbolic indexing. - """ - index_cache::Union{Nothing, IndexCache} - """ - The hierarchical parent system before simplification. - """ - parent::Any - isscheduled::Bool - - function OptimizationSystem(tag, op, unknowns, ps, var_to_name, observed, - constraints, name, description, systems, defaults, metadata = nothing, - gui_metadata = nothing, namespacing = true, complete = false, - index_cache = nothing, parent = nothing, isscheduled = false; - checks::Union{Bool, Int} = true) - if checks == true || (checks & CheckUnits) > 0 - u = __get_unit_type(unknowns, ps) - unwrap(op) isa Symbolic && check_units(u, op) - check_units(u, observed) - check_units(u, constraints) - check_subsystems(systems) - end - new(tag, op, unknowns, ps, var_to_name, observed, - constraints, name, description, systems, defaults, metadata, gui_metadata, - namespacing, complete, index_cache, parent, isscheduled) - end -end - -equations(sys::AbstractOptimizationSystem) = objective(sys) # needed for Base.show - -function OptimizationSystem(op, unknowns, ps; - observed = [], - constraints = [], - default_u0 = Dict(), - default_p = Dict(), - defaults = _merge(Dict(default_u0), Dict(default_p)), - name = nothing, - description = "", - systems = OptimizationSystem[], - checks = true, - metadata = nothing, - gui_metadata = nothing) - name === nothing && - throw(ArgumentError("The `name` keyword must be provided. Please consider using the `@named` macro")) - constraints = value.(reduce(vcat, scalarize(constraints); init = [])) - unknowns′ = value.(reduce(vcat, scalarize(unknowns); init = [])) - ps′ = value.(ps) - op′ = value(scalarize(op)) - - irreducible_subs = Dict() - for i in eachindex(unknowns′) - var = unknowns′[i] - if hasbounds(var) - irreducible_subs[var] = irrvar = setirreducible(var, true) - unknowns′[i] = irrvar - end - end - op′ = fast_substitute(op′, irreducible_subs) - constraints = fast_substitute.(constraints, (irreducible_subs,)) - - if !(isempty(default_u0) && isempty(default_p)) - Base.depwarn( - "`default_u0` and `default_p` are deprecated. Use `defaults` instead.", - :OptimizationSystem, force = true) - end - sysnames = nameof.(systems) - if length(unique(sysnames)) != length(sysnames) - throw(ArgumentError("System names must be unique.")) - end - defaults = todict(defaults) - defaults = Dict(fast_substitute(value(k), irreducible_subs) => fast_substitute( - value(v), irreducible_subs) - for (k, v) in pairs(defaults) if value(v) !== nothing) - - var_to_name = Dict() - process_variables!(var_to_name, defaults, Dict(), unknowns′) - process_variables!(var_to_name, defaults, Dict(), ps′) - isempty(observed) || collect_var_to_name!(var_to_name, (eq.lhs for eq in observed)) - - OptimizationSystem(Threads.atomic_add!(SYSTEM_COUNT, UInt(1)), - op′, unknowns′, ps′, var_to_name, - observed, - constraints, - name, description, systems, defaults, metadata, gui_metadata; - checks = checks) -end - -function OptimizationSystem(objective; constraints = [], kwargs...) - allunknowns = OrderedSet() - ps = OrderedSet() - collect_vars!(allunknowns, ps, objective, nothing) - for cons in constraints - collect_vars!(allunknowns, ps, cons, nothing) - end - for ssys in get(kwargs, :systems, OptimizationSystem[]) - collect_scoped_vars!(allunknowns, ps, ssys, nothing) - end - new_ps = OrderedSet() - for p in ps - if iscall(p) && operation(p) === getindex - par = arguments(p)[begin] - if Symbolics.shape(Symbolics.unwrap(par)) !== Symbolics.Unknown() && - all(par[i] in ps for i in eachindex(par)) - push!(new_ps, par) - else - push!(new_ps, p) - end - else - push!(new_ps, p) - end - end - return OptimizationSystem( - objective, collect(allunknowns), collect(new_ps); constraints, kwargs...) -end - -function flatten(sys::OptimizationSystem) - systems = get_systems(sys) - isempty(systems) && return sys - - return OptimizationSystem( - objective(sys), - unknowns(sys), - parameters(sys); - observed = observed(sys), - constraints = constraints(sys), - defaults = defaults(sys), - name = nameof(sys), - metadata = get_metadata(sys), - checks = false - ) -end - -function calculate_gradient(sys::OptimizationSystem) - expand_derivatives.(gradient(objective(sys), unknowns(sys))) -end - -function generate_gradient(sys::OptimizationSystem, vs = unknowns(sys), - ps = parameters(sys; initial_parameters = true); kwargs...) - grad = calculate_gradient(sys) - p = reorder_parameters(sys, ps) - return build_function_wrapper(sys, grad, vs, p...; kwargs...) -end - -function calculate_hessian(sys::OptimizationSystem) - expand_derivatives.(hessian(objective(sys), unknowns(sys))) -end - -function generate_hessian( - sys::OptimizationSystem, vs = unknowns(sys), ps = parameters( - sys; initial_parameters = true); - sparse = false, kwargs...) - if sparse - hess = sparsehessian(objective(sys), unknowns(sys)) - else - hess = calculate_hessian(sys) - end - p = reorder_parameters(sys, ps) - return build_function_wrapper(sys, hess, vs, p...; kwargs...) -end - -function generate_function(sys::OptimizationSystem, vs = unknowns(sys), - ps = parameters(sys; initial_parameters = true); - kwargs...) - eqs = objective(sys) - p = reorder_parameters(sys, ps) - return build_function_wrapper(sys, eqs, vs, p...; kwargs...) -end - -function namespace_objective(sys::AbstractSystem) - op = objective(sys) - namespace_expr(op, sys) -end - -function objective(sys) - op = get_op(sys) - systems = get_systems(sys) - if isempty(systems) - op - else - op + reduce(+, map(sys_ -> namespace_objective(sys_), systems)) - end -end - -namespace_constraint(eq::Equation, sys) = namespace_equation(eq, sys) - -namespace_constraint(ineq::Inequality, sys) = namespace_inequality(ineq, sys) - -function namespace_inequality(ineq::Inequality, sys, n = nameof(sys)) - _lhs = namespace_expr(ineq.lhs, sys, n) - _rhs = namespace_expr(ineq.rhs, sys, n) - Inequality(_lhs, - _rhs, - ineq.relational_op) -end - -function namespace_constraints(sys) - cstrs = constraints(sys) - isempty(cstrs) && return Vector{Union{Equation, Inequality}}(undef, 0) - map(cstr -> namespace_constraint(cstr, sys), cstrs) -end - -function constraints(sys) - cs = get_constraints(sys) - systems = get_systems(sys) - isempty(systems) ? cs : [cs; reduce(vcat, namespace_constraints.(systems))] -end - -hessian_sparsity(sys::OptimizationSystem) = hessian_sparsity(get_op(sys), unknowns(sys)) - -""" -```julia -DiffEqBase.OptimizationProblem{iip}(sys::OptimizationSystem, u0map, - parammap = DiffEqBase.NullParameters(); - grad = false, - hess = false, sparse = false, - cons_j = false, cons_h = false, - checkbounds = false, - linenumbers = true, parallel = SerialForm(), - kwargs...) where {iip} -``` - -Generates an OptimizationProblem from an OptimizationSystem and allows for automatically -symbolically calculating numerical enhancements. - -Certain solvers require setting `cons_j`, `cons_h` to `true` for constrained-optimization problems. -""" -function DiffEqBase.OptimizationProblem(sys::OptimizationSystem, args...; kwargs...) - DiffEqBase.OptimizationProblem{true}(sys::OptimizationSystem, args...; kwargs...) -end -function DiffEqBase.OptimizationProblem{iip}(sys::OptimizationSystem, u0map, - parammap = DiffEqBase.NullParameters(); - lb = nothing, ub = nothing, - grad = false, - hess = false, sparse = false, - cons_j = false, cons_h = false, - cons_sparse = false, checkbounds = false, - linenumbers = true, parallel = SerialForm(), - eval_expression = false, eval_module = @__MODULE__, - 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`") - end - if haskey(kwargs, :lcons) || haskey(kwargs, :ucons) - Base.depwarn( - "`lcons` and `ucons` are deprecated. Specify constraints directly instead.", - :OptimizationProblem, force = true) - end - - dvs = unknowns(sys) - ps = parameters(sys) - cstr = constraints(sys) - - if isnothing(lb) && isnothing(ub) # use the symbolically specified bounds - lb = first.(getbounds.(dvs)) - ub = last.(getbounds.(dvs)) - isboolean = symtype.(unwrap.(dvs)) .<: Bool - lb[isboolean] .= 0 - ub[isboolean] .= 1 - else # use the user supplied variable bounds - xor(isnothing(lb), isnothing(ub)) && - throw(ArgumentError("Expected both `lb` and `ub` to be supplied")) - !isnothing(lb) && length(lb) != length(dvs) && - throw(ArgumentError("Expected both `lb` to be of the same length as the vector of optimization variables")) - !isnothing(ub) && length(ub) != length(dvs) && - throw(ArgumentError("Expected both `ub` to be of the same length as the vector of optimization variables")) - end - - int = symtype.(unwrap.(dvs)) .<: Integer - - defs = defaults(sys) - defs = mergedefaults(defs, parammap, ps) - defs = mergedefaults(defs, u0map, dvs) - - u0 = varmap_to_vars(u0map, dvs; defaults = defs, tofloat = false) - if parammap isa MTKParameters - p = parammap - elseif has_index_cache(sys) && get_index_cache(sys) !== nothing - p = MTKParameters(sys, parammap, u0map) - else - p = varmap_to_vars(parammap, ps; defaults = defs, tofloat = false) - end - lb = varmap_to_vars(dvs .=> lb, dvs; defaults = defs, tofloat = false) - ub = varmap_to_vars(dvs .=> ub, dvs; defaults = defs, tofloat = false) - - if !isnothing(lb) && all(lb .== -Inf) && !isnothing(ub) && all(ub .== Inf) - lb = nothing - ub = nothing - end - - f = let _f = eval_or_rgf( - generate_function( - sys; checkbounds = checkbounds, linenumbers = linenumbers, - expression = Val{true}, wrap_mtkparameters = false, cse); - eval_expression, - eval_module) - __f(u, p) = _f(u, p) - __f(u, p::MTKParameters) = _f(u, p...) - __f - end - obj_expr = subs_constants(objective(sys)) - - if grad - _grad = let (grad_oop, grad_iip) = eval_or_rgf.( - generate_gradient( - sys; checkbounds = checkbounds, - linenumbers = linenumbers, - parallel = parallel, expression = Val{true}, - wrap_mtkparameters = false, cse); - eval_expression, - eval_module) - _grad(u, p) = grad_oop(u, p) - _grad(J, u, p) = (grad_iip(J, u, p); J) - _grad(u, p::MTKParameters) = grad_oop(u, p...) - _grad(J, u, p::MTKParameters) = (grad_iip(J, u, p...); J) - _grad - end - else - _grad = nothing - end - - if hess - _hess = let (hess_oop, hess_iip) = eval_or_rgf.( - generate_hessian( - sys; checkbounds = checkbounds, - linenumbers = linenumbers, - sparse = sparse, parallel = parallel, - expression = Val{true}, wrap_mtkparameters = false, cse); - eval_expression, - eval_module) - _hess(u, p) = hess_oop(u, p) - _hess(J, u, p) = (hess_iip(J, u, p); J) - _hess(u, p::MTKParameters) = hess_oop(u, p...) - _hess(J, u, p::MTKParameters) = (hess_iip(J, u, p...); J) - _hess - end - else - _hess = nothing - end - - if sparse - hess_prototype = hessian_sparsity(sys) - else - hess_prototype = nothing - end - - observedfun = ObservedFunctionCache(sys; eval_expression, eval_module, checkbounds, cse) - - if length(cstr) > 0 - @named cons_sys = ConstraintsSystem(cstr, dvs, ps; checks) - cons_sys = complete(cons_sys) - cons, lcons_, ucons_ = generate_function(cons_sys; checkbounds = checkbounds, - linenumbers = linenumbers, - expression = Val{true}, wrap_mtkparameters = false, cse) - cons = let (cons_oop, cons_iip) = eval_or_rgf.(cons; eval_expression, eval_module) - _cons(u, p) = cons_oop(u, p) - _cons(resid, u, p) = cons_iip(resid, u, p) - _cons(u, p::MTKParameters) = cons_oop(u, p...) - _cons(resid, u, p::MTKParameters) = cons_iip(resid, u, p...) - end - if cons_j - _cons_j = let (cons_jac_oop, cons_jac_iip) = eval_or_rgf.( - generate_jacobian(cons_sys; - checkbounds = checkbounds, - linenumbers = linenumbers, - parallel = parallel, expression = Val{true}, - sparse = cons_sparse, wrap_mtkparameters = false, cse); - eval_expression, - eval_module) - _cons_j(u, p) = cons_jac_oop(u, p) - _cons_j(J, u, p) = (cons_jac_iip(J, u, p); J) - _cons_j(u, p::MTKParameters) = cons_jac_oop(u, p...) - _cons_j(J, u, p::MTKParameters) = (cons_jac_iip(J, u, p...); J) - _cons_j - end - else - _cons_j = nothing - end - if cons_h - _cons_h = let (cons_hess_oop, cons_hess_iip) = eval_or_rgf.( - generate_hessian( - cons_sys; checkbounds = checkbounds, - linenumbers = linenumbers, - sparse = cons_sparse, parallel = parallel, - expression = Val{true}, wrap_mtkparameters = false, cse); - eval_expression, - eval_module) - _cons_h(u, p) = cons_hess_oop(u, p) - _cons_h(J, u, p) = (cons_hess_iip(J, u, p); J) - _cons_h(u, p::MTKParameters) = cons_hess_oop(u, p...) - _cons_h(J, u, p::MTKParameters) = (cons_hess_iip(J, u, p...); J) - _cons_h - end - else - _cons_h = nothing - end - cons_expr = subs_constants(constraints(cons_sys)) - - if !haskey(kwargs, :lcons) && !haskey(kwargs, :ucons) # use the symbolically specified bounds - lcons = lcons_ - ucons = ucons_ - else # use the user supplied constraints bounds - (haskey(kwargs, :lcons) ⊻ haskey(kwargs, :ucons)) && - throw(ArgumentError("Expected both `ucons` and `lcons` to be supplied")) - haskey(kwargs, :lcons) && length(kwargs[:lcons]) != length(cstr) && - throw(ArgumentError("Expected `lcons` to be of the same length as the vector of constraints")) - haskey(kwargs, :ucons) && length(kwargs[:ucons]) != length(cstr) && - throw(ArgumentError("Expected `ucons` to be of the same length as the vector of constraints")) - lcons = haskey(kwargs, :lcons) - ucons = haskey(kwargs, :ucons) - end - - if cons_sparse - cons_jac_prototype = jacobian_sparsity(cons_sys) - cons_hess_prototype = hessian_sparsity(cons_sys) - else - cons_jac_prototype = nothing - cons_hess_prototype = nothing - end - _f = DiffEqBase.OptimizationFunction{iip}(f, - sys = sys, - SciMLBase.NoAD(); - grad = _grad, - hess = _hess, - hess_prototype = hess_prototype, - cons = cons, - cons_j = _cons_j, - cons_h = _cons_h, - cons_jac_prototype = cons_jac_prototype, - cons_hess_prototype = cons_hess_prototype, - expr = obj_expr, - cons_expr = cons_expr, - observed = observedfun) - OptimizationProblem{iip}(_f, u0, p; lb = lb, ub = ub, int = int, - lcons = lcons, ucons = ucons, kwargs...) - else - _f = DiffEqBase.OptimizationFunction{iip}(f, - sys = sys, - SciMLBase.NoAD(); - grad = _grad, - hess = _hess, - hess_prototype = hess_prototype, - expr = obj_expr, - observed = observedfun) - OptimizationProblem{iip}(_f, u0, p; lb = lb, ub = ub, int = int, - kwargs...) - end -end - -""" -```julia -DiffEqBase.OptimizationProblemExpr{iip}(sys::OptimizationSystem, - parammap = DiffEqBase.NullParameters(); - u0 = nothing, - grad = false, - hes = false, sparse = false, - checkbounds = false, - linenumbers = true, parallel = SerialForm(), - kwargs...) where {iip} -``` - -Generates a Julia expression for an OptimizationProblem from an -OptimizationSystem and allows for automatically symbolically -calculating numerical enhancements. -""" -struct OptimizationProblemExpr{iip} end - -function OptimizationProblemExpr(sys::OptimizationSystem, args...; kwargs...) - OptimizationProblemExpr{true}(sys::OptimizationSystem, args...; kwargs...) -end - -function OptimizationProblemExpr{iip}(sys::OptimizationSystem, u0map, - parammap = DiffEqBase.NullParameters(); - lb = nothing, ub = nothing, - grad = false, - hess = false, sparse = false, - cons_j = false, cons_h = false, - checkbounds = false, - linenumbers = false, parallel = SerialForm(), - 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`") - end - if haskey(kwargs, :lcons) || haskey(kwargs, :ucons) - Base.depwarn( - "`lcons` and `ucons` are deprecated. Specify constraints directly instead.", - :OptimizationProblem, force = true) - end - - dvs = unknowns(sys) - ps = parameters(sys) - cstr = constraints(sys) - - if isnothing(lb) && isnothing(ub) # use the symbolically specified bounds - lb = first.(getbounds.(dvs)) - ub = last.(getbounds.(dvs)) - isboolean = symtype.(unwrap.(dvs)) .<: Bool - lb[isboolean] .= 0 - ub[isboolean] .= 1 - else # use the user supplied variable bounds - xor(isnothing(lb), isnothing(ub)) && - throw(ArgumentError("Expected both `lb` and `ub` to be supplied")) - !isnothing(lb) && length(lb) != length(dvs) && - throw(ArgumentError("Expected `lb` to be of the same length as the vector of optimization variables")) - !isnothing(ub) && length(ub) != length(dvs) && - throw(ArgumentError("Expected `ub` to be of the same length as the vector of optimization variables")) - end - - int = symtype.(unwrap.(dvs)) .<: Integer - - defs = defaults(sys) - defs = mergedefaults(defs, parammap, ps) - defs = mergedefaults(defs, u0map, dvs) - - u0 = varmap_to_vars(u0map, dvs; defaults = defs, tofloat = false) - if has_index_cache(sys) && get_index_cache(sys) !== nothing - p = MTKParameters(sys, parammap, u0map) - else - p = varmap_to_vars(parammap, ps; defaults = defs, tofloat = false) - end - lb = varmap_to_vars(dvs .=> lb, dvs; defaults = defs, tofloat = false) - ub = varmap_to_vars(dvs .=> ub, dvs; defaults = defs, tofloat = false) - - if !isnothing(lb) && all(lb .== -Inf) && !isnothing(ub) && all(ub .== Inf) - lb = nothing - ub = nothing - end - - idx = iip ? 2 : 1 - f = generate_function(sys, checkbounds = checkbounds, linenumbers = linenumbers, - expression = Val{true}) - if grad - _grad = eval_or_rgf( - generate_gradient( - sys, checkbounds = checkbounds, linenumbers = linenumbers, - parallel = parallel, expression = Val{true})[idx]; - eval_expression, - eval_module) - else - _grad = :nothing - end - - if hess - _hess = eval_or_rgf( - generate_hessian(sys, checkbounds = checkbounds, linenumbers = linenumbers, - sparse = sparse, parallel = parallel, - expression = Val{false})[idx]; - eval_expression, - eval_module) - else - _hess = :nothing - end - - if sparse - hess_prototype = hessian_sparsity(sys) - else - hess_prototype = nothing - end - - obj_expr = toexpr(subs_constants(objective(sys))) - pairs_arr = if p isa SciMLBase.NullParameters - [Symbol(_s) => Expr(:ref, :x, i) for (i, _s) in enumerate(dvs)] - else - vcat([Symbol(_s) => Expr(:ref, :x, i) for (i, _s) in enumerate(dvs)], - [Symbol(_p) => p[i] for (i, _p) in enumerate(ps)]) - end - rep_pars_vals!(obj_expr, pairs_arr) - - if length(cstr) > 0 - @named cons_sys = ConstraintsSystem(cstr, dvs, ps) - cons, lcons_, ucons_ = generate_function(cons_sys, checkbounds = checkbounds, - linenumbers = linenumbers, - expression = Val{true}) - cons = eval_or_rgf(cons; eval_expression, eval_module) - if cons_j - _cons_j = eval_or_rgf( - generate_jacobian(cons_sys; expression = Val{true}, sparse = sparse)[2]; - eval_expression, eval_module) - else - _cons_j = nothing - end - if cons_h - _cons_h = eval_or_rgf( - generate_hessian(cons_sys; expression = Val{true}, sparse = sparse)[2]; - eval_expression, eval_module) - else - _cons_h = nothing - end - - cons_expr = toexpr.(subs_constants(constraints(cons_sys))) - rep_pars_vals!.(cons_expr, Ref(pairs_arr)) - - if !haskey(kwargs, :lcons) && !haskey(kwargs, :ucons) # use the symbolically specified bounds - lcons = lcons_ - ucons = ucons_ - else # use the user supplied constraints bounds - (haskey(kwargs, :lcons) ⊻ haskey(kwargs, :ucons)) && - throw(ArgumentError("Expected both `ucons` and `lcons` to be supplied")) - haskey(kwargs, :lcons) && length(kwargs[:lcons]) != length(cstr) && - throw(ArgumentError("Expected `lcons` to be of the same length as the vector of constraints")) - haskey(kwargs, :ucons) && length(kwargs[:ucons]) != length(cstr) && - throw(ArgumentError("Expected `ucons` to be of the same length as the vector of constraints")) - lcons = haskey(kwargs, :lcons) - ucons = haskey(kwargs, :ucons) - end - - if sparse - cons_jac_prototype = jacobian_sparsity(cons_sys) - cons_hess_prototype = hessian_sparsity(cons_sys) - else - cons_jac_prototype = nothing - cons_hess_prototype = nothing - end - - quote - f = $f - p = $p - u0 = $u0 - grad = $_grad - hess = $_hess - lb = $lb - ub = $ub - int = $int - cons = $cons[1] - lcons = $lcons - ucons = $ucons - cons_j = $_cons_j - cons_h = $_cons_h - _f = OptimizationFunction{iip}(f, SciMLBase.NoAD(); - grad = grad, - hess = hess, - hess_prototype = hess_prototype, - cons = cons, - cons_j = cons_j, - cons_h = cons_h, - cons_jac_prototype = cons_jac_prototype, - cons_hess_prototype = cons_hess_prototype, - expr = obj_expr, - cons_expr = cons_expr) - OptimizationProblem{$iip}( - _f, u0, p; lb = lb, ub = ub, int = int, lcons = lcons, - ucons = ucons, kwargs...) - end - else - quote - f = $f - p = $p - u0 = $u0 - grad = $_grad - hess = $_hess - lb = $lb - ub = $ub - int = $int - _f = OptimizationFunction{iip}(f, SciMLBase.NoAD(); - grad = grad, - hess = hess, - hess_prototype = hess_prototype, - expr = obj_expr) - OptimizationProblem{$iip}(_f, u0, p; lb = lb, ub = ub, int = int, kwargs...) - end - end -end - -function structural_simplify(sys::OptimizationSystem; split = true, kwargs...) - sys = flatten(sys) - cons = constraints(sys) - econs = Equation[] - icons = similar(cons, 0) - for e in cons - if e isa Equation - push!(econs, e) - else - push!(icons, e) - end - end - nlsys = NonlinearSystem(econs, unknowns(sys), parameters(sys); name = :___tmp_nlsystem) - 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) - cons_simplified = similar(cons, length(icons) + length(seqs)) - for (i, eq) in enumerate(Iterators.flatten((seqs, icons))) - cons_simplified[i] = fixpoint_sub(eq, subs) - end - newsts = setdiff(unknowns(sys), keys(subs)) - @set! sys.constraints = cons_simplified - @set! sys.observed = [observed(sys); obs] - neweqs = fixpoint_sub.(equations(sys), (subs,)) - @set! sys.op = length(neweqs) == 1 ? first(neweqs) : neweqs - @set! sys.unknowns = newsts - sys = complete(sys; split) - return sys -end - -supports_initialization(::OptimizationSystem) = false From 5eb6a1c0ee645a07d99af5b565aabab6873e990b Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Mon, 21 Apr 2025 10:51:39 +0530 Subject: [PATCH 055/235] refactor: remove `constraints_system.jl` --- src/ModelingToolkit.jl | 2 - .../optimization/constraints_system.jl | 255 ------------------ 2 files changed, 257 deletions(-) delete mode 100644 src/systems/optimization/constraints_system.jl diff --git a/src/ModelingToolkit.jl b/src/ModelingToolkit.jl index 1bdb215ff0..d99730cf08 100644 --- a/src/ModelingToolkit.jl +++ b/src/ModelingToolkit.jl @@ -165,7 +165,6 @@ include("systems/codegen_utils.jl") include("systems/problem_utils.jl") include("linearization.jl") -include("systems/optimization/constraints_system.jl") include("systems/optimization/modelingtoolkitize.jl") include("systems/diffeqs/abstractodesystem.jl") @@ -273,7 +272,6 @@ export IntervalNonlinearProblem, IntervalNonlinearProblemExpr export OptimizationProblem, OptimizationProblemExpr, constraints export SteadyStateProblem, SteadyStateProblemExpr export JumpProblem -export ConstraintsSystem export alias_elimination, flatten export connect, domain_connect, @connector, Connection, AnalysisPoint, Flow, Stream, instream diff --git a/src/systems/optimization/constraints_system.jl b/src/systems/optimization/constraints_system.jl deleted file mode 100644 index ae8577662d..0000000000 --- a/src/systems/optimization/constraints_system.jl +++ /dev/null @@ -1,255 +0,0 @@ -""" -$(TYPEDEF) - -A constraint system of equations. - -# Fields -$(FIELDS) - -# Examples - -```julia -@variables x y z -@parameters a b c - -cstr = [0 ~ a*(y-x), - 0 ~ x*(b-z)-y, - 0 ~ x*y - c*z - x^2 + y^2 ≲ 1] -@named ns = ConstraintsSystem(cstr, [x,y,z],[a,b,c]) -``` -""" -struct ConstraintsSystem <: AbstractTimeIndependentSystem - """ - A tag for the system. If two systems have the same tag, then they are - structurally identical. - """ - tag::UInt - """Vector of equations defining the system.""" - constraints::Vector{Union{Equation, Inequality}} - """Unknown variables.""" - unknowns::Vector - """Parameters.""" - ps::Vector - """Array variables.""" - var_to_name::Any - """Observed equations.""" - observed::Vector{Equation} - """ - Jacobian matrix. Note: this field will not be defined until - [`calculate_jacobian`](@ref) is called on the system. - """ - jac::RefValue{Any} - """ - The name of the system. - """ - name::Symbol - """ - A description of the system. - """ - description::String - """ - The internal systems. These are required to have unique names. - """ - systems::Vector{ConstraintsSystem} - """ - The default values to use when initial conditions and/or - parameters are not supplied in `ODEProblem`. - """ - defaults::Dict - """ - Type of the system. - """ - connector_type::Any - """ - Metadata for the system, to be used by downstream packages. - """ - metadata::Any - """ - Cache for intermediate tearing state. - """ - tearing_state::Any - """ - Substitutions generated by tearing. - """ - substitutions::Any - """ - If false, then `sys.x` no longer performs namespacing. - """ - namespacing::Bool - """ - If true, denotes the model will not be modified any further. - """ - complete::Bool - """ - Cached data for fast symbolic indexing. - """ - index_cache::Union{Nothing, IndexCache} - - function ConstraintsSystem(tag, constraints, unknowns, ps, var_to_name, observed, jac, - name, description, - systems, - defaults, connector_type, metadata = nothing, - tearing_state = nothing, substitutions = nothing, namespacing = true, - complete = false, index_cache = nothing; - checks::Union{Bool, Int} = true) - if checks == true || (checks & CheckUnits) > 0 - u = __get_unit_type(unknowns, ps) - check_units(u, constraints) - check_subsystems(systems) - end - new(tag, constraints, unknowns, ps, var_to_name, - observed, jac, name, description, systems, - defaults, connector_type, metadata, tearing_state, substitutions, - namespacing, complete, index_cache) - end -end - -equations(sys::ConstraintsSystem) = constraints(sys) # needed for Base.show - -function ConstraintsSystem(constraints, unknowns, ps; - observed = [], - name = nothing, - description = "", - default_u0 = Dict(), - default_p = Dict(), - defaults = _merge(Dict(default_u0), Dict(default_p)), - systems = ConstraintsSystem[], - connector_type = nothing, - continuous_events = nothing, # this argument is only required for ODESystems, but is added here for the constructor to accept it without error - discrete_events = nothing, # this argument is only required for ODESystems, but is added here for the constructor to accept it without error - checks = true, - metadata = nothing) - continuous_events === nothing || isempty(continuous_events) || - throw(ArgumentError("ConstraintsSystem does not accept `continuous_events`, you provided $continuous_events")) - discrete_events === nothing || isempty(discrete_events) || - throw(ArgumentError("ConstraintsSystem does not accept `discrete_events`, you provided $discrete_events")) - - name === nothing && - throw(ArgumentError("The `name` keyword must be provided. Please consider using the `@named` macro")) - - cstr = value.(Symbolics.canonical_form.(vcat(scalarize(constraints)...))) - unknowns′ = value.(scalarize(unknowns)) - ps′ = value.(ps) - - if !(isempty(default_u0) && isempty(default_p)) - Base.depwarn( - "`default_u0` and `default_p` are deprecated. Use `defaults` instead.", - :ConstraintsSystem, force = true) - end - sysnames = nameof.(systems) - if length(unique(sysnames)) != length(sysnames) - throw(ArgumentError("System names must be unique.")) - end - - jac = RefValue{Any}(EMPTY_JAC) - defaults = todict(defaults) - defaults = Dict(value(k) => value(v) - for (k, v) in pairs(defaults) if value(v) !== nothing) - - var_to_name = Dict() - process_variables!(var_to_name, defaults, Dict(), unknowns′) - process_variables!(var_to_name, defaults, Dict(), ps′) - isempty(observed) || collect_var_to_name!(var_to_name, (eq.lhs for eq in observed)) - - ConstraintsSystem(Threads.atomic_add!(SYSTEM_COUNT, UInt(1)), - cstr, unknowns, ps, var_to_name, observed, jac, name, description, systems, - defaults, - connector_type, metadata, checks = checks) -end - -function calculate_jacobian(sys::ConstraintsSystem; sparse = false, simplify = false) - cache = get_jac(sys)[] - if cache isa Tuple && cache[2] == (sparse, simplify) - return cache[1] - end - - lhss = generate_canonical_form_lhss(sys) - vals = [dv for dv in unknowns(sys)] - if sparse - jac = sparsejacobian(lhss, vals, simplify = simplify) - else - jac = jacobian(lhss, vals, simplify = simplify) - end - get_jac(sys)[] = jac, (sparse, simplify) - return jac -end - -function generate_jacobian( - sys::ConstraintsSystem, vs = unknowns(sys), ps = parameters( - sys; initial_parameters = true); - sparse = false, simplify = false, kwargs...) - jac = calculate_jacobian(sys, sparse = sparse, simplify = simplify) - p = reorder_parameters(sys, ps) - return build_function_wrapper(sys, jac, vs, p...; kwargs...) -end - -function calculate_hessian(sys::ConstraintsSystem; sparse = false, simplify = false) - lhss = generate_canonical_form_lhss(sys) - vals = [dv for dv in unknowns(sys)] - if sparse - hess = [sparsehessian(lhs, vals, simplify = simplify) for lhs in lhss] - else - hess = [hessian(lhs, vals, simplify = simplify) for lhs in lhss] - end - return hess -end - -function generate_hessian( - sys::ConstraintsSystem, vs = unknowns(sys), ps = parameters( - sys; initial_parameters = true); - sparse = false, simplify = false, kwargs...) - hess = calculate_hessian(sys, sparse = sparse, simplify = simplify) - p = reorder_parameters(sys, ps) - return build_function_wrapper(sys, hess, vs, p...; kwargs...) -end - -function generate_function(sys::ConstraintsSystem, dvs = unknowns(sys), - ps = parameters(sys; initial_parameters = true); - kwargs...) - lhss = generate_canonical_form_lhss(sys) - p = reorder_parameters(sys, value.(ps)) - func = build_function_wrapper(sys, lhss, value.(dvs), p...; kwargs...) - - cstr = constraints(sys) - lcons = fill(-Inf, length(cstr)) - ucons = zeros(length(cstr)) - lcons[findall(Base.Fix2(isa, Equation), cstr)] .= 0.0 - - return func, lcons, ucons -end - -function jacobian_sparsity(sys::ConstraintsSystem) - lhss = generate_canonical_form_lhss(sys) - jacobian_sparsity(lhss, unknowns(sys)) -end - -function hessian_sparsity(sys::ConstraintsSystem) - lhss = generate_canonical_form_lhss(sys) - [hessian_sparsity(eq, unknowns(sys)) for eq in lhss] -end - -""" -Convert the system of equalities and inequalities into a canonical form: -h(x) = 0 -g(x) <= 0 -""" -function generate_canonical_form_lhss(sys) - lhss = subs_constants([Symbolics.canonical_form(eq).lhs for eq in constraints(sys)]) -end - -function get_cmap(sys::ConstraintsSystem, exprs = nothing) - #Inject substitutions for constants => values - cs = collect_constants([get_constraints(sys); get_observed(sys)]) #ctrls? what else? - if !empty_substitutions(sys) - cs = [cs; collect_constants(get_substitutions(sys).subs)] - end - if exprs !== nothing - cs = [cs; collect_constants(exprs)] - end - # Swap constants for their values - cmap = map(x -> x ~ getdefault(x), cs) - return cmap, cs -end - -supports_initialization(::ConstraintsSystem) = false From adfb917d7c261b8fc99d2e0fa0c7da887a0c394f Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Wed, 14 May 2025 12:10:51 +0530 Subject: [PATCH 056/235] remove abstractodesystem.jl refactor: remove `abstractodesystem.jl` refactor: remove `AbstractODESystem` --- src/ModelingToolkit.jl | 2 - src/systems/diffeqs/abstractodesystem.jl | 1559 ---------------------- 2 files changed, 1561 deletions(-) delete mode 100644 src/systems/diffeqs/abstractodesystem.jl diff --git a/src/ModelingToolkit.jl b/src/ModelingToolkit.jl index d99730cf08..9dd89d452c 100644 --- a/src/ModelingToolkit.jl +++ b/src/ModelingToolkit.jl @@ -130,7 +130,6 @@ TODO abstract type AbstractSystem end abstract type AbstractTimeDependentSystem <: AbstractSystem end abstract type AbstractTimeIndependentSystem <: AbstractSystem end -abstract type AbstractODESystem <: AbstractTimeDependentSystem end abstract type AbstractMultivariateSystem <: AbstractSystem end abstract type AbstractOptimizationSystem <: AbstractTimeIndependentSystem end abstract type AbstractDiscreteSystem <: AbstractTimeDependentSystem end @@ -167,7 +166,6 @@ include("linearization.jl") include("systems/optimization/modelingtoolkitize.jl") -include("systems/diffeqs/abstractodesystem.jl") include("systems/nonlinear/homotopy_continuation.jl") include("systems/nonlinear/modelingtoolkitize.jl") include("systems/nonlinear/initializesystem.jl") diff --git a/src/systems/diffeqs/abstractodesystem.jl b/src/systems/diffeqs/abstractodesystem.jl deleted file mode 100644 index d802f49fee..0000000000 --- a/src/systems/diffeqs/abstractodesystem.jl +++ /dev/null @@ -1,1559 +0,0 @@ -struct Schedule - var_eq_matching::Any - dummy_sub::Any -end - -""" - is_dde(sys::AbstractSystem) - -Return a boolean indicating whether a system represents a set of delay -differential equations. -""" -is_dde(sys::AbstractSystem) = has_is_dde(sys) && get_is_dde(sys) - -function _check_if_dde(eqs, iv, subsystems) - is_dde = any(ModelingToolkit.is_dde, subsystems) - if !is_dde - vs = Set() - for eq in eqs - vars!(vs, eq) - is_dde = any(vs) do sym - isdelay(unwrap(sym), iv) - end - is_dde && break - end - end - return is_dde -end - -function filter_kwargs(kwargs) - kwargs = Dict(kwargs) - for key in keys(kwargs) - key in DiffEqBase.allowedkeywords || delete!(kwargs, key) - end - pairs(NamedTuple(kwargs)) -end -function gen_quoted_kwargs(kwargs) - kwargparam = Expr(:parameters) - for kw in kwargs - push!(kwargparam.args, Expr(:kw, kw[1], kw[2])) - end - kwargparam -end - -function calculate_tgrad(sys::AbstractODESystem; - simplify = false) - isempty(get_tgrad(sys)[]) || return get_tgrad(sys)[] # use cached tgrad, if possible - - # We need to remove explicit time dependence on the unknown because when we - # have `u(t) * t` we want to have the tgrad to be `u(t)` instead of `u'(t) * - # t + u(t)`. - rhs = [detime_dvs(eq.rhs) for eq in full_equations(sys)] - iv = get_iv(sys) - xs = unknowns(sys) - rule = Dict(map((x, xt) -> xt => x, detime_dvs.(xs), xs)) - rhs = substitute.(rhs, Ref(rule)) - tgrad = [expand_derivatives(Differential(iv)(r), simplify) for r in rhs] - reverse_rule = Dict(map((x, xt) -> x => xt, detime_dvs.(xs), xs)) - tgrad = Num.(substitute.(tgrad, Ref(reverse_rule))) - get_tgrad(sys)[] = tgrad - return tgrad -end - -function calculate_jacobian(sys::AbstractODESystem; - sparse = false, simplify = false, dvs = unknowns(sys)) - if isequal(dvs, unknowns(sys)) - cache = get_jac(sys)[] - if cache isa Tuple && cache[2] == (sparse, simplify) - return cache[1] - end - end - - rhs = [eq.rhs - eq.lhs for eq in full_equations(sys)] #need du terms on rhs for differentiating wrt du - - if sparse - jac = sparsejacobian(rhs, dvs, simplify = simplify) - W_s = W_sparsity(sys) - (Is, Js, Vs) = findnz(W_s) - # Add nonzeros of W as non-structural zeros of the Jacobian (to ensure equal results for oop and iip Jacobian.) - for (i, j) in zip(Is, Js) - iszero(jac[i, j]) && begin - jac[i, j] = 1 - jac[i, j] = 0 - end - end - else - jac = jacobian(rhs, dvs, simplify = simplify) - end - - if isequal(dvs, unknowns(sys)) - get_jac(sys)[] = jac, (sparse, simplify) # cache Jacobian - end - - return jac -end - -function calculate_control_jacobian(sys::AbstractODESystem; - sparse = false, simplify = false) - cache = get_ctrl_jac(sys)[] - if cache isa Tuple && cache[2] == (sparse, simplify) - return cache[1] - end - - rhs = [eq.rhs for eq in full_equations(sys)] - ctrls = unbound_inputs(sys) - - if sparse - jac = sparsejacobian(rhs, ctrls, simplify = simplify) - else - jac = jacobian(rhs, ctrls, simplify = simplify) - end - - get_ctrl_jac(sys)[] = jac, (sparse, simplify) # cache Jacobian - return jac -end - -function generate_tgrad( - sys::AbstractODESystem, dvs = unknowns(sys), ps = parameters( - sys; initial_parameters = true); - simplify = false, kwargs...) - tgrad = calculate_tgrad(sys, simplify = simplify) - p = reorder_parameters(sys, ps) - return build_function_wrapper(sys, tgrad, - dvs, - p..., - get_iv(sys); - kwargs...) -end - -function generate_jacobian(sys::AbstractODESystem, dvs = unknowns(sys), - ps = parameters(sys; initial_parameters = true); - simplify = false, sparse = false, kwargs...) - jac = calculate_jacobian(sys; simplify = simplify, sparse = sparse) - p = reorder_parameters(sys, ps) - return build_function_wrapper(sys, jac, - dvs, - p..., - get_iv(sys); - wrap_code = sparse ? assert_jac_length_header(sys) : (identity, identity), - kwargs...) -end - -function assert_jac_length_header(sys) - W = W_sparsity(sys) - identity, - function add_header(expr) - Func(expr.args, [], expr.body, - [:(@assert $(SymbolicUtils.Code.toexpr(term(findnz, expr.args[1])))[1:2] == - $(findnz(W)[1:2]))]) - end -end - -function generate_W(sys::AbstractODESystem, γ = 1.0, dvs = unknowns(sys), - ps = parameters(sys; initial_parameters = true); - simplify = false, sparse = false, kwargs...) - @variables ˍ₋gamma - M = calculate_massmatrix(sys; simplify) - sparse && (M = SparseArrays.sparse(M)) - J = calculate_jacobian(sys; simplify, sparse, dvs) - W = ˍ₋gamma * M + J - - p = reorder_parameters(sys, ps) - return build_function_wrapper(sys, W, - dvs, - p..., - ˍ₋gamma, - get_iv(sys); - wrap_code = sparse ? assert_jac_length_header(sys) : (identity, identity), - p_end = 1 + length(p), - kwargs...) -end - -function generate_control_jacobian(sys::AbstractODESystem, dvs = unknowns(sys), - ps = parameters(sys; initial_parameters = true); - simplify = false, sparse = false, kwargs...) - jac = calculate_control_jacobian(sys; simplify = simplify, sparse = sparse) - p = reorder_parameters(sys, ps) - return build_function_wrapper(sys, jac, dvs, p..., get_iv(sys); kwargs...) -end - -function generate_dae_jacobian(sys::AbstractODESystem, dvs = unknowns(sys), - ps = parameters(sys; initial_parameters = true); simplify = false, sparse = false, - kwargs...) - jac_u = calculate_jacobian(sys; simplify = simplify, sparse = sparse) - derivatives = Differential(get_iv(sys)).(unknowns(sys)) - jac_du = calculate_jacobian(sys; simplify = simplify, sparse = sparse, - dvs = derivatives) - dvs = unknowns(sys) - @variables ˍ₋gamma - jac = ˍ₋gamma * jac_du + jac_u - pre = get_preprocess_constants(jac) - p = reorder_parameters(sys, ps) - return build_function_wrapper(sys, jac, derivatives, dvs, p..., ˍ₋gamma, get_iv(sys); - p_start = 3, p_end = 2 + length(p), kwargs...) -end - -function generate_function(sys::AbstractODESystem, dvs = unknowns(sys), - ps = parameters(sys; initial_parameters = true); - implicit_dae = false, - ddvs = implicit_dae ? map(Differential(get_iv(sys)), dvs) : - nothing, - isdde = false, - kwargs...) - eqs = [eq for eq in equations(sys)] - if !implicit_dae - check_operator_variables(eqs, Differential) - check_lhs(eqs, Differential, Set(dvs)) - end - - rhss = implicit_dae ? [_iszero(eq.lhs) ? eq.rhs : eq.rhs - eq.lhs for eq in eqs] : - [eq.rhs for eq in eqs] - - if !isempty(assertions(sys)) - rhss[end] += unwrap(get_assertions_expr(sys)) - end - - # TODO: add an optional check on the ordering of observed equations - u = dvs - p = reorder_parameters(sys, ps) - t = get_iv(sys) - - if implicit_dae - build_function_wrapper(sys, rhss, ddvs, u, p..., t; p_start = 3, kwargs...) - else - build_function_wrapper(sys, rhss, u, p..., t; kwargs...) - end -end - -function isdelay(var, iv) - iv === nothing && return false - isvariable(var) || return false - isparameter(var) && return false - if iscall(var) && !ModelingToolkit.isoperator(var, Symbolics.Operator) - args = arguments(var) - length(args) == 1 || return false - isequal(args[1], iv) || return true - end - return false -end -const DDE_HISTORY_FUN = Sym{Symbolics.FnType{Tuple{Any, <:Real}, Vector{Real}}}(:___history___) -const DEFAULT_PARAMS_ARG = Sym{Any}(:ˍ₋arg3) -function delay_to_function( - sys::AbstractODESystem, eqs = full_equations(sys); history_arg = DEFAULT_PARAMS_ARG) - delay_to_function(eqs, - get_iv(sys), - Dict{Any, Int}(operation(s) => i for (i, s) in enumerate(unknowns(sys))), - parameters(sys), - DDE_HISTORY_FUN; history_arg) -end -function delay_to_function(eqs::Vector, iv, sts, ps, h; history_arg = DEFAULT_PARAMS_ARG) - delay_to_function.(eqs, (iv,), (sts,), (ps,), (h,); history_arg) -end -function delay_to_function(eq::Equation, iv, sts, ps, h; history_arg = DEFAULT_PARAMS_ARG) - delay_to_function(eq.lhs, iv, sts, ps, h; history_arg) ~ delay_to_function( - eq.rhs, iv, sts, ps, h; history_arg) -end -function delay_to_function(expr, iv, sts, ps, h; history_arg = DEFAULT_PARAMS_ARG) - if isdelay(expr, iv) - v = operation(expr) - time = arguments(expr)[1] - idx = sts[v] - return term(getindex, h(history_arg, time), idx, type = Real) # BIG BIG HACK - elseif iscall(expr) - return maketerm(typeof(expr), - operation(expr), - map(x -> delay_to_function(x, iv, sts, ps, h; history_arg), arguments(expr)), - metadata(expr)) - else - return expr - end -end - -function calculate_massmatrix(sys::AbstractODESystem; simplify = false) - eqs = [eq for eq in equations(sys)] - M = zeros(length(eqs), length(eqs)) - for (i, eq) in enumerate(eqs) - if iscall(eq.lhs) && operation(eq.lhs) isa Differential - st = var_from_nested_derivative(eq.lhs)[1] - j = variable_index(sys, st) - M[i, j] = 1 - else - _iszero(eq.lhs) || - error("Only semi-explicit constant mass matrices are currently supported. Faulty equation: $eq.") - end - end - M = simplify ? ModelingToolkit.simplify.(M) : M - # M should only contain concrete numbers - if isdiag(M) - M = Diagonal(M) - end - M == I ? I : M -end - -function jacobian_sparsity(sys::AbstractODESystem) - sparsity = torn_system_jacobian_sparsity(sys) - sparsity === nothing || return sparsity - - jacobian_sparsity([eq.rhs for eq in full_equations(sys)], - [dv for dv in unknowns(sys)]) -end - -function jacobian_dae_sparsity(sys::AbstractODESystem) - J1 = jacobian_sparsity([eq.rhs for eq in full_equations(sys)], - [dv for dv in unknowns(sys)]) - derivatives = Differential(get_iv(sys)).(unknowns(sys)) - J2 = jacobian_sparsity([eq.rhs for eq in full_equations(sys)], - [dv for dv in derivatives]) - J1 + J2 -end - -function W_sparsity(sys::AbstractODESystem) - jac_sparsity = jacobian_sparsity(sys) - (n, n) = size(jac_sparsity) - M = calculate_massmatrix(sys) - M_sparsity = M isa UniformScaling ? sparse(I(n)) : - SparseMatrixCSC{Bool, Int64}((!iszero).(M)) - jac_sparsity .| M_sparsity -end - -function isautonomous(sys::AbstractODESystem) - tgrad = calculate_tgrad(sys; simplify = true) - all(iszero, tgrad) -end - -""" -```julia -DiffEqBase.ODEFunction{iip}(sys::AbstractODESystem, dvs = unknowns(sys), - ps = parameters(sys); - version = nothing, tgrad = false, - jac = false, - sparse = false, - kwargs...) where {iip} -``` - -Create an `ODEFunction` from the [`ODESystem`](@ref). The arguments `dvs` and `ps` -are used to set the order of the dependent variable and parameter vectors, -respectively. -""" -function DiffEqBase.ODEFunction(sys::AbstractODESystem, args...; kwargs...) - ODEFunction{true}(sys, args...; kwargs...) -end - -function DiffEqBase.ODEFunction{true}(sys::AbstractODESystem, args...; - kwargs...) - ODEFunction{true, SciMLBase.AutoSpecialize}(sys, args...; kwargs...) -end - -function DiffEqBase.ODEFunction{false}(sys::AbstractODESystem, args...; - kwargs...) - ODEFunction{false, SciMLBase.FullSpecialize}(sys, args...; kwargs...) -end - -function DiffEqBase.ODEFunction{iip, specialize}(sys::AbstractODESystem, - dvs = unknowns(sys), - ps = parameters(sys), u0 = nothing; - version = nothing, tgrad = false, - jac = false, p = nothing, - t = nothing, - eval_expression = false, - sparse = false, simplify = false, - eval_module = @__MODULE__, - steady_state = false, - checkbounds = false, - sparsity = false, - analytic = nothing, - split_idxs = nothing, - initialization_data = nothing, - 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`") - end - f_gen = generate_function(sys, dvs, ps; expression = Val{true}, - expression_module = eval_module, checkbounds = checkbounds, cse, - kwargs...) - f_oop, f_iip = eval_or_rgf.(f_gen; eval_expression, eval_module) - f = GeneratedFunctionWrapper{(2, 3, is_split(sys))}(f_oop, f_iip) - - if specialize === SciMLBase.FunctionWrapperSpecialize && iip - if u0 === nothing || p === nothing || t === nothing - error("u0, p, and t must be specified for FunctionWrapperSpecialize on ODEFunction.") - end - f = SciMLBase.wrapfun_iip(f, (u0, u0, p, t)) - end - - if tgrad - tgrad_gen = generate_tgrad(sys, dvs, ps; - simplify = simplify, - expression = Val{true}, - expression_module = eval_module, cse, - checkbounds = checkbounds, kwargs...) - tgrad_oop, tgrad_iip = eval_or_rgf.(tgrad_gen; eval_expression, eval_module) - _tgrad = GeneratedFunctionWrapper{(2, 3, is_split(sys))}(tgrad_oop, tgrad_iip) - else - _tgrad = nothing - end - - if jac - jac_gen = generate_jacobian(sys, dvs, ps; - simplify = simplify, sparse = sparse, - expression = Val{true}, - expression_module = eval_module, cse, - checkbounds = checkbounds, kwargs...) - jac_oop, jac_iip = eval_or_rgf.(jac_gen; eval_expression, eval_module) - - _jac = GeneratedFunctionWrapper{(2, 3, is_split(sys))}(jac_oop, jac_iip) - else - _jac = nothing - end - - M = calculate_massmatrix(sys) - - _M = if sparse && !(u0 === nothing || M === I) - SparseArrays.sparse(M) - elseif u0 === nothing || M === I - M - elseif M isa Diagonal - Diagonal(ArrayInterface.restructure(u0, diag(M))) - else - ArrayInterface.restructure(u0 .* u0', M) - end - - observedfun = ObservedFunctionCache( - sys; steady_state, eval_expression, eval_module, checkbounds, cse) - - if sparse - uElType = u0 === nothing ? Float64 : eltype(u0) - W_prototype = similar(W_sparsity(sys), uElType) - else - W_prototype = nothing - end - - @set! sys.split_idxs = split_idxs - - ODEFunction{iip, specialize}(f; - sys = sys, - jac = _jac === nothing ? nothing : _jac, - tgrad = _tgrad === nothing ? nothing : _tgrad, - mass_matrix = _M, - jac_prototype = W_prototype, - observed = observedfun, - sparsity = sparsity ? W_sparsity(sys) : nothing, - analytic = analytic, - initialization_data) -end - -""" -```julia -DiffEqBase.DAEFunction{iip}(sys::AbstractODESystem, dvs = unknowns(sys), - ps = parameters(sys); - version = nothing, tgrad = false, - jac = false, - sparse = false, - kwargs...) where {iip} -``` - -Create an `DAEFunction` from the [`ODESystem`](@ref). The arguments `dvs` and -`ps` are used to set the order of the dependent variable and parameter vectors, -respectively. -""" -function DiffEqBase.DAEFunction(sys::AbstractODESystem, args...; kwargs...) - DAEFunction{true}(sys, args...; kwargs...) -end - -function DiffEqBase.DAEFunction{iip}(sys::AbstractODESystem, dvs = unknowns(sys), - ps = parameters(sys), u0 = nothing; - ddvs = map(Base.Fix2(diff2term, get_iv(sys)) ∘ Differential(get_iv(sys)), dvs), - version = nothing, p = nothing, - jac = false, - eval_expression = false, - sparse = false, simplify = false, - eval_module = @__MODULE__, - checkbounds = false, - initialization_data = nothing, - 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`") - end - f_gen = generate_function(sys, dvs, ps; implicit_dae = true, - expression = Val{true}, cse, - expression_module = eval_module, checkbounds = checkbounds, - kwargs...) - f_oop, f_iip = eval_or_rgf.(f_gen; eval_expression, eval_module) - f = GeneratedFunctionWrapper{(3, 4, is_split(sys))}(f_oop, f_iip) - - if jac - jac_gen = generate_dae_jacobian(sys, dvs, ps; - simplify = simplify, sparse = sparse, - expression = Val{true}, - expression_module = eval_module, cse, - checkbounds = checkbounds, kwargs...) - jac_oop, jac_iip = eval_or_rgf.(jac_gen; eval_expression, eval_module) - - _jac = GeneratedFunctionWrapper{(3, 5, is_split(sys))}(jac_oop, jac_iip) - else - _jac = nothing - end - - observedfun = ObservedFunctionCache( - sys; eval_expression, eval_module, checkbounds = get(kwargs, :checkbounds, false), cse) - - jac_prototype = if sparse - uElType = u0 === nothing ? Float64 : eltype(u0) - if jac - J1 = calculate_jacobian(sys, sparse = sparse) - derivatives = Differential(get_iv(sys)).(unknowns(sys)) - J2 = calculate_jacobian(sys; sparse = sparse, dvs = derivatives) - similar(J1 + J2, uElType) - else - similar(jacobian_dae_sparsity(sys), uElType) - end - else - nothing - end - - DAEFunction{iip}(f; - sys = sys, - jac = _jac === nothing ? nothing : _jac, - jac_prototype = jac_prototype, - observed = observedfun, - initialization_data) -end - -function DiffEqBase.DDEFunction(sys::AbstractODESystem, args...; kwargs...) - DDEFunction{true}(sys, args...; kwargs...) -end - -function DiffEqBase.DDEFunction{iip}(sys::AbstractODESystem, dvs = unknowns(sys), - ps = parameters(sys), u0 = nothing; - eval_expression = false, - eval_module = @__MODULE__, - checkbounds = false, - initialization_data = nothing, - 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`") - end - f_gen = generate_function(sys, dvs, ps; isdde = true, - expression = Val{true}, - expression_module = eval_module, checkbounds = checkbounds, - cse, kwargs...) - f_oop, f_iip = eval_or_rgf.(f_gen; eval_expression, eval_module) - f = GeneratedFunctionWrapper{(3, 4, is_split(sys))}(f_oop, f_iip) - - DDEFunction{iip}(f; sys = sys, initialization_data) -end - -function DiffEqBase.SDDEFunction(sys::AbstractODESystem, args...; kwargs...) - SDDEFunction{true}(sys, args...; kwargs...) -end - -function DiffEqBase.SDDEFunction{iip}(sys::AbstractODESystem, dvs = unknowns(sys), - ps = parameters(sys), u0 = nothing; - eval_expression = false, - eval_module = @__MODULE__, - checkbounds = false, - initialization_data = nothing, - 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`") - end - f_gen = generate_function(sys, dvs, ps; isdde = true, - expression = Val{true}, - expression_module = eval_module, checkbounds = checkbounds, - cse, kwargs...) - f_oop, f_iip = eval_or_rgf.(f_gen; eval_expression, eval_module) - f = GeneratedFunctionWrapper{(3, 4, is_split(sys))}(f_oop, f_iip) - - g_gen = generate_diffusion_function(sys, dvs, ps; expression = Val{true}, - isdde = true, cse, kwargs...) - g_oop, g_iip = eval_or_rgf.(g_gen; eval_expression, eval_module) - g = GeneratedFunctionWrapper{(3, 4, is_split(sys))}(g_oop, g_iip) - - SDDEFunction{iip}(f, g; sys = sys, initialization_data) -end - -""" -```julia -ODEFunctionExpr{iip}(sys::AbstractODESystem, dvs = unknowns(sys), - ps = parameters(sys); - version = nothing, tgrad = false, - jac = false, - sparse = false, - kwargs...) where {iip} -``` - -Create a Julia expression for an `ODEFunction` from the [`ODESystem`](@ref). -The arguments `dvs` and `ps` are used to set the order of the dependent -variable and parameter vectors, respectively. -""" -struct ODEFunctionExpr{iip, specialize} end - -function ODEFunctionExpr{iip, specialize}(sys::AbstractODESystem, dvs = unknowns(sys), - ps = parameters(sys), u0 = nothing; - version = nothing, tgrad = false, - jac = false, p = nothing, - linenumbers = false, - sparse = false, simplify = false, - steady_state = false, - sparsity = false, - 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`") - end - f_oop, f_iip = generate_function(sys, dvs, ps; expression = Val{true}, kwargs...) - - fsym = gensym(:f) - _f = :($fsym = $(GeneratedFunctionWrapper{(2, 3, is_split(sys))})($f_oop, $f_iip)) - tgradsym = gensym(:tgrad) - if tgrad - tgrad_oop, tgrad_iip = generate_tgrad(sys, dvs, ps; - simplify = simplify, - expression = Val{true}, kwargs...) - _tgrad = :($tgradsym = $(GeneratedFunctionWrapper{(2, 3, is_split(sys))})( - $tgrad_oop, $tgrad_iip)) - else - _tgrad = :($tgradsym = nothing) - end - - jacsym = gensym(:jac) - if jac - jac_oop, jac_iip = generate_jacobian(sys, dvs, ps; - sparse = sparse, simplify = simplify, - expression = Val{true}, kwargs...) - _jac = :($jacsym = $(GeneratedFunctionWrapper{(2, 3, is_split(sys))})( - $jac_oop, $jac_iip)) - else - _jac = :($jacsym = nothing) - end - - Msym = gensym(:M) - M = calculate_massmatrix(sys) - if sparse && !(u0 === nothing || M === I) - _M = :($Msym = $(SparseArrays.sparse(M))) - elseif u0 === nothing || M === I - _M = :($Msym = $M) - else - _M = :($Msym = $(ArrayInterface.restructure(u0 .* u0', M))) - end - - jp_expr = sparse ? :($similar($(get_jac(sys)[]), Float64)) : :nothing - ex = quote - let $_f, $_tgrad, $_jac, $_M - ODEFunction{$iip, $specialize}($fsym, - sys = $sys, - jac = $jacsym, - tgrad = $tgradsym, - mass_matrix = $Msym, - jac_prototype = $jp_expr, - sparsity = $(sparsity ? jacobian_sparsity(sys) : nothing), - observed = $observedfun_exp) - end - end - !linenumbers ? Base.remove_linenums!(ex) : ex -end - -function ODEFunctionExpr(sys::AbstractODESystem, args...; kwargs...) - ODEFunctionExpr{true}(sys, args...; kwargs...) -end - -function ODEFunctionExpr{true}(sys::AbstractODESystem, args...; kwargs...) - return ODEFunctionExpr{true, SciMLBase.AutoSpecialize}(sys, args...; kwargs...) -end - -function ODEFunctionExpr{false}(sys::AbstractODESystem, args...; kwargs...) - return ODEFunctionExpr{false, SciMLBase.FullSpecialize}(sys, args...; kwargs...) -end - -""" -```julia -DAEFunctionExpr{iip}(sys::AbstractODESystem, dvs = unknowns(sys), - ps = parameters(sys); - version = nothing, tgrad = false, - jac = false, - sparse = false, - kwargs...) where {iip} -``` - -Create a Julia expression for an `ODEFunction` from the [`ODESystem`](@ref). -The arguments `dvs` and `ps` are used to set the order of the dependent -variable and parameter vectors, respectively. -""" -struct DAEFunctionExpr{iip} end - -function DAEFunctionExpr{iip}(sys::AbstractODESystem, dvs = unknowns(sys), - ps = parameters(sys), u0 = nothing; - version = nothing, tgrad = false, - jac = false, p = nothing, - linenumbers = false, - 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`") - end - f_oop, f_iip = generate_function(sys, dvs, ps; expression = Val{true}, - implicit_dae = true, kwargs...) - fsym = gensym(:f) - _f = :($fsym = $(GeneratedFunctionWrapper{(3, 4, is_split(sys))})($f_oop, $f_iip)) - ex = quote - $_f - ODEFunction{$iip}($fsym) - end - !linenumbers ? Base.remove_linenums!(ex) : ex -end - -function DAEFunctionExpr(sys::AbstractODESystem, args...; kwargs...) - DAEFunctionExpr{true}(sys, args...; kwargs...) -end - -struct SymbolicTstops{F} - fn::F -end - -function (st::SymbolicTstops)(p, tspan) - unique!(sort!(reduce(vcat, st.fn(p, tspan...)))) -end - -function SymbolicTstops( - sys::AbstractSystem; eval_expression = false, eval_module = @__MODULE__) - tstops = symbolic_tstops(sys) - isempty(tstops) && return nothing - t0 = gensym(:t0) - t1 = gensym(:t1) - tstops = map(tstops) do val - if is_array_of_symbolics(val) || val isa AbstractArray - collect(val) - else - term(:, t0, unwrap(val), t1; type = AbstractArray{Real}) - end - end - rps = reorder_parameters(sys) - tstops, _ = build_function_wrapper(sys, tstops, - rps..., - t0, - t1; - expression = Val{true}, - p_start = 1, p_end = length(rps), add_observed = false, force_SA = true) - tstops = eval_or_rgf(tstops; eval_expression, eval_module) - tstops = GeneratedFunctionWrapper{(1, 3, is_split(sys))}(tstops, nothing) - return SymbolicTstops(tstops) -end - -""" -```julia -DiffEqBase.ODEProblem{iip}(sys::AbstractODESystem, u0map, tspan, - parammap = DiffEqBase.NullParameters(); - allow_cost = false, - version = nothing, tgrad = false, - jac = false, - checkbounds = false, sparse = false, - simplify = false, - linenumbers = true, parallel = SerialForm(), - kwargs...) where {iip} -``` - -Generates an ODEProblem from an ODESystem and allows for automatically -symbolically calculating numerical enhancements. -""" -function DiffEqBase.ODEProblem(sys::AbstractODESystem, args...; kwargs...) - ODEProblem{true}(sys, args...; kwargs...) -end - -function DiffEqBase.ODEProblem(sys::AbstractODESystem, - u0map::StaticArray, - args...; - kwargs...) - ODEProblem{false, SciMLBase.FullSpecialize}(sys, u0map, args...; kwargs...) -end - -function DiffEqBase.ODEProblem{true}(sys::AbstractODESystem, args...; kwargs...) - ODEProblem{true, SciMLBase.AutoSpecialize}(sys, args...; kwargs...) -end - -function DiffEqBase.ODEProblem{false}(sys::AbstractODESystem, args...; kwargs...) - ODEProblem{false, SciMLBase.FullSpecialize}(sys, args...; kwargs...) -end - -function DiffEqBase.ODEProblem{iip, specialize}(sys::AbstractODESystem, u0map = [], - tspan = get_tspan(sys), - parammap = DiffEqBase.NullParameters(); - allow_cost = false, - callback = nothing, - check_length = true, - warn_initialize_determined = true, - eval_expression = false, - 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`") - end - - if !isnothing(get_constraintsystem(sys)) - error("An ODESystem with constraints cannot be used to construct a regular ODEProblem. - Consider a BVProblem instead.") - end - - if !isempty(get_costs(sys)) && !allow_cost - error("ODEProblem will not optimize solutions of ODESystems that have associated cost functions. - Solvers for optimal control problems are forthcoming. In order to bypass this error (e.g. - to check the cost of a regular solution), pass `allow_cost` = true into the constructor.") - end - - f, u0, p = process_SciMLProblem(ODEFunction{iip, specialize}, sys, u0map, parammap; - t = tspan !== nothing ? tspan[1] : tspan, - check_length, warn_initialize_determined, eval_expression, eval_module, kwargs...) - cbs = process_events(sys; callback, eval_expression, eval_module, kwargs...) - - kwargs = filter_kwargs(kwargs) - pt = something(get_metadata(sys), StandardODEProblem()) - - kwargs1 = (;) - if cbs !== nothing - kwargs1 = merge(kwargs1, (callback = cbs,)) - end - - tstops = SymbolicTstops(sys; eval_expression, eval_module) - if tstops !== nothing - kwargs1 = merge(kwargs1, (; tstops)) - end - - # Call `remake` so it runs initialization if it is trivial - return remake(ODEProblem{iip}(f, u0, tspan, p, pt; kwargs1..., kwargs...)) -end -get_callback(prob::ODEProblem) = prob.kwargs[:callback] - -""" -```julia -SciMLBase.BVProblem{iip}(sys::AbstractODESystem, u0map, tspan, - parammap = DiffEqBase.NullParameters(); - constraints = nothing, guesses = nothing, - version = nothing, tgrad = false, - jac = true, sparse = true, - simplify = false, - kwargs...) where {iip} -``` - -Create a boundary value problem from the [`ODESystem`](@ref). - -`u0map` is used to specify fixed initial values for the states. Every variable -must have either an initial guess supplied using `guesses` or a fixed initial -value specified using `u0map`. - -Boundary value conditions are supplied to ODESystems -in the form of a ConstraintsSystem. These equations -should specify values that state variables should -take at specific points, as in `x(0.5) ~ 1`). More general constraints that -should hold over the entire solution, such as `x(t)^2 + y(t)^2`, should be -specified as one of the equations used to build the `ODESystem`. - -If an ODESystem without `constraints` is specified, it will be treated as an initial value problem. - -```julia - @parameters g t_c = 0.5 - @variables x(..) y(t) λ(t) - eqs = [D(D(x(t))) ~ λ * x(t) - D(D(y)) ~ λ * y - g - x(t)^2 + y^2 ~ 1] - cstr = [x(0.5) ~ 1] - @mtkbuild pend = ODESystem(eqs, t; constraints = cstrs) - - tspan = (0.0, 1.5) - u0map = [x(t) => 0.6, y => 0.8] - parammap = [g => 1] - guesses = [λ => 1] - - bvp = SciMLBase.BVProblem{true, SciMLBase.AutoSpecialize}(pend, u0map, tspan, parammap; guesses, check_length = false) -``` - -If the `ODESystem` has algebraic equations, like `x(t)^2 + y(t)^2`, the resulting -`BVProblem` must be solved using BVDAE solvers, such as Ascher. -""" -function SciMLBase.BVProblem(sys::AbstractODESystem, args...; kwargs...) - BVProblem{true}(sys, args...; kwargs...) -end - -function SciMLBase.BVProblem(sys::AbstractODESystem, - u0map::StaticArray, - args...; - kwargs...) - BVProblem{false, SciMLBase.FullSpecialize}(sys, u0map, args...; kwargs...) -end - -function SciMLBase.BVProblem{true}(sys::AbstractODESystem, args...; kwargs...) - BVProblem{true, SciMLBase.AutoSpecialize}(sys, args...; kwargs...) -end - -function SciMLBase.BVProblem{false}(sys::AbstractODESystem, args...; kwargs...) - BVProblem{false, SciMLBase.FullSpecialize}(sys, args...; kwargs...) -end - -function SciMLBase.BVProblem{iip, specialize}(sys::AbstractODESystem, u0map = [], - tspan = get_tspan(sys), - parammap = DiffEqBase.NullParameters(); - guesses = Dict(), - allow_cost = false, - version = nothing, tgrad = false, - callback = nothing, - check_length = true, - warn_initialize_determined = true, - eval_expression = false, - eval_module = @__MODULE__, - 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`") - end - !isnothing(callback) && error("BVP solvers do not support callbacks.") - - if !isempty(get_costs(sys)) && !allow_cost - error("BVProblem will not optimize solutions of ODESystems that have associated cost functions. - Solvers for optimal control problems are forthcoming. In order to bypass this error (e.g. - to check the cost of a regular solution), pass `allow_cost` = true into the constructor.") - end - - has_alg_eqs(sys) && - error("The BVProblem constructor currently does not support ODESystems with algebraic equations.") # Remove this when the BVDAE solvers get updated, the codegen should work when it does. - - sts = unknowns(sys) - ps = parameters(sys) - constraintsys = get_constraintsystem(sys) - - if !isnothing(constraintsys) - (length(constraints(constraintsys)) + length(u0map) > length(sts)) && - @warn "The BVProblem is overdetermined. The total number of conditions (# constraints + # fixed initial values given by u0map) exceeds the total number of states. The BVP solvers will default to doing a nonlinear least-squares optimization." - end - - # ODESystems without algebraic equations should use both fixed values + guesses - # for initialization. - _u0map = has_alg_eqs(sys) ? u0map : merge(Dict(u0map), Dict(guesses)) - f, u0, p = process_SciMLProblem(ODEFunction{iip, specialize}, sys, _u0map, parammap; - t = tspan !== nothing ? tspan[1] : tspan, guesses, - check_length, warn_initialize_determined, eval_expression, eval_module, cse, kwargs...) - - stidxmap = Dict([v => i for (i, v) in enumerate(sts)]) - u0_idxs = has_alg_eqs(sys) ? collect(1:length(sts)) : [stidxmap[k] for (k, v) in u0map] - - fns = generate_function_bc(sys, u0, u0_idxs, tspan; cse) - bc_oop, bc_iip = eval_or_rgf.(fns; eval_expression, eval_module) - bc(sol, p, t) = bc_oop(sol, p, t) - bc(resid, u, p, t) = bc_iip(resid, u, p, t) - - return BVProblem{iip}(f, bc, u0, tspan, p; kwargs...) -end - -get_callback(prob::BVProblem) = error("BVP solvers do not support callbacks.") - -""" - generate_function_bc(sys::ODESystem, u0, u0_idxs, tspan) - - Given an ODESystem with constraints, generate the boundary condition function to pass to boundary value problem solvers. - Expression uses the constraints and the provided initial conditions. -""" -function generate_function_bc(sys::ODESystem, u0, u0_idxs, tspan; kwargs...) - iv = get_iv(sys) - sts = unknowns(sys) - ps = parameters(sys) - np = length(ps) - ns = length(sts) - stidxmap = Dict([v => i for (i, v) in enumerate(sts)]) - pidxmap = Dict([v => i for (i, v) in enumerate(ps)]) - - @variables sol(..)[1:ns] - - conssys = get_constraintsystem(sys) - cons = Any[] - if !isnothing(conssys) - cons = [con.lhs - con.rhs for con in constraints(conssys)] - - for st in get_unknowns(conssys) - x = operation(st) - t = only(arguments(st)) - idx = stidxmap[x(iv)] - - cons = map(c -> Symbolics.substitute(c, Dict(x(t) => sol(t)[idx])), cons) - end - end - - init_conds = Any[] - for i in u0_idxs - expr = sol(tspan[1])[i] - u0[i] - push!(init_conds, expr) - end - - exprs = vcat(init_conds, cons) - _p = reorder_parameters(sys, ps) - - build_function_wrapper(sys, exprs, sol, _p..., iv; output_type = Array, kwargs...) -end - -""" -```julia -DiffEqBase.DAEProblem{iip}(sys::AbstractODESystem, du0map, u0map, tspan, - parammap = DiffEqBase.NullParameters(); - version = nothing, tgrad = false, - jac = false, - checkbounds = false, sparse = false, - simplify = false, - linenumbers = true, parallel = SerialForm(), - kwargs...) where {iip} -``` - -Generates a DAEProblem from an ODESystem and allows for automatically -symbolically calculating numerical enhancements. - -Note: Solvers for DAEProblems like DFBDF, DImplicitEuler, DABDF2 are -generally slower than the ones for ODEProblems. We recommend trying -ODEProblem and its solvers for your problem first. -""" -function DiffEqBase.DAEProblem(sys::AbstractODESystem, args...; kwargs...) - DAEProblem{true}(sys, args...; kwargs...) -end - -function DiffEqBase.DAEProblem{iip}(sys::AbstractODESystem, du0map, u0map, tspan, - parammap = DiffEqBase.NullParameters(); - allow_cost = false, - 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`.") - end - - if !isempty(get_costs(sys)) && !allow_cost - error("DAEProblem will not optimize solutions of ODESystems that have associated cost functions. - Solvers for optimal control problems are forthcoming. In order to bypass this error (e.g. - to check the cost of a regular solution), pass `allow_cost` = true into the constructor.") - end - - f, du0, u0, p = process_SciMLProblem(DAEFunction{iip}, sys, u0map, parammap; - implicit_dae = true, du0map = du0map, check_length, - t = tspan !== nothing ? tspan[1] : tspan, - warn_initialize_determined, kwargs...) - diffvars = collect_differential_variables(sys) - sts = unknowns(sys) - differential_vars = map(Base.Fix2(in, diffvars), sts) - kwargs = filter_kwargs(kwargs) - - kwargs1 = (;) - - tstops = SymbolicTstops(sys; eval_expression, eval_module) - if tstops !== nothing - kwargs1 = merge(kwargs1, (; tstops)) - end - - # Call `remake` so it runs initialization if it is trivial - return remake(DAEProblem{iip}( - f, du0, u0, tspan, p; differential_vars = differential_vars, - kwargs..., kwargs1...)) -end - -function generate_history(sys::AbstractODESystem, u0; expression = Val{false}, kwargs...) - p = reorder_parameters(sys) - build_function_wrapper( - sys, u0, p..., get_iv(sys); expression, p_start = 1, p_end = length(p), - similarto = typeof(u0), wrap_delays = false, kwargs...) -end - -function DiffEqBase.DDEProblem(sys::AbstractODESystem, args...; kwargs...) - DDEProblem{true}(sys, args...; kwargs...) -end -function DiffEqBase.DDEProblem{iip}(sys::AbstractODESystem, u0map = [], - tspan = get_tspan(sys), - parammap = DiffEqBase.NullParameters(); - callback = nothing, - check_length = true, - eval_expression = false, - eval_module = @__MODULE__, - u0_constructor = identity, - 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`") - end - f, u0, p = process_SciMLProblem(DDEFunction{iip}, sys, u0map, parammap; - t = tspan !== nothing ? tspan[1] : tspan, - symbolic_u0 = true, u0_constructor, cse, - check_length, eval_expression, eval_module, kwargs...) - h_gen = generate_history(sys, u0; expression = Val{true}, cse) - h_oop, h_iip = eval_or_rgf.(h_gen; eval_expression, eval_module) - h = h_oop - u0 = float.(h(p, tspan[1])) - if u0 !== nothing - u0 = u0_constructor(u0) - end - - cbs = process_events(sys; callback, eval_expression, eval_module, kwargs...) - kwargs = filter_kwargs(kwargs) - - kwargs1 = (;) - if cbs !== nothing - kwargs1 = merge(kwargs1, (callback = cbs,)) - end - # Call `remake` so it runs initialization if it is trivial - return remake(DDEProblem{iip}(f, u0, h, tspan, p; kwargs1..., kwargs...)) -end - -function DiffEqBase.SDDEProblem(sys::AbstractODESystem, args...; kwargs...) - SDDEProblem{true}(sys, args...; kwargs...) -end -function DiffEqBase.SDDEProblem{iip}(sys::AbstractODESystem, u0map = [], - tspan = get_tspan(sys), - parammap = DiffEqBase.NullParameters(); - callback = nothing, - check_length = true, - sparsenoise = nothing, - eval_expression = false, - eval_module = @__MODULE__, - u0_constructor = identity, - 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`") - end - f, u0, p = process_SciMLProblem(SDDEFunction{iip}, sys, u0map, parammap; - t = tspan !== nothing ? tspan[1] : tspan, - symbolic_u0 = true, eval_expression, eval_module, u0_constructor, - check_length, cse, kwargs...) - h_gen = generate_history(sys, u0; expression = Val{true}, cse) - h_oop, h_iip = eval_or_rgf.(h_gen; eval_expression, eval_module) - h = h_oop - u0 = h(p, tspan[1]) - if u0 !== nothing - u0 = u0_constructor(u0) - end - - cbs = process_events(sys; callback, eval_expression, eval_module, kwargs...) - kwargs = filter_kwargs(kwargs) - - kwargs1 = (;) - if cbs !== nothing - kwargs1 = merge(kwargs1, (callback = cbs,)) - end - - noiseeqs = get_noiseeqs(sys) - sparsenoise === nothing && (sparsenoise = get(kwargs, :sparse, false)) - if noiseeqs isa AbstractVector - noise_rate_prototype = nothing - elseif sparsenoise - I, J, V = findnz(SparseArrays.sparse(noiseeqs)) - noise_rate_prototype = SparseArrays.sparse(I, J, zero(eltype(u0))) - else - noise_rate_prototype = zeros(eltype(u0), size(noiseeqs)) - end - # Call `remake` so it runs initialization if it is trivial - return remake(SDDEProblem{iip}(f, f.g, u0, h, tspan, p; - noise_rate_prototype = - noise_rate_prototype, kwargs1..., kwargs...)) -end - -""" -```julia -ODEProblemExpr{iip}(sys::AbstractODESystem, u0map, tspan, - parammap = DiffEqBase.NullParameters(); - version = nothing, tgrad = false, - jac = false, - checkbounds = false, sparse = false, - linenumbers = true, parallel = SerialForm(), - skipzeros = true, fillzeros = true, - simplify = false, - kwargs...) where {iip} -``` - -Generates a Julia expression for constructing an ODEProblem from an -ODESystem and allows for automatically symbolically calculating -numerical enhancements. -""" -struct ODEProblemExpr{iip} end - -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`") - end - f, u0, p = process_SciMLProblem( - ODEFunctionExpr{iip}, sys, u0map, parammap; check_length, - t = tspan !== nothing ? tspan[1] : tspan, - kwargs...) - linenumbers = get(kwargs, :linenumbers, true) - kwargs = filter_kwargs(kwargs) - kwarg_params = gen_quoted_kwargs(kwargs) - odep = Expr(:call, :ODEProblem, kwarg_params, :f, :u0, :tspan, :p) - ex = quote - f = $f - u0 = $u0 - tspan = $tspan - p = $p - $odep - end - !linenumbers ? Base.remove_linenums!(ex) : ex -end - -function ODEProblemExpr(sys::AbstractODESystem, args...; kwargs...) - ODEProblemExpr{true}(sys, args...; kwargs...) -end - -""" -```julia -DAEProblemExpr{iip}(sys::AbstractODESystem, u0map, tspan, - parammap = DiffEqBase.NullParameters(); - version = nothing, tgrad = false, - jac = false, - checkbounds = false, sparse = false, - linenumbers = true, parallel = SerialForm(), - skipzeros = true, fillzeros = true, - simplify = false, - kwargs...) where {iip} -``` - -Generates a Julia expression for constructing a DAEProblem from an -ODESystem and allows for automatically symbolically calculating -numerical enhancements. -""" -struct DAEProblemExpr{iip} end - -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`") - end - f, du0, u0, p = process_SciMLProblem(DAEFunctionExpr{iip}, sys, u0map, parammap; - t = tspan !== nothing ? tspan[1] : tspan, - implicit_dae = true, du0map = du0map, check_length, - kwargs...) - linenumbers = get(kwargs, :linenumbers, true) - diffvars = collect_differential_variables(sys) - sts = unknowns(sys) - differential_vars = map(Base.Fix2(in, diffvars), sts) - kwargs = filter_kwargs(kwargs) - kwarg_params = gen_quoted_kwargs(kwargs) - push!(kwarg_params.args, Expr(:kw, :differential_vars, :differential_vars)) - prob = Expr(:call, :(DAEProblem{$iip}), kwarg_params, :f, :du0, :u0, :tspan, :p) - ex = quote - f = $f - u0 = $u0 - du0 = $du0 - tspan = $tspan - p = $p - differential_vars = $differential_vars - $prob - end - !linenumbers ? Base.remove_linenums!(ex) : ex -end - -function DAEProblemExpr(sys::AbstractODESystem, args...; kwargs...) - DAEProblemExpr{true}(sys, args...; kwargs...) -end - -""" -```julia -SciMLBase.SteadyStateProblem(sys::AbstractODESystem, u0map, - parammap = DiffEqBase.NullParameters(); - version = nothing, tgrad = false, - jac = false, - checkbounds = false, sparse = false, - linenumbers = true, parallel = SerialForm(), - kwargs...) where {iip} -``` - -Generates an SteadyStateProblem from an ODESystem and allows for automatically -symbolically calculating numerical enhancements. -""" -function SciMLBase.SteadyStateProblem(sys::AbstractODESystem, args...; kwargs...) - SteadyStateProblem{true}(sys, args...; kwargs...) -end - -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`") - end - f, u0, p = process_SciMLProblem(ODEFunction{iip}, sys, u0map, parammap; - steady_state = true, - check_length, force_initialization_time_independent = true, kwargs...) - kwargs = filter_kwargs(kwargs) - SteadyStateProblem{iip}(f, u0, p; kwargs...) -end - -""" -```julia -SciMLBase.SteadyStateProblemExpr(sys::AbstractODESystem, u0map, - parammap = DiffEqBase.NullParameters(); - version = nothing, tgrad = false, - jac = false, - checkbounds = false, sparse = false, - skipzeros = true, fillzeros = true, - linenumbers = true, parallel = SerialForm(), - kwargs...) where {iip} -``` - -Generates a Julia expression for building a SteadyStateProblem from -an ODESystem and allows for automatically symbolically calculating -numerical enhancements. -""" -struct SteadyStateProblemExpr{iip} end - -function SteadyStateProblemExpr{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 `SteadyStateProblemExpr`") - end - f, u0, p = process_SciMLProblem(ODEFunctionExpr{iip}, sys, u0map, parammap; - steady_state = true, - check_length, kwargs...) - linenumbers = get(kwargs, :linenumbers, true) - kwargs = filter_kwargs(kwargs) - kwarg_params = gen_quoted_kwargs(kwargs) - prob = Expr(:call, :SteadyStateProblem, kwarg_params, :f, :u0, :p) - ex = quote - f = $f - u0 = $u0 - p = $p - $prob - end - !linenumbers ? Base.remove_linenums!(ex) : ex -end - -function SteadyStateProblemExpr(sys::AbstractODESystem, args...; kwargs...) - SteadyStateProblemExpr{true}(sys, args...; kwargs...) -end - -function _match_eqs(eqs1, eqs2) - eqpairs = Pair[] - for (i, eq) in enumerate(eqs1) - for (j, eq2) in enumerate(eqs2) - if isequal(eq, eq2) - push!(eqpairs, i => j) - break - end - end - end - eqpairs -end - -function isisomorphic(sys1::AbstractODESystem, sys2::AbstractODESystem) - sys1 = flatten(sys1) - sys2 = flatten(sys2) - - iv2 = only(independent_variables(sys2)) - sys1 = convert_system(ODESystem, sys1, iv2) - s1, s2 = unknowns(sys1), unknowns(sys2) - p1, p2 = parameters(sys1), parameters(sys2) - - (length(s1) != length(s2)) || (length(p1) != length(p2)) && return false - - eqs1 = equations(sys1) - eqs2 = equations(sys2) - - pps = permutations(p2) - psts = permutations(s2) - orig = [p1; s1] - perms = ([x; y] for x in pps for y in psts) - - for perm in perms - rules = Dict(orig .=> perm) - neweqs1 = substitute(eqs1, rules) - eqpairs = _match_eqs(neweqs1, eqs2) - if length(eqpairs) == length(eqs1) - return true - end - end - return false -end - -function flatten_equations(eqs) - mapreduce(vcat, eqs; init = Equation[]) do eq - islhsarr = eq.lhs isa AbstractArray || Symbolics.isarraysymbolic(eq.lhs) - isrhsarr = eq.rhs isa AbstractArray || Symbolics.isarraysymbolic(eq.rhs) - if islhsarr || isrhsarr - islhsarr && isrhsarr || - error("LHS ($(eq.lhs)) and RHS ($(eq.rhs)) must either both be array expressions or both scalar") - size(eq.lhs) == size(eq.rhs) || - error("Size of LHS ($(eq.lhs)) and RHS ($(eq.rhs)) must match: got $(size(eq.lhs)) and $(size(eq.rhs))") - return vec(collect(eq.lhs) .~ collect(eq.rhs)) - else - eq - end - end -end - -struct InitializationProblem{iip, specialization} end - -""" -```julia -InitializationProblem{iip}(sys::AbstractODESystem, t, u0map, - parammap = DiffEqBase.NullParameters(); - version = nothing, tgrad = false, - jac = false, - checkbounds = false, sparse = false, - simplify = false, - linenumbers = true, parallel = SerialForm(), - initialization_eqs = [], - fully_determined = false, - kwargs...) where {iip} -``` - -Generates a NonlinearProblem or NonlinearLeastSquaresProblem from an ODESystem -which represents the initialization, i.e. the calculation of the consistent -initial conditions for the given DAE. -""" -function InitializationProblem(sys::AbstractSystem, args...; kwargs...) - InitializationProblem{true}(sys, args...; kwargs...) -end - -function InitializationProblem(sys::AbstractSystem, t, - u0map::StaticArray, - args...; - kwargs...) - InitializationProblem{false, SciMLBase.FullSpecialize}( - sys, t, u0map, args...; kwargs...) -end - -function InitializationProblem{true}(sys::AbstractSystem, args...; kwargs...) - InitializationProblem{true, SciMLBase.AutoSpecialize}(sys, args...; kwargs...) -end - -function InitializationProblem{false}(sys::AbstractSystem, args...; kwargs...) - InitializationProblem{false, SciMLBase.FullSpecialize}(sys, args...; kwargs...) -end - -const INCOMPLETE_INITIALIZATION_MESSAGE = """ - Initialization incomplete. Not all of the state variables of the - DAE system can be determined by the initialization. Missing - variables: - """ - -struct IncompleteInitializationError <: Exception - uninit::Any -end - -function Base.showerror(io::IO, e::IncompleteInitializationError) - println(io, INCOMPLETE_INITIALIZATION_MESSAGE) - println(io, e.uninit) -end - -function InitializationProblem{iip, specialize}(sys::AbstractSystem, - t, u0map = [], - parammap = DiffEqBase.NullParameters(); - guesses = [], - check_length = true, - warn_initialize_determined = true, - initialization_eqs = [], - fully_determined = nothing, - check_units = true, - use_scc = true, - allow_incomplete = false, - force_time_independent = false, - 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`") - end - if isempty(u0map) && get_initializesystem(sys) !== nothing - isys = get_initializesystem(sys; initialization_eqs, check_units) - simplify_system = false - elseif isempty(u0map) && get_initializesystem(sys) === nothing - isys = generate_initializesystem( - sys; initialization_eqs, check_units, pmap = parammap, - guesses, algebraic_only) - simplify_system = true - else - isys = generate_initializesystem( - sys; u0map, initialization_eqs, check_units, - pmap = parammap, guesses, algebraic_only) - simplify_system = true - end - - # useful for `SteadyStateProblem` since `f` has to be autonomous and the - # initialization should be too - if force_time_independent - idx = findfirst(isequal(get_iv(sys)), get_ps(isys)) - idx === nothing || deleteat!(get_ps(isys), idx) - end - - if simplify_system - isys = structural_simplify(isys; fully_determined, split = is_split(sys)) - end - - ts = get_tearing_state(isys) - unassigned_vars = StructuralTransformations.singular_check(ts) - if warn_initialize_determined && !isempty(unassigned_vars) - errmsg = """ - The initialization system is structurally singular. Guess values may \ - significantly affect the initial values of the ODE. The problematic variables \ - are $unassigned_vars. - - Note that the identification of problematic variables is a best-effort heuristic. - """ - @warn errmsg - end - - uninit = setdiff(unknowns(sys), [unknowns(isys); observables(isys)]) - - # TODO: throw on uninitialized arrays - filter!(x -> !(x isa Symbolics.Arr), uninit) - if is_time_dependent(sys) && !isempty(uninit) - allow_incomplete || throw(IncompleteInitializationError(uninit)) - # for incomplete initialization, we will add the missing variables as parameters. - # they will be updated by `update_initializeprob!` and `initializeprobmap` will - # use them to construct the new `u0`. - newparams = map(toparam, uninit) - append!(get_ps(isys), newparams) - isys = complete(isys) - end - - neqs = length(equations(isys)) - nunknown = length(unknowns(isys)) - - if use_scc - scc_message = "`SCCNonlinearProblem` can only be used for initialization of fully determined systems and hence will not be used here. " - else - scc_message = "" - end - - if warn_initialize_determined && neqs > nunknown - @warn "Initialization system is overdetermined. $neqs equations for $nunknown unknowns. Initialization will default to using least squares. $(scc_message)To suppress this warning pass warn_initialize_determined = false. To make this warning into an error, pass fully_determined = true" - end - if warn_initialize_determined && neqs < nunknown - @warn "Initialization system is underdetermined. $neqs equations for $nunknown unknowns. Initialization will default to using least squares. $(scc_message)To suppress this warning pass warn_initialize_determined = false. To make this warning into an error, pass fully_determined = true" - end - - parammap = recursive_unwrap(anydict(parammap)) - if t !== nothing - parammap[get_iv(sys)] = t - end - filter!(kvp -> kvp[2] !== missing, parammap) - - u0map = to_varmap(u0map, unknowns(sys)) - if isempty(guesses) - guesses = Dict() - end - - filter_missing_values!(u0map) - filter_missing_values!(parammap) - u0map = merge(ModelingToolkit.guesses(sys), todict(guesses), u0map) - - TProb = if neqs == nunknown && isempty(unassigned_vars) - if use_scc && neqs > 0 - if is_split(isys) - SCCNonlinearProblem - else - @warn "`SCCNonlinearProblem` can only be used with `split = true` systems. Simplify your `ODESystem` with `split = true` or pass `use_scc = false` to disable this warning" - NonlinearProblem - end - else - NonlinearProblem - end - else - NonlinearLeastSquaresProblem - end - TProb{iip}(isys, u0map, parammap; kwargs..., - build_initializeprob = false, is_initializeprob = true) -end From 1dcc454e27527cb757adf49fecc3239394a244a4 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Wed, 14 May 2025 13:50:38 +0530 Subject: [PATCH 057/235] refactor: do not rely on `ArrayPartition` when unit-checking jumps --- src/systems/unit_check.jl | 8 ++++++-- src/systems/validation.jl | 9 +++++++-- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/systems/unit_check.jl b/src/systems/unit_check.jl index 83a5ac5483..acf7451065 100644 --- a/src/systems/unit_check.jl +++ b/src/systems/unit_check.jl @@ -267,9 +267,13 @@ function validate(jump::MassActionJump, t::Symbolic; info::String = "") ["scaled_rates", "1/(t*reactants^$n))"]; info) end -function validate(jumps::ArrayPartition{<:Union{Any, Vector{<:JumpType}}}, t::Symbolic) +function validate(jumps::Vector{JumpType}, t::Symbolic) labels = ["in Mass Action Jumps,", "in Constant Rate Jumps,", "in Variable Rate Jumps,"] - all([validate(jumps.x[idx], t, info = labels[idx]) for idx in 1:3]) + majs = filter(x -> x isa MassActionJump, jumps) + crjs = filter(x -> x isa ConstantRateJump, jumps) + vrjs = filter(x -> x isa VariableRateJump, jumps) + splitjumps = [majs, crjs, vrjs] + all([validate(js, t; info) for (js, info) in zip(splitjumps, labels)]) end function validate(eq::Union{Inequality, Equation}; info::String = "") diff --git a/src/systems/validation.jl b/src/systems/validation.jl index 84dd3b07e5..d416a02ea2 100644 --- a/src/systems/validation.jl +++ b/src/systems/validation.jl @@ -5,6 +5,7 @@ using ..ModelingToolkit: ValidationError, ModelingToolkit, Connection, instream, JumpType, VariableUnit, get_systems, Conditional, Comparison +using JumpProcesses: MassActionJump, ConstantRateJump, VariableRateJump using Symbolics: Symbolic, value, issym, isadd, ismul, ispow const MT = ModelingToolkit @@ -231,9 +232,13 @@ function validate(jump::MT.MassActionJump, t::Symbolic; info::String = "") ["scaled_rates", "1/(t*reactants^$n))"]; info) end -function validate(jumps::ArrayPartition{<:Union{Any, Vector{<:JumpType}}}, t::Symbolic) +function validate(jumps::Vector{JumpType}, t::Symbolic) labels = ["in Mass Action Jumps,", "in Constant Rate Jumps,", "in Variable Rate Jumps,"] - all([validate(jumps.x[idx], t, info = labels[idx]) for idx in 1:3]) + majs = filter(x -> x isa MassActionJump, jumps) + crjs = filter(x -> x isa ConstantRateJump, jumps) + vrjs = filter(x -> x isa VariableRateJump, jumps) + splitjumps = [majs, crjs, vrjs] + all([validate(js, t; info) for (js, info) in zip(splitjumps, labels)]) end function validate(eq::MT.Equation; info::String = "") From 74a21539369fac1318336886659730f6f0d41916 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Wed, 14 May 2025 12:23:23 +0530 Subject: [PATCH 058/235] feat: add unified `System` type feat: add unified `System` type feat: add `is_discrete_system` feat: add `is_dde` to `System` refactor: change defaults of `costs` and `consolidate` feat: implement `is_time_dependent` for `System` feat: add `check_complete` feat: add `flatten(::System)` feat: add `isscheduled` to `System` feat: implement `Base.:(==)` for `System` feat: implement `supports_initialization(::System)` feat: add `schedule` field in `System` refactor: improve `System` constructors fix: fix `==` and `hash` implementations for `System` feat: add utility constructors for `OptimizationSystem` and `JumpSystem` feat: add utility constructor for `SDESystem` fix: fix error message for events in time-independent systems refactor: do not use `process_equations` in `System` constructor fix: fix default `costs` in `System` constructor feat: add `initializesystem` field to `System` fix: fix brownians passed to `System` constructor fix: fix `flatten(::System)` feat: add `System(::Equation, ...)` constructor fix: unwrap costs in `System` constructor fix: fix noise equations unit checking fix: convert `constraints` to appropriate type fix: correctly order unknowns in `System` constructor feat: add `preface` to `System` fix: respect scoping in `System` constructor variable discovery fix: fix `flatten(::System)` feat: add `OptimizationSystem` ctor where cost is an array --- src/ModelingToolkit.jl | 4 +- src/systems/abstractsystem.jl | 17 - src/systems/system.jl | 779 ++++++++++++++++++++++++++++++++++ 3 files changed, 782 insertions(+), 18 deletions(-) create mode 100644 src/systems/system.jl diff --git a/src/ModelingToolkit.jl b/src/ModelingToolkit.jl index 9dd89d452c..9f8b19aa1d 100644 --- a/src/ModelingToolkit.jl +++ b/src/ModelingToolkit.jl @@ -160,6 +160,8 @@ 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/system.jl") include("systems/codegen_utils.jl") include("systems/problem_utils.jl") include("linearization.jl") @@ -255,7 +257,7 @@ export AbstractTimeDependentSystem, AbstractMultivariateSystem export ODEFunction, ODEFunctionExpr, ODEProblemExpr, convert_system, - add_accumulations, System + System, OptimizationSystem, JumpSystem, SDESystem export DAEFunctionExpr, DAEProblemExpr export SDEFunction, SDEFunctionExpr, SDEProblemExpr export SystemStructure diff --git a/src/systems/abstractsystem.jl b/src/systems/abstractsystem.jl index 1da2a8ff73..6b5ede1369 100644 --- a/src/systems/abstractsystem.jl +++ b/src/systems/abstractsystem.jl @@ -2579,23 +2579,6 @@ end ### ### Inheritance & composition ### -function Base.hash(sys::AbstractSystem, s::UInt) - s = hash(nameof(sys), s) - s = foldr(hash, get_systems(sys), init = s) - s = foldr(hash, get_unknowns(sys), init = s) - s = foldr(hash, get_ps(sys), init = s) - if sys isa OptimizationSystem - s = hash(get_op(sys), s) - else - s = foldr(hash, get_eqs(sys), init = s) - end - s = foldr(hash, get_observed(sys), init = s) - s = foldr(hash, get_continuous_events(sys), init = s) - s = foldr(hash, get_discrete_events(sys), init = s) - s = hash(independent_variables(sys), s) - return s -end - """ $(TYPEDSIGNATURES) diff --git a/src/systems/system.jl b/src/systems/system.jl new file mode 100644 index 0000000000..d89bdb0f8e --- /dev/null +++ b/src/systems/system.jl @@ -0,0 +1,779 @@ +struct Schedule{V <: BipartiteGraphs.Matching} + """ + Maximal matching of variables to equations calculated during structural simplification. + """ + var_eq_matching::V + """ + Mapping of `Differential`s of variables to corresponding derivative expressions. + """ + dummy_sub::Dict{Any, Any} +end + +struct System <: AbstractSystem + tag::UInt + eqs::Vector{Equation} + # nothing - no noise + # vector - diagonal noise + # matrix - generic form + # column matrix - scalar noise + noise_eqs::Union{Nothing, AbstractVector, AbstractMatrix} + jumps::Vector{JumpType} + constraints::Vector{Union{Equation, Inequality}} + costs::Vector{<:BasicSymbolic} + consolidate::Any + unknowns::Vector + ps::Vector + brownians::Vector + iv::Union{Nothing, BasicSymbolic{Real}} + observed::Vector{Equation} + parameter_dependencies::Vector{Equation} + var_to_name::Dict{Symbol, Any} + name::Symbol + description::String + defaults::Dict + guesses::Dict + systems::Vector{System} + initialization_eqs::Vector{Equation} + continuous_events::Vector{SymbolicContinuousCallback} + discrete_events::Vector{SymbolicDiscreteCallback} + connector_type::Any + assertions::Dict{BasicSymbolic, String} + metadata::Any + gui_metadata::Any # ? + is_dde::Bool + tstops::Vector{Any} + tearing_state::Any + namespacing::Bool + complete::Bool + index_cache::Union{Nothing, IndexCache} + ignored_connections::Union{ + Nothing, Tuple{Vector{IgnoredAnalysisPoint}, Vector{IgnoredAnalysisPoint}}} + preface::Any + parent::Union{Nothing, System} + initializesystem::Union{Nothing, System} + is_initializesystem::Bool + isscheduled::Bool + schedule::Union{Schedule, Nothing} + + function System( + tag, eqs, noise_eqs, jumps, constraints, costs, consolidate, unknowns, ps, + brownians, iv, observed, parameter_dependencies, var_to_name, name, description, + defaults, guesses, systems, initialization_eqs, continuous_events, discrete_events, + connector_type, assertions = Dict{BasicSymbolic, String}(), + metadata = nothing, gui_metadata = nothing, + is_dde = false, tstops = [], tearing_state = nothing, namespacing = true, + complete = false, index_cache = nothing, ignored_connections = nothing, + preface = nothing, parent = nothing, initializesystem = nothing, + is_initializesystem = false, isscheduled = false, schedule = nothing; + checks::Union{Bool, Int} = true) + if is_initializesystem && iv !== nothing + throw(ArgumentError(""" + Expected initialization system to be time-independent. Found independent + variable $iv. + """)) + end + jumps = Vector{JumpType}(jumps) + if (checks == true || (checks & CheckComponents) > 0) && iv !== nothing + check_independent_variables([iv]) + check_variables(unknowns, iv) + check_parameters(ps, iv) + check_equations(eqs, iv) + if noise_eqs !== nothing && size(noise_eqs, 1) != length(eqs) + throw(IllFormedNoiseEquationsError(size(noise_eqs, 1), length(eqs))) + end + check_equations(equations(continuous_events), iv) + check_subsystems(systems) + end + if checks == true || (checks & CheckUnits) > 0 + u = __get_unit_type(unknowns, ps, iv) + if noise_eqs === nothing + check_units(u, eqs) + else + check_units(u, eqs, noise_eqs) + end + if iv !== nothing + check_units(u, jumps, iv) + end + isempty(constraints) || check_units(u, constraints) + end + new(tag, eqs, noise_eqs, jumps, constraints, costs, + consolidate, unknowns, ps, brownians, iv, + observed, parameter_dependencies, var_to_name, name, description, defaults, + guesses, systems, initialization_eqs, continuous_events, discrete_events, + connector_type, assertions, metadata, gui_metadata, is_dde, + tstops, tearing_state, namespacing, complete, index_cache, ignored_connections, + preface, parent, initializesystem, is_initializesystem, isscheduled, schedule) + end +end + +function default_consolidate(costs, subcosts) + return sum(costs; init = 0.0) + sum(subcosts; init = 0.0) +end + +function System(eqs::Vector{Equation}, iv, dvs, ps, brownians = []; + constraints = Union{Equation, Inequality}[], noise_eqs = nothing, jumps = [], + costs = BasicSymbolic[], consolidate = default_consolidate, + observed = Equation[], parameter_dependencies = Equation[], defaults = Dict(), + guesses = Dict(), systems = System[], initialization_eqs = Equation[], + continuous_events = SymbolicContinuousCallback[], discrete_events = SymbolicDiscreteCallback[], + connector_type = nothing, assertions = Dict{BasicSymbolic, String}(), + metadata = nothing, gui_metadata = nothing, is_dde = nothing, tstops = [], + tearing_state = nothing, ignored_connections = nothing, parent = nothing, + description = "", name = nothing, discover_from_metadata = true, + initializesystem = nothing, is_initializesystem = false, preface = [], + checks = true) + name === nothing && throw(NoNameError()) + + iv = unwrap(iv) + ps = unwrap.(ps) + dvs = unwrap.(dvs) + filter!(!Base.Fix2(isdelay, iv), dvs) + brownians = unwrap.(brownians) + + if !(eqs isa AbstractArray) + eqs = [eqs] + end + + if noise_eqs !== nothing + noise_eqs = unwrap.(noise_eqs) + end + + costs = unwrap.(costs) + if isempty(costs) + costs = Union{BasicSymbolic, Real}[] + end + + parameter_dependencies, ps = process_parameter_dependencies(parameter_dependencies, ps) + defaults = anydict(defaults) + guesses = anydict(guesses) + var_to_name = anydict() + + let defaults = discover_from_metadata ? defaults : Dict(), + guesses = discover_from_metadata ? guesses : Dict() + + process_variables!(var_to_name, defaults, guesses, dvs) + process_variables!(var_to_name, defaults, guesses, ps) + process_variables!( + var_to_name, defaults, guesses, [eq.lhs for eq in parameter_dependencies]) + process_variables!( + var_to_name, defaults, guesses, [eq.rhs for eq in parameter_dependencies]) + process_variables!(var_to_name, defaults, guesses, [eq.lhs for eq in observed]) + process_variables!(var_to_name, defaults, guesses, [eq.rhs for eq in observed]) + end + filter!(!(isnothing ∘ last), defaults) + filter!(!(isnothing ∘ last), guesses) + defaults = anydict([unwrap(k) => unwrap(v) for (k, v) in defaults]) + guesses = anydict([unwrap(k) => unwrap(v) for (k, v) in guesses]) + + sysnames = nameof.(systems) + unique_sysnames = Set(sysnames) + if length(unique_sysnames) != length(sysnames) + throw(NonUniqueSubsystemsError(sysnames, unique_sysnames)) + end + continuous_events, discrete_events = create_symbolic_events( + continuous_events, discrete_events, eqs, iv) + + if iv === nothing && (!isempty(continuous_events) || !isempty(discrete_events)) + throw(EventsInTimeIndependentSystemError(continuous_events, discrete_events)) + end + + if is_dde === nothing + is_dde = _check_if_dde(eqs, iv, systems) + end + + assertions = Dict{BasicSymbolic, String}(unwrap(k) => v for (k, v) in assertions) + + System(Threads.atomic_add!(SYSTEM_COUNT, UInt(1)), eqs, noise_eqs, jumps, constraints, + costs, consolidate, dvs, ps, brownians, iv, observed, parameter_dependencies, + var_to_name, name, description, defaults, guesses, systems, initialization_eqs, + continuous_events, discrete_events, connector_type, assertions, metadata, gui_metadata, is_dde, + tstops, tearing_state, true, false, nothing, ignored_connections, preface, parent, + initializesystem, is_initializesystem; checks) +end + +function System(eqs::Vector{Equation}, dvs, ps; kwargs...) + System(eqs, nothing, dvs, ps; kwargs...) +end + +function System(eqs::Vector{Equation}, iv; kwargs...) + iv === nothing && return System(eqs; kwargs...) + + diffvars = OrderedSet() + othervars = OrderedSet() + ps = Set() + diffeqs = Equation[] + othereqs = Equation[] + for eq in eqs + if !(eq.lhs isa Union{Symbolic, Number}) + push!(othereqs, eq) + continue + end + collect_vars!(othervars, ps, eq, iv) + if iscall(eq.lhs) && operation(eq.lhs) isa Differential + var, _ = var_from_nested_derivative(eq.lhs) + if var in diffvars + throw(ArgumentError(""" + The differential variable $var is not unique in the system of \ + equations. + """)) + end + # this check ensures var is correctly scoped, since `collect_vars!` won't pick + # it up if it belongs to an ancestor system. + if var in othervars + push!(diffvars, var) + end + push!(diffeqs, eq) + else + push!(othereqs, eq) + end + end + + allunknowns = union(diffvars, othervars) + eqs = [diffeqs; othereqs] + + brownians = Set() + for x in allunknowns + x = unwrap(x) + if getvariabletype(x) == BROWNIAN + push!(brownians, x) + end + end + setdiff!(allunknowns, brownians) + + for eq in get(kwargs, :parameter_dependencies, Equation[]) + collect_vars!(allunknowns, ps, eq, iv) + end + + cstrs = Vector{Union{Equation, Inequality}}(get(kwargs, :constraints, [])) + cstrunknowns, cstrps = process_constraint_system(cstrs, allunknowns, ps, iv) + union!(allunknowns, cstrunknowns) + union!(ps, cstrps) + + for ssys in get(kwargs, :systems, System[]) + collect_scoped_vars!(allunknowns, ps, ssys, iv) + end + + costs = get(kwargs, :costs, nothing) + if costs !== nothing + costunknowns, costps = process_costs(costs, allunknowns, ps, iv) + union!(allunknowns, costunknowns) + union!(ps, costps) + end + + for v in allunknowns + isdelay(v, iv) || continue + collect_vars!(allunknowns, ps, arguments(v)[1], iv) + end + + new_ps = gather_array_params(ps) + + noiseeqs = get(kwargs, :noise_eqs, nothing) + if noiseeqs !== nothing + # validate noise equations + noisedvs = OrderedSet() + noiseps = OrderedSet() + collect_vars!(noisedvs, noiseps, noiseeqs, iv) + for dv in noisedvs + dv ∈ allunknowns || + throw(ArgumentError("Variable $dv in noise equations is not an unknown of the system.")) + end + end + + return System( + eqs, iv, collect(allunknowns), collect(new_ps), collect(brownians); kwargs...) +end + +function System(eqs::Vector{Equation}; kwargs...) + eqs = collect(eqs) + + allunknowns = OrderedSet() + ps = OrderedSet() + for eq in eqs + collect_vars!(allunknowns, ps, eq, nothing) + end + for eq in get(kwargs, :parameter_dependencies, Equation[]) + collect_vars!(allunknowns, ps, eq, nothing) + end + for ssys in get(kwargs, :systems, System[]) + collect_scoped_vars!(allunknowns, ps, ssys, nothing) + end + costs = get(kwargs, :costs, nothing) + if costs !== nothing + costunknowns, costps = process_costs(costs, allunknowns, ps, nothing) + union!(allunknowns, costunknowns) + union!(ps, costps) + end + cstrs = Vector{Union{Equation, Inequality}}(get(kwargs, :constraints, [])) + for eq in cstrs + collect_vars!(allunknowns, ps, eq, nothing) + end + + new_ps = gather_array_params(ps) + + return System(eqs, nothing, collect(allunknowns), collect(new_ps); kwargs...) +end + +System(eq::Equation, args...; kwargs...) = System([eq], args...; kwargs...) + +function gather_array_params(ps) + new_ps = OrderedSet() + for p in ps + if iscall(p) && operation(p) === getindex + par = arguments(p)[begin] + if Symbolics.shape(Symbolics.unwrap(par)) !== Symbolics.Unknown() && + all(par[i] in ps for i in eachindex(par)) + push!(new_ps, par) + else + push!(new_ps, p) + end + else + if symbolic_type(p) == ArraySymbolic() && + Symbolics.shape(unwrap(p)) != Symbolics.Unknown() + for i in eachindex(p) + delete!(new_ps, p[i]) + end + end + push!(new_ps, p) + end + end + return new_ps +end + +""" +Process variables in constraints of the (ODE) System. +""" +function process_constraint_system( + constraints::Vector{Union{Equation, Inequality}}, sts, ps, iv; validate = true) + isempty(constraints) && return Set(), Set() + + constraintsts = OrderedSet() + constraintps = OrderedSet() + for cons in constraints + collect_vars!(constraintsts, constraintps, cons, iv) + union!(constraintsts, collect_applied_operators(cons, Differential)) + end + + # Validate the states. + if validate + validate_vars_and_find_ps!(constraintsts, constraintps, sts, iv) + end + + return constraintsts, constraintps +end + +""" +Process the costs for the constraint system. +""" +function process_costs(costs::Vector, sts, ps, iv) + coststs = OrderedSet() + costps = OrderedSet() + for cost in costs + collect_vars!(coststs, costps, cost, iv) + end + + validate_vars_and_find_ps!(coststs, costps, sts, iv) + coststs, costps +end + +""" +Validate that all the variables in an auxiliary system of the (ODE) System (constraint or costs) are +well-formed states or parameters. + - Callable/delay variables (e.g. of the form x(0.6) should be unknowns of the system (and have one arg, etc.) + - Callable/delay parameters should be parameters of the system + +Return the set of additional parameters found in the system, e.g. in x(p) ~ 3 then p should be added as a +parameter of the system. +""" +function validate_vars_and_find_ps!(auxvars, auxps, sysvars, iv) + sts = sysvars + + for var in auxvars + if !iscall(var) + occursin(iv, var) && (var ∈ sts || + throw(ArgumentError("Time-dependent variable $var is not an unknown of the system."))) + elseif length(arguments(var)) > 1 + throw(ArgumentError("Too many arguments for variable $var.")) + elseif length(arguments(var)) == 1 + if iscall(var) && operation(var) isa Differential + var = only(arguments(var)) + end + arg = only(arguments(var)) + operation(var)(iv) ∈ sts || + throw(ArgumentError("Variable $var is not a variable of the System. Called variables must be variables of the System.")) + + isequal(arg, iv) || isparameter(arg) || arg isa Integer || + arg isa AbstractFloat || + throw(ArgumentError("Invalid argument specified for variable $var. The argument of the variable should be either $iv, a parameter, or a value specifying the time that the constraint holds.")) + + isparameter(arg) && !isequal(arg, iv) && push!(auxps, arg) + else + var ∈ sts && + @warn "Variable $var has no argument. It will be interpreted as $var($iv), and the constraint will apply to the entire interval." + end + end +end + +""" + $(TYPEDSIGNATURES) + +Check if a system is a (possibly implicit) discrete system. Hybrid systems are turned into +callbacks, so checking if any LHS is shifted is sufficient. If a variable is shifted in +the input equations there _will_ be a `Shift` equation in the simplified system. +""" +function is_discrete_system(sys::System) + any(eq -> isoperator(eq.lhs, Shift), equations(sys)) +end + +SymbolicIndexingInterface.is_time_dependent(sys::System) = get_iv(sys) !== nothing + +""" + is_dde(sys::AbstractSystem) + +Return a boolean indicating whether a system represents a set of delay +differential equations. +""" +is_dde(sys::AbstractSystem) = has_is_dde(sys) && get_is_dde(sys) + +function _check_if_dde(eqs, iv, subsystems) + is_dde = any(ModelingToolkit.is_dde, subsystems) + if !is_dde + vs = Set() + for eq in eqs + vars!(vs, eq) + is_dde = any(vs) do sym + isdelay(unwrap(sym), iv) + end + is_dde && break + end + end + return is_dde +end + +function flatten(sys::System, noeqs = false) + systems = get_systems(sys) + isempty(systems) && return sys + costs = cost(sys) + if _iszero(costs) + costs = Union{Real, BasicSymbolic}[] + else + costs = [costs] + end + # We don't include `ignored_connections` in the flattened system, because + # connection expansion inherently requires the hierarchy structure. If the system + # is being flattened, then we no longer want to expand connections (or have already + # done so) and thus don't care about `ignored_connections`. + return System(noeqs ? Equation[] : equations(sys), get_iv(sys), unknowns(sys), + parameters(sys; initial_parameters = true), brownians(sys); + jumps = jumps(sys), constraints = constraints(sys), costs = costs, + consolidate = default_consolidate, observed = observed(sys), + parameter_dependencies = parameter_dependencies(sys), defaults = defaults(sys), + guesses = guesses(sys), continuous_events = continuous_events(sys), + discrete_events = discrete_events(sys), assertions = assertions(sys), + is_dde = is_dde(sys), tstops = symbolic_tstops(sys), + initialization_eqs = initialization_equations(sys), + # without this, any defaults/guesses obtained from metadata that were + # later removed by the user will be re-added. Right now, we just want to + # retain `defaults(sys)` as-is. + discover_from_metadata = false, metadata = get_metadata(sys), + description = description(sys), name = nameof(sys)) +end + +has_massactionjumps(js::System) = any(x -> x isa MassActionJump, jumps(js)) +has_constantratejumps(js::System) = any(x -> x isa ConstantRateJump, jumps(js)) +has_variableratejumps(js::System) = any(x -> x isa VariableRateJump, jumps(js)) +# TODO: do we need this? it's kind of weird to keep +has_equations(js::System) = !isempty(equations(js)) + +function noise_equations_equal(sys1::System, sys2::System) + neqs1 = get_noise_eqs(sys1) + neqs2 = get_noise_eqs(sys2) + if neqs1 === nothing && neqs2 === nothing + return true + elseif neqs1 === nothing || neqs2 === nothing + return false + end + ndims(neqs1) == ndims(neqs2) || return false + + eqs1 = get_eqs(sys1) + eqs2 = get_eqs(sys2) + + # get the permutation vector of `eqs2` in terms of `eqs1` + # eqs1_used tracks the elements of `eqs1` already used in the permutation + eqs1_used = falses(length(eqs1)) + # the permutation of `eqs1` that gives `eqs2` + eqs2_perm = Int[] + for eq in eqs2 + # find the first unused element of `eqs1` equal to `eq` + idx = findfirst(i -> isequal(eq, eqs1[i]) && !eqs1_used[i], eachindex(eqs1)) + # none found, so noise equations are not equal + idx === nothing && return false + push!(eqs2_perm, idx) + end + + if neqs1 isa Vector + return isequal(@view(neqs1[eqs2_perm]), neqs2) + else + return isequal(@view(neqs1[eqs2_perm, :]), neqs2) + end +end + +function ignored_connections_equal(sys1::System, sys2::System) + ic1 = get_ignored_connections(sys1) + ic2 = get_ignored_connections(sys2) + if ic1 === nothing && ic2 === nothing + return true + elseif ic1 === nothing || ic2 === nothing + return false + end + return _eq_unordered(ic1[1], ic2[1]) && _eq_unordered(ic1[2], ic2[2]) +end + +function Base.:(==)(sys1::System, sys2::System) + sys1 === sys2 && return true + iv1 = get_iv(sys1) + iv2 = get_iv(sys2) + isequal(iv1, iv2) && + isequal(nameof(sys1), nameof(sys2)) && + _eq_unordered(get_eqs(sys1), get_eqs(sys2)) && + noise_equations_equal(sys1, sys2) && + _eq_unordered(get_jumps(sys1), get_jumps(sys2)) && + _eq_unordered(get_constraints(sys1), get_constraints(sys2)) && + _eq_unordered(get_costs(sys1), get_costs(sys2)) && + isequal(get_consolidate(sys1), get_consolidate(sys2)) && + _eq_unordered(get_unknowns(sys1), get_unknowns(sys2)) && + _eq_unordered(get_ps(sys1), get_ps(sys2)) && + _eq_unordered(get_brownians(sys1), get_brownians(sys2)) && + _eq_unordered(get_observed(sys1), get_observed(sys2)) && + _eq_unordered(get_parameter_dependencies(sys1), get_parameter_dependencies(sys2)) && + isequal(get_description(sys1), get_description(sys2)) && + isequal(get_defaults(sys1), get_defaults(sys2)) && + isequal(get_guesses(sys1), get_guesses(sys2)) && + _eq_unordered(get_initialization_eqs(sys1), get_initialization_eqs(sys2)) && + _eq_unordered(get_continuous_events(sys1), get_continuous_events(sys2)) && + _eq_unordered(get_discrete_events(sys1), get_discrete_events(sys2)) && + isequal(get_connector_type(sys1), get_connector_type(sys2)) && + isequal(get_assertions(sys1), get_assertions(sys2)) && + isequal(get_metadata(sys1), get_metadata(sys2)) && + isequal(get_gui_metadata(sys1), get_gui_metadata(sys2)) && + get_is_dde(sys1) == get_is_dde(sys2) && + _eq_unordered(get_tstops(sys1), get_tstops(sys2)) && + # not comparing tearing states because checking if they're equal up to ordering + # is difficult + getfield(sys1, :namespacing) == getfield(sys2, :namespacing) && + getfield(sys1, :complete) == getfield(sys2, :complete) && + ignored_connections_equal(sys1, sys2) && + get_parent(sys1) == get_parent(sys2) && + get_isscheduled(sys1) == get_isscheduled(sys2) && + all(s1 == s2 for (s1, s2) in zip(get_systems(sys1), get_systems(sys2))) +end + +function Base.hash(sys::System, h::UInt) + h = hash(nameof(sys), h) + h = hash(get_iv(sys), h) + # be considerate of things compared using `_eq_unordered` in `==` + eqs = get_eqs(sys) + eq_sortperm = sortperm(eqs; by = string) + h = hash(@view(eqs[eq_sortperm]), h) + neqs = get_noise_eqs(sys) + if neqs === nothing + h = hash(nothing, h) + elseif neqs isa Vector + h = hash(@view(neqs[eq_sortperm]), h) + else + h = hash(@view(neqs[eq_sortperm, :]), h) + end + h = hash(Set(get_jumps(sys)), h) + h = hash(Set(get_constraints(sys)), h) + h = hash(Set(get_costs(sys)), h) + h = hash(get_consolidate(sys), h) + h = hash(Set(get_unknowns(sys)), h) + h = hash(Set(get_ps(sys)), h) + h = hash(Set(get_brownians(sys)), h) + h = hash(Set(get_observed(sys)), h) + h = hash(Set(get_parameter_dependencies(sys)), h) + h = hash(get_description(sys), h) + h = hash(get_defaults(sys), h) + h = hash(get_guesses(sys), h) + h = hash(Set(get_initialization_eqs(sys)), h) + h = hash(Set(get_continuous_events(sys)), h) + h = hash(Set(get_discrete_events(sys)), h) + h = hash(get_connector_type(sys), h) + h = hash(get_assertions(sys), h) + h = hash(get_metadata(sys), h) + h = hash(get_gui_metadata(sys), h) + h = hash(get_is_dde(sys), h) + h = hash(Set(get_tstops(sys)), h) + h = hash(Set(getfield(sys, :namespacing)), h) + h = hash(Set(getfield(sys, :complete)), h) + ics = get_ignored_connections(sys) + if ics === nothing + h = hash(ics, h) + else + h = hash(Set(ics[1]), hash(Set(ics[2]), h), h) + end + h = hash(get_parent(sys), h) + h = hash(get_isscheduled(sys), h) + for s in get_systems(sys) + h = hash(s, h) + end + return h +end + +""" + $(TYPEDSIGNATURES) +""" +function check_complete(sys::System, obj) + iscomplete(sys) || throw(SystemNotCompleteError(obj)) +end + +function NonlinearSystem(sys::System) + if !is_time_dependent(sys) + throw(ArgumentError("`NonlinearSystem` constructor expects a time-dependent `System`")) + end + eqs = equations(sys) + obs = observed(sys) + subrules = Dict([D(x) => 0.0 for x in unknowns(sys)]) + eqs = map(eqs) do eq + fast_substitute(eq, subrules) + end + nsys = System(eqs, unknowns(sys), [parameters(sys); get_iv(sys)]; + parameter_dependencies = parameter_dependencies(sys), + defaults = merge(defaults(sys), Dict(get_iv(sys) => Inf)), guesses = guesses(sys), + initialization_eqs = initialization_equations(sys), name = nameof(sys), + observed = obs) + if iscomplete(sys) + nsys = complete(nsys; split = is_split(sys)) + end + return nsys +end + +######## +# Utility constructors +######## + +function OptimizationSystem(cost; kwargs...) + return System(Equation[]; costs = [cost], kwargs...) +end + +function OptimizationSystem(cost, dvs, ps; kwargs...) + return System(Equation[], nothing, dvs, ps; costs = [cost], kwargs...) +end + +function OptimizationSystem(cost::Array; kwargs...) + return System(Equation[]; costs = vec(cost), kwargs...) +end + +function OptimizationSystem(cost::Array, dvs, ps; kwargs...) + return System(Equation[], nothing, dvs, ps; costs = vec(cost), kwargs...) +end + +function JumpSystem(jumps, iv; kwargs...) + mask = isa.(jumps, Equation) + eqs = Vector{Equation}(jumps[mask]) + jumps = jumps[.!mask] + return System(eqs, iv; jumps, kwargs...) +end + +function JumpSystem(jumps, iv, dvs, ps; kwargs...) + mask = isa.(jumps, Equation) + eqs = Vector{Equation}(jumps[mask]) + jumps = jumps[.!mask] + return System(eqs, iv, dvs, ps; jumps, kwargs...) +end + +function SDESystem(eqs::Vector{Equation}, noise, iv; is_scalar_noise = false, kwargs...) + if is_scalar_noise + if !(noise isa Vector) + throw(ArgumentError("Expected noise to be a vector if `is_scalar_noise`")) + end + noise = repeat(reshape(noise, (1, :)), length(eqs)) + end + return System(eqs, iv; noise_eqs = noise, kwargs...) +end + +function SDESystem( + eqs::Vector{Equation}, noise, iv, dvs, ps; is_scalar_noise = false, kwargs...) + if is_scalar_noise + if !(noise isa Vector) + throw(ArgumentError("Expected noise to be a vector if `is_scalar_noise`")) + end + noise = repeat(reshape(noise, (1, :)), length(eqs)) + end + return System(eqs, iv, dvs, ps; noise_eqs = noise, kwargs...) +end + +function SDESystem(sys::System, noise; kwargs...) + SDESystem(equations(sys), noise, get_iv(sys); kwargs...) +end + +struct SystemNotCompleteError <: Exception + obj::Any +end + +function Base.showerror(io::IO, err::SystemNotCompleteError) + print(io, """ + A completed system is required. Call `complete` or `structural_simplify` on the \ + system before creating a `$(err.obj)`. + """) +end + +struct IllFormedNoiseEquationsError <: Exception + noise_eqs_rows::Int + eqs_length::Int +end + +function Base.showerror(io::IO, err::IllFormedNoiseEquationsError) + print(io, """ + Noise equations are ill-formed. The number of rows much must number of drift \ + equations. `size(neqs, 1) == $(err.noise_eqs_rows) != length(eqs) == \ + $(err.eqs_length)`. + """) +end + +function NoNameError() + ArgumentError(""" + The `name` keyword must be provided. Please consider using the `@named` macro. + """) +end + +struct NonUniqueSubsystemsError <: Exception + names::Vector{Symbol} + uniques::Set{Symbol} +end + +function Base.showerror(io::IO, err::NonUniqueSubsystemsError) + dupes = Set{Symbol}() + for n in err.names + if !(n in err.uniques) + push!(dupes, n) + end + delete!(err.uniques, n) + end + println(io, "System names must be unique. The following system names were duplicated:") + for n in dupes + println(io, " ", n) + end +end + +struct EventsInTimeIndependentSystemError <: Exception + cevents::Vector + devents::Vector +end + +function Base.showerror(io::IO, err::EventsInTimeIndependentSystemError) + println(io, """ + Events are not supported in time-indepent systems. Provide an independent variable to \ + make the system time-dependent or remove the events. + + The following continuous events were provided: + $(err.cevents) + + The following discrete events were provided: + $(err.devents) + """) +end + +function supports_initialization(sys::System) + return isempty(jumps(sys)) && _iszero(cost(sys)) && + isempty(constraints(sys)) +end From c146dd08ee0202b68fc13bb4ee35702f715091f1 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Mon, 14 Apr 2025 23:14:54 +0530 Subject: [PATCH 059/235] feat: add getters for new `System` fields --- src/systems/abstractsystem.jl | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/systems/abstractsystem.jl b/src/systems/abstractsystem.jl index 6b5ede1369..7ed9d97ad0 100644 --- a/src/systems/abstractsystem.jl +++ b/src/systems/abstractsystem.jl @@ -876,11 +876,14 @@ end for prop in [:eqs :tag - :noiseeqs + :noiseeqs # TODO: remove + :noise_eqs :iv :unknowns :ps :tspan + :brownians + :jumps :name :description :var_to_name From 0da70f3ff4d96b9d6038014d583ae2cc8eb88760 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Mon, 14 Apr 2025 23:15:20 +0530 Subject: [PATCH 060/235] feat: add hierarchical aggregator functions for jumps, brownians and cost feat: export `jumps` --- src/ModelingToolkit.jl | 2 +- src/systems/abstractsystem.jl | 51 +++++++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+), 1 deletion(-) diff --git a/src/ModelingToolkit.jl b/src/ModelingToolkit.jl index 9f8b19aa1d..0fd1347117 100644 --- a/src/ModelingToolkit.jl +++ b/src/ModelingToolkit.jl @@ -289,7 +289,7 @@ export Differential, expand_derivatives, @derivatives export Equation, ConstrainedEquation export Term, Sym export SymScope, LocalScope, ParentScope, GlobalScope -export independent_variable, equations, controls, observed, full_equations +export independent_variable, equations, controls, observed, full_equations, jumps export initialization_equations, guesses, defaults, parameter_dependencies, hierarchy export structural_simplify, expand_connections, linearize, linearization_function, LinearizationProblem diff --git a/src/systems/abstractsystem.jl b/src/systems/abstractsystem.jl index 7ed9d97ad0..243865dda1 100644 --- a/src/systems/abstractsystem.jl +++ b/src/systems/abstractsystem.jl @@ -1301,6 +1301,28 @@ function namespace_equation(eq::Equation, (_lhs ~ _rhs)::Equation end +function namespace_jump(j::ConstantRateJump, sys) + return ConstantRateJump(namespace_expr(j.rate, sys), namespace_expr(j.affect!, sys)) +end + +function namespace_jump(j::VariableRateJump, sys) + return VariableRateJump(namespace_expr(j.rate, sys), namespace_expr(j.affect!, sys)) +end + +function namespace_jump(j::MassActionJump, sys) + return MassActionJump(namespace_expr(j.scaled_rates, sys), + [namespace_expr(k, sys) => namespace_expr(v, sys) for (k, v) in j.reactant_stoch], + [namespace_expr(k, sys) => namespace_expr(v, sys) for (k, v) in j.net_stoch]) +end + +function namespace_jumps(sys::AbstractSystem) + return [namespace_jump(j, sys) for j in get_jumps(sys)] +end + +function namespace_brownians(sys::AbstractSystem) + return [renamespace(sys, b) for b in brownians(sys)] +end + function namespace_assignment(eq::Assignment, sys) _lhs = namespace_expr(eq.lhs, sys) _rhs = namespace_expr(eq.rhs, sys) @@ -1734,6 +1756,35 @@ function equations_toplevel(sys::AbstractSystem) return get_eqs(sys) end +function jumps(sys::AbstractSystem) + js = get_jumps(sys) + systems = get_systems(sys) + if isempty(systems) + return js + end + return [js; reduce(vcat, namespace_jumps.(systems); init = [])] +end + +function brownians(sys::AbstractSystem) + bs = get_brownians(sys) + systems = get_systems(sys) + if isempty(systems) + return bs + end + return [bs; reduce(vcat, namespace_brownians.(systems); init = [])] +end + +function cost(sys::AbstractSystem) + cs = get_costs(sys) + consolidate = get_consolidate(sys) + systems = get_systems(sys) + if isempty(systems) + return consolidate(cs, Float64[]) + end + subcosts = [namespace_expr(cost(subsys), subsys) for subsys in systems] + return consolidate(cs, subcosts) +end + """ $(TYPEDSIGNATURES) From d15f60efab218b39552bcfd92b6d3991dcf2a8b0 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Mon, 21 Apr 2025 10:49:51 +0530 Subject: [PATCH 061/235] refactor: move `constraints` to `abstractsystem.jl` --- src/systems/abstractsystem.jl | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/systems/abstractsystem.jl b/src/systems/abstractsystem.jl index 243865dda1..4bb45738de 100644 --- a/src/systems/abstractsystem.jl +++ b/src/systems/abstractsystem.jl @@ -1785,6 +1785,30 @@ function cost(sys::AbstractSystem) return consolidate(cs, subcosts) end +namespace_constraint(eq::Equation, sys) = namespace_equation(eq, sys) + +namespace_constraint(ineq::Inequality, sys) = namespace_inequality(ineq, sys) + +function namespace_inequality(ineq::Inequality, sys, n = nameof(sys)) + _lhs = namespace_expr(ineq.lhs, sys, n) + _rhs = namespace_expr(ineq.rhs, sys, n) + Inequality(_lhs, + _rhs, + ineq.relational_op) +end + +function namespace_constraints(sys) + cstrs = constraints(sys) + isempty(cstrs) && return Vector{Union{Equation, Inequality}}(undef, 0) + map(cstr -> namespace_constraint(cstr, sys), cstrs) +end + +function constraints(sys) + cs = get_constraints(sys) + systems = get_systems(sys) + isempty(systems) ? cs : [cs; reduce(vcat, namespace_constraints.(systems))] +end + """ $(TYPEDSIGNATURES) From e5bad1ed88a085dcaf48b4226e412f241d097fb2 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Mon, 14 Apr 2025 23:55:02 +0530 Subject: [PATCH 062/235] refactor: don't warn about system supertype for `System` --- src/systems/abstractsystem.jl | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/systems/abstractsystem.jl b/src/systems/abstractsystem.jl index 4bb45738de..a5567f7743 100644 --- a/src/systems/abstractsystem.jl +++ b/src/systems/abstractsystem.jl @@ -244,7 +244,9 @@ Get the independent variable(s) of the system `sys`. See also [`@independent_variables`](@ref) and [`ModelingToolkit.get_iv`](@ref). """ function independent_variables(sys::AbstractSystem) - @warn "Please declare ($(typeof(sys))) as a subtype of `AbstractTimeDependentSystem`, `AbstractTimeIndependentSystem` or `AbstractMultivariateSystem`." + if !(sys isa System) + @warn "Please declare ($(typeof(sys))) as a subtype of `AbstractTimeDependentSystem`, `AbstractTimeIndependentSystem` or `AbstractMultivariateSystem`." + end if isdefined(sys, :iv) return [getfield(sys, :iv)] elseif isdefined(sys, :ivs) From acc4f3186cb39b39eff52b2ed06c1f1eb134eee8 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Sun, 20 Apr 2025 15:22:32 +0530 Subject: [PATCH 063/235] refactor: port `stochastic_integral_transform` and `Girsanov_transform` --- src/systems/diffeqs/basic_transformations.jl | 175 +++++++++++++++++++ 1 file changed, 175 insertions(+) diff --git a/src/systems/diffeqs/basic_transformations.jl b/src/systems/diffeqs/basic_transformations.jl index 37c8d8d021..3c1081fcbe 100644 --- a/src/systems/diffeqs/basic_transformations.jl +++ b/src/systems/diffeqs/basic_transformations.jl @@ -234,3 +234,178 @@ function change_independent_variable( end return transform(sys) end + +""" +$(TYPEDSIGNATURES) + +Choose correction_factor=-1//2 (1//2) to convert Ito -> Stratonovich (Stratonovich->Ito). +""" +function stochastic_integral_transform(sys::System, correction_factor) + if !isempty(get_systems(sys)) + throw(ArgumentError("The system must be flattened.")) + end + if get_noise_eqs(sys) === nothing + throw(ArgumentError(""" + `$stochastic_integral_transform` expects a system with noise_eqs. If your \ + noise is specified using brownian variables, consider calling \ + `structural_simplify`. + """)) + end + name = nameof(sys) + noise_eqs = get_noise_eqs(sys) + eqs = equations(sys) + dvs = unknowns(sys) + ps = parameters(sys) + # use the general interface + if noise_eqs isa Vector + _eqs = reduce(vcat, [eqs[i].lhs ~ noise_eqs[i] for i in eachindex(dvs)]) + de = System(_eqs, get_iv(sys), dvs, ps, name = name, checks = false) + + jac = calculate_jacobian(de, sparse = false, simplify = false) + ∇σσ′ = simplify.(jac * noise_eqs) + else + dimunknowns, m = size(noise_eqs) + _eqs = reduce(vcat, [eqs[i].lhs ~ noise_eqs[i] for i in eachindex(dvs)]) + de = System(_eqs, get_iv(sys), dvs, ps, name = name, checks = false) + + jac = calculate_jacobian(de, sparse = false, simplify = false) + ∇σσ′ = simplify.(jac * noise_eqs[:, 1]) + for k in 2:m + __eqs = reduce(vcat, + [eqs[i].lhs ~ noise_eqs[Int(i + (k - 1) * dimunknowns)] + for i in eachindex(dvs)]) + de = System(__eqs, get_iv(sys), dvs, dvs, name = name, checks = false) + + jac = calculate_jacobian(de, sparse = false, simplify = false) + ∇σσ′ = ∇σσ′ + simplify.(jac * noise_eqs[:, k]) + end + end + deqs = reduce(vcat, + [eqs[i].lhs ~ eqs[i].rhs + correction_factor * ∇σσ′[i] for i in eachindex(dvs)]) + + # reduce(vcat, [1]) == 1 for some reason + if deqs isa Equation + deqs = [deqs] + end + return @set sys.eqs = deqs +end + +""" +$(TYPEDSIGNATURES) + +Measure transformation method that allows for a reduction in the variance of an estimator `Exp(g(X_t))`. +Input: Original SDE system and symbolic function `u(t,x)` with scalar output that + defines the adjustable parameters `d` in the Girsanov transformation. Optional: initial + condition for `θ0`. +Output: Modified SDESystem with additional component `θ_t` and initial value `θ0`, as well as + the weight `θ_t/θ0` as observed equation, such that the estimator `Exp(g(X_t)θ_t/θ0)` + has a smaller variance. + +Reference: +Kloeden, P. E., Platen, E., & Schurz, H. (2012). Numerical solution of SDE through computer +experiments. Springer Science & Business Media. + +# Example + +```julia +using ModelingToolkit +using ModelingToolkit: t_nounits as t, D_nounits as D + +@parameters α β +@variables x(t) y(t) z(t) + +eqs = [D(x) ~ α*x] +noiseeqs = [β*x] + +@named de = SDESystem(eqs,noiseeqs,t,[x],[α,β]) + +# define u (user choice) +u = x +θ0 = 0.1 +g(x) = x[1]^2 +demod = ModelingToolkit.Girsanov_transform(de, u; θ0=0.1) + +u0modmap = [ + x => x0 +] + +parammap = [ + α => 1.5, + β => 1.0 +] + +probmod = SDEProblem(complete(demod),u0modmap,(0.0,1.0),parammap) +ensemble_probmod = EnsembleProblem(probmod; + output_func = (sol,i) -> (g(sol[x,end])*sol[demod.weight,end],false), + ) + +simmod = solve(ensemble_probmod,EM(),dt=dt,trajectories=numtraj) +``` + +""" +function Girsanov_transform(sys::System, u; θ0 = 1.0) + name = nameof(sys) + + # register new variable θ corresponding to 1D correction process θ(t) + t = get_iv(sys) + D = Differential(t) + @variables θ(t), weight(t) + + # determine the adjustable parameters `d` given `u` + # gradient of u with respect to unknowns + grad = Symbolics.gradient(u, unknowns(sys)) + + noiseeqs = copy(get_noise_eqs(sys)) + if noiseeqs isa Vector + d = simplify.(-(noiseeqs .* grad) / u) + drift_correction = noiseeqs .* d + else + d = simplify.(-noiseeqs * grad / u) + drift_correction = noiseeqs * d + end + + eqs = equations(sys) + dvs = unknowns(sys) + # transformation adds additional unknowns θ: newX = (X,θ) + # drift function for unknowns is modified + # θ has zero drift + deqs = reduce( + vcat, [eqs[i].lhs ~ eqs[i].rhs - drift_correction[i] for i in eachindex(dvs)]) + if deqs isa Equation + deqs = [deqs] + end + deqsθ = D(θ) ~ 0 + push!(deqs, deqsθ) + + # diffusion matrix is of size d x m (d unknowns, m noise), with diagonal noise represented as a d-dimensional vector + # for diagonal noise processes with m>1, the noise process will become non-diagonal; extra unknown component but no new noise process. + # new diffusion matrix is of size d+1 x M + # diffusion for state is unchanged + + noiseqsθ = θ * d + + if noiseeqs isa Vector + m = size(noiseeqs) + if m == 1 + push!(noiseeqs, noiseqsθ) + else + noiseeqs = [Array(Diagonal(wrap.(noiseeqs))); noiseqsθ'] + end + else + noiseeqs = [Array(noiseeqs); noiseqsθ'] + end + + unknown_vars = [dvs; θ] + + # return modified SDE System + @set! sys.eqs = deqs + @set! sys.noise_eqs = noiseeqs + @set! sys.unknowns = unknown_vars + get_defaults(sys)[θ] = θ0 + obs = observed(sys) + @set! sys.observed = [weight ~ θ / θ0; obs] + if get_parent(sys) !== nothing + @set! sys.parent.unknowns = [get_unknowns(get_parent(sys)); [θ, weight]] + end + return sys +end From a0dbe7ffc7ee9918c0bd853ee7719fa4e1c0e77f Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Sun, 27 Apr 2025 23:01:16 +0530 Subject: [PATCH 064/235] refactor: move `eval_or_rgf` to `codegen_utils.jl` --- src/systems/codegen_utils.jl | 21 +++++++++++++++++++++ src/utils.jl | 8 -------- 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/src/systems/codegen_utils.jl b/src/systems/codegen_utils.jl index bedfdbcc37..5d8982c244 100644 --- a/src/systems/codegen_utils.jl +++ b/src/systems/codegen_utils.jl @@ -1,3 +1,24 @@ +""" + $(TYPEDSIGNATURES) + +Given a function expression `expr`, return a callable version of it. + +# Keyword arguments +- `eval_expression`: Whether to use `eval` to make `expr` callable. If `false`, uses + RuntimeGeneratedFunctions.jl. +- `eval_module`: The module to `eval` the expression `expr` in. If `!eval_expression`, + this is the cache and context module for the `RuntimeGeneratedFunction`. +""" +function eval_or_rgf(expr::Expr; eval_expression = false, eval_module = @__MODULE__) + if eval_expression + return eval_module.eval(expr) + else + return drop_expr(RuntimeGeneratedFunction(eval_module, eval_module, expr)) + end +end + +eval_or_rgf(::Nothing; kws...) = nothing + """ $(TYPEDSIGNATURES) diff --git a/src/utils.jl b/src/utils.jl index c3d736f92f..be28fa3401 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -1011,14 +1011,6 @@ function restrict_array_to_union(arr) return Array{T, ndims(arr)}(arr) end -function eval_or_rgf(expr::Expr; eval_expression = false, eval_module = @__MODULE__) - if eval_expression - return eval_module.eval(expr) - else - return drop_expr(RuntimeGeneratedFunction(eval_module, eval_module, expr)) - end -end - function _with_unit(f, x, t, args...) x = f(x, args...) if hasmetadata(x, VariableUnit) && (t isa Symbolic && hasmetadata(t, VariableUnit)) From b938241a70214dcc2debe632b8b6cdcd3cff3331 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Sun, 27 Apr 2025 23:01:50 +0530 Subject: [PATCH 065/235] feat: allow `GeneratedFunctionWrapper` to compile functions and build expressions --- src/systems/codegen_utils.jl | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/systems/codegen_utils.jl b/src/systems/codegen_utils.jl index 5d8982c244..0e6a6979fc 100644 --- a/src/systems/codegen_utils.jl +++ b/src/systems/codegen_utils.jl @@ -265,6 +265,14 @@ function GeneratedFunctionWrapper{P}(foop::O, fiip::I) where {P, O, I} GeneratedFunctionWrapper{P, O, I}(foop, fiip) end +function GeneratedFunctionWrapper{P}(::Type{Val{true}}, foop, fiip; kwargs...) where {P} + :($(GeneratedFunctionWrapper{P})($foop, $fiip)) +end + +function GeneratedFunctionWrapper{P}(::Type{Val{false}}, foop, fiip; kws...) where {P} + GeneratedFunctionWrapper{P}(eval_or_rgf(foop; kws...), eval_or_rgf(fiip; kws...)) +end + function (gfw::GeneratedFunctionWrapper)(args...) _generated_call(gfw, args...) end From 84d69bce8d35cbf6cb1e7c4c51ed228eaccdb0bd Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Mon, 28 Apr 2025 12:15:19 +0530 Subject: [PATCH 066/235] feat: add `maybe_compile_function` --- src/systems/codegen_utils.jl | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/src/systems/codegen_utils.jl b/src/systems/codegen_utils.jl index 0e6a6979fc..6f90af8c16 100644 --- a/src/systems/codegen_utils.jl +++ b/src/systems/codegen_utils.jl @@ -310,3 +310,37 @@ end return :($f($(fargs...))) end end + +""" + $(TYPEDSIGNATURES) + +Optionally compile a method and optionally wrap it in a `GeneratedFunctionWrapper` on the +basis of `expression` `wrap_gfw`, both of type `Union{Type{Val{true}}, Type{Val{false}}}`. +`gfw_args` is the first type parameter of `GeneratedFunctionWrapper`. `f` is a tuple of +function expressions of the form `(oop, iip)` or a single out-of-place function expression. +Keyword arguments are forwarded to `eval_or_rgf`. +""" +function maybe_compile_function(expression, wrap_gfw::Type{Val{true}}, + gfw_args::Tuple{Int, Int, Bool}, f::NTuple{2, Expr}; kwargs...) + GeneratedFunctionWrapper{gfw_args}(expression, f...; kwargs...) +end + +function maybe_compile_function(expression::Type{Val{false}}, wrap_gfw::Type{Val{false}}, + gfw_args::Tuple{Int, Int, Bool}, f::NTuple{2, Expr}; kwargs...) + eval_or_rgf.(f; kwargs...) +end + +function maybe_compile_function(expression::Type{Val{true}}, wrap_gfw::Type{Val{false}}, + gfw_args::Tuple{Int, Int, Bool}, f::Union{Expr, NTuple{2, Expr}}; kwargs...) + return f +end + +function maybe_compile_function(expression, wrap_gfw::Type{Val{true}}, + gfw_args::Tuple{Int, Int, Bool}, f::Expr; kwargs...) + GeneratedFunctionWrapper{gfw_args}(expression, f, nothing; kwargs...) +end + +function maybe_compile_function(expression::Type{Val{false}}, wrap_gfw::Type{Val{false}}, + gfw_args::Tuple{Int, Int, Bool}, f::Expr; kwargs...) + eval_or_rgf(f; kwargs...) +end From c8e4be53895aa6e155d026a014babb8f34a8d760 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Tue, 15 Apr 2025 15:39:08 +0530 Subject: [PATCH 067/235] refactor: fix and document `delay_to_function`, implement it for `System` --- src/systems/codegen_utils.jl | 74 ++++++++++++++++++++++++++++++++++-- 1 file changed, 71 insertions(+), 3 deletions(-) diff --git a/src/systems/codegen_utils.jl b/src/systems/codegen_utils.jl index 6f90af8c16..e7ba2659d7 100644 --- a/src/systems/codegen_utils.jl +++ b/src/systems/codegen_utils.jl @@ -107,6 +107,74 @@ function array_variable_assignments(args...; argument_name = generated_argument_ return assignments end +""" + $(TYPEDSIGNATURES) + +Check if the variable `var` is a delayed variable, where `iv` is the independent +variable. +""" +function isdelay(var, iv) + iv === nothing && return false + isvariable(var) || return false + isparameter(var) && return false + if iscall(var) && !ModelingToolkit.isoperator(var, Symbolics.Operator) + args = arguments(var) + length(args) == 1 || return false + isequal(args[1], iv) || return true + end + return false +end + +""" +The argument of generated functions corresponding to the history function. +""" +const DDE_HISTORY_FUN = Sym{Symbolics.FnType{Tuple{Any, <:Real}, Vector{Real}}}(:___history___) + +""" + $(TYPEDSIGNATURES) + +Turn delayed unknowns in `eqs` into calls to `DDE_HISTORY_FUNCTION`. + +# Arguments + +- `sys`: The system of DDEs. +- `eqs`: The equations to convert. + +# Keyword Arguments + +- `param_arg`: The name of the variable containing the parameter object. +""" +function delay_to_function( + sys::AbstractSystem, eqs = full_equations(sys); param_arg = MTKPARAMETERS_ARG) + delay_to_function(eqs, + get_iv(sys), + Dict{Any, Int}(operation(s) => i for (i, s) in enumerate(unknowns(sys))), + parameters(sys), + DDE_HISTORY_FUN; param_arg) +end +function delay_to_function(eqs::Vector, iv, sts, ps, h; param_arg = MTKPARAMETERS_ARG) + delay_to_function.(eqs, (iv,), (sts,), (ps,), (h,); param_arg) +end +function delay_to_function(eq::Equation, iv, sts, ps, h; param_arg = MTKPARAMETERS_ARG) + delay_to_function(eq.lhs, iv, sts, ps, h; param_arg) ~ delay_to_function( + eq.rhs, iv, sts, ps, h; param_arg) +end +function delay_to_function(expr, iv, sts, ps, h; param_arg = MTKPARAMETERS_ARG) + if isdelay(expr, iv) + v = operation(expr) + time = arguments(expr)[1] + idx = sts[v] + return term(getindex, h(param_arg, time), idx, type = Real) + elseif iscall(expr) + return maketerm(typeof(expr), + operation(expr), + map(x -> delay_to_function(x, iv, sts, ps, h; param_arg), arguments(expr)), + metadata(expr)) + else + return expr + end +end + """ $(TYPEDSIGNATURES) @@ -159,11 +227,11 @@ function build_function_wrapper(sys::AbstractSystem, expr, args...; p_start = 2, obs = filter(filter_observed, observed(sys)) # turn delayed unknowns into calls to the history function if wrap_delays - history_arg = is_split(sys) ? MTKPARAMETERS_ARG : generated_argument_name(p_start) + param_arg = is_split(sys) ? MTKPARAMETERS_ARG : generated_argument_name(p_start) obs = map(obs) do eq - delay_to_function(sys, eq; history_arg) + delay_to_function(sys, eq; param_arg) end - expr = delay_to_function(sys, expr; history_arg) + expr = delay_to_function(sys, expr; param_arg) # add extra argument args = (args[1:(p_start - 1)]..., DDE_HISTORY_FUN, args[p_start:end]...) p_start += 1 From e96e76b01138eec15ea32fca555fd21ac363e357 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Sun, 13 Apr 2025 16:33:25 +0530 Subject: [PATCH 068/235] feat: add initial codegen for `System` feat: add more codegen implementation refactor: compile functions in `generate_*` fix: fix `calculate_jacobian` fix: respect `return_sparsity` in `generate_cost_hessian` feat: add `generate_control_jacobian` --- src/ModelingToolkit.jl | 1 + src/systems/codegen.jl | 562 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 563 insertions(+) create mode 100644 src/systems/codegen.jl diff --git a/src/ModelingToolkit.jl b/src/ModelingToolkit.jl index 0fd1347117..352ac8b68f 100644 --- a/src/ModelingToolkit.jl +++ b/src/ModelingToolkit.jl @@ -163,6 +163,7 @@ include("systems/imperative_affect.jl") include("systems/callbacks.jl") include("systems/system.jl") include("systems/codegen_utils.jl") +include("systems/codegen.jl") include("systems/problem_utils.jl") include("linearization.jl") diff --git a/src/systems/codegen.jl b/src/systems/codegen.jl new file mode 100644 index 0000000000..00f5a714f8 --- /dev/null +++ b/src/systems/codegen.jl @@ -0,0 +1,562 @@ +""" + $(TYPEDSIGNATURES) + +Generate the RHS function for the `equations` of a `System`. + +# Arguments + +# Keyword Arguments + +""" +function generate_rhs(sys::System, dvs = unknowns(sys), + ps = parameters(sys; initial_parameters = true); implicit_dae = false, + scalar = false, expression = Val{true}, wrap_gfw = Val{false}, + eval_expression = false, eval_module = @__MODULE__, override_discrete = false, + kwargs...) + eqs = equations(sys) + obs = observed(sys) + u = dvs + p = reorder_parameters(sys, ps) + t = get_iv(sys) + ddvs = nothing + extra_assignments = Assignment[] + + # used for DAEProblem and ImplicitDiscreteProblem + if implicit_dae + if override_discrete || is_discrete_system(sys) + # ImplicitDiscrete case + D = Shift(t, 1) + rhss = map(eqs) do eq + # Algebraic equations get shifted forward 1, to match with differential + # equations + _iszero(eq.lhs) ? distribute_shift(D(eq.rhs)) : (eq.rhs - eq.lhs) + end + # Handle observables in algebraic equations, since they are shifted + shifted_obs = Equation[distribute_shift(D(eq)) for eq in obs] + obsidxs = observed_equations_used_by(sys, rhss; obs = shifted_obs) + extra_assignments = [Assignment(shifted_obs[i].lhs, shifted_obs[i].rhs) + for i in obsidxs] + else + D = Differential(t) + rhss = [_iszero(eq.lhs) ? eq.rhs : eq.rhs - eq.lhs for eq in eqs] + end + ddvs = map(D, dvs) + else + if !override_discrete && !is_discrete_system(sys) + check_operator_variables(eqs, Differential) + check_lhs(eqs, Differential, Set(dvs)) + end + rhss = [eq.rhs for eq in eqs] + end + + if !isempty(assertions(sys)) + rhss[end] += unwrap(get_assertions_expr(sys)) + end + + # TODO: add an optional check on the ordering of observed equations + if scalar + rhss = only(rhss) + u = only(u) + end + + args = (u, p...) + p_start = 2 + if t !== nothing + args = (args..., t) + end + if implicit_dae + args = (ddvs, args...) + p_start += 1 + end + + res = build_function_wrapper(sys, rhss, args...; p_start, extra_assignments, + expression = Val{true}, expression_module = eval_module, kwargs...) + nargs = length(args) - length(p) + 1 + if is_dde(sys) + p_start += 1 + nargs += 1 + end + return maybe_compile_function( + expression, wrap_gfw, (p_start, nargs, is_split(sys)), + res; eval_expression, eval_module) +end + +function generate_diffusion_function(sys::System, dvs = unknowns(sys), + ps = parameters(sys; initial_parameters = true); expression = Val{true}, + wrap_gfw = Val{false}, eval_expression = false, + eval_module = @__MODULE__, kwargs...) + eqs = get_noise_eqs(sys) + if ndims(eqs) == 2 && size(eqs, 2) == 1 + # scalar noise + eqs = vec(eqs) + end + p = reorder_parameters(sys, ps) + res = build_function_wrapper(sys, eqs, dvs, p..., get_iv(sys); kwargs...) + if expression == Val{true} + return res + end + f_oop, f_iip = eval_or_rgf.(res; eval_expression, eval_module) + p_start = 2 + nargs = 3 + if is_dde(sys) + p_start += 1 + nargs += 1 + end + return maybe_compile_function( + expression, wrap_gfw, (p_start, nargs, is_split(sys)), res; eval_expression, eval_module) +end + +function calculate_tgrad(sys::System; simplify = false) + # We need to remove explicit time dependence on the unknown because when we + # have `u(t) * t` we want to have the tgrad to be `u(t)` instead of `u'(t) * + # t + u(t)`. + rhs = [detime_dvs(eq.rhs) for eq in full_equations(sys)] + iv = get_iv(sys) + xs = unknowns(sys) + rule = Dict(map((x, xt) -> xt => x, detime_dvs.(xs), xs)) + rhs = substitute.(rhs, Ref(rule)) + tgrad = [expand_derivatives(Differential(iv)(r), simplify) for r in rhs] + reverse_rule = Dict(map((x, xt) -> x => xt, detime_dvs.(xs), xs)) + tgrad = Num.(substitute.(tgrad, Ref(reverse_rule))) + return tgrad +end + +function calculate_jacobian(sys::System; + sparse = false, simplify = false, dvs = unknowns(sys)) + obs = Dict(eq.lhs => eq.rhs for eq in observed(sys)) + rhs = map(eq -> fixpoint_sub(eq.rhs - eq.lhs, obs), equations(sys)) + + if sparse + jac = sparsejacobian(rhs, dvs; simplify) + if get_iv(sys) !== nothing + W_s = W_sparsity(sys) + (Is, Js, Vs) = findnz(W_s) + # Add nonzeros of W as non-structural zeros of the Jacobian (to ensure equal + # results for oop and iip Jacobian) + for (i, j) in zip(Is, Js) + iszero(jac[i, j]) && begin + jac[i, j] = 1 + jac[i, j] = 0 + end + end + end + else + jac = jacobian(rhs, dvs; simplify) + end + + return jac +end + +function generate_jacobian(sys::System; + simplify = false, sparse = false, eval_expression = false, + eval_module = @__MODULE__, expression = Val{true}, wrap_gfw = Val{false}, + kwargs...) + dvs = unknowns(sys) + jac = calculate_jacobian(sys; simplify, sparse, dvs) + p = reorder_parameters(sys) + t = get_iv(sys) + if t === nothing + wrap_code = (identity, identity) + else + wrap_code = sparse ? assert_jac_length_header(sys) : (identity, identity) + end + args = (dvs, p...) + nargs = 2 + if is_time_dependent(sys) + args = (args..., t) + nargs = 3 + end + res = build_function_wrapper(sys, jac, args...; wrap_code, expression = Val{true}, + expression_module = eval_module, kwargs...) + return maybe_compile_function( + expression, wrap_gfw, (2, nargs, is_split(sys)), res; eval_expression, eval_module) +end + +function assert_jac_length_header(sys) + W = W_sparsity(sys) + identity, + function add_header(expr) + Func(expr.args, [], expr.body, + [:(@assert $(SymbolicUtils.Code.toexpr(term(findnz, expr.args[1])))[1:2] == + $(findnz(W)[1:2]))]) + end +end + +function generate_tgrad( + sys::System, dvs = unknowns(sys), ps = parameters( + sys; initial_parameters = true); + simplify = false, eval_expression = false, eval_module = @__MODULE__, + expression = Val{true}, wrap_gfw = Val{false}, kwargs...) + tgrad = calculate_tgrad(sys, simplify = simplify) + p = reorder_parameters(sys, ps) + res = build_function_wrapper(sys, tgrad, + dvs, + p..., + get_iv(sys); + expression = Val{true}, + expression_module = eval_module, + kwargs...) + + return maybe_compile_function( + expression, wrap_gfw, (2, 3, is_split(sys)), res; eval_expression, eval_module) +end + +const W_GAMMA = only(@variables ˍ₋gamma) + +function generate_W(sys::System, γ = 1.0, dvs = unknowns(sys), + ps = parameters(sys; initial_parameters = true); + simplify = false, sparse = false, expression = Val{true}, wrap_gfw = Val{false}, + eval_expression = false, eval_module = @__MODULE__, kwargs...) + M = calculate_massmatrix(sys; simplify) + if sparse + M = SparseArrays.sparse(M) + end + J = calculate_jacobian(sys; simplify, sparse, dvs) + W = W_GAMMA * M + J + t = get_iv(sys) + if t !== nothing + wrap_code = sparse ? assert_jac_length_header(sys) : (identity, identity) + end + + p = reorder_parameters(sys, ps) + res = build_function_wrapper(sys, W, dvs, p..., W_GAMMA, t; wrap_code, + p_end = 1 + length(p), kwargs...) + return maybe_compile_function( + expression, wrap_gfw, (2, 4, is_split(sys)), res; eval_expression, eval_module) +end + +function generate_dae_jacobian(sys::System, dvs = unknowns(sys), + ps = parameters(sys; initial_parameters = true); simplify = false, sparse = false, + expression = Val{true}, wrap_gfw = Val{false}, eval_expression = false, + eval_module = @__MODULE__, kwargs...) + jac_u = calculate_jacobian(sys; simplify = simplify, sparse = sparse) + t = get_iv(sys) + derivatives = Differential(t).(unknowns(sys)) + jac_du = calculate_jacobian(sys; simplify = simplify, sparse = sparse, + dvs = derivatives) + dvs = unknowns(sys) + jac = W_GAMMA * jac_du + jac_u + p = reorder_parameters(sys, ps) + res = build_function_wrapper(sys, jac, derivatives, dvs, p..., W_GAMMA, t; + p_start = 3, p_end = 2 + length(p), kwargs...) + return maybe_compile_function( + expression, wrap_gfw, (3, 5, is_split(sys)), res; eval_expression, eval_module) +end + +function generate_history(sys::System, u0; expression = Val{true}, wrap_gfw = Val{false}, + eval_expression = false, eval_module = @__MODULE__, kwargs...) + p = reorder_parameters(sys) + res = build_function_wrapper(sys, u0, p..., get_iv(sys); expression = Val{true}, + expression_module = eval_module, p_start = 1, p_end = length(p), + similarto = typeof(u0), wrap_delays = false, kwargs...) + return maybe_compile_function( + expression, wrap_gfw, (1, 2, is_split(sys)), res; eval_expression, eval_module) +end + +function calculate_massmatrix(sys::System; simplify = false) + eqs = [eq for eq in equations(sys)] + M = zeros(length(eqs), length(eqs)) + for (i, eq) in enumerate(eqs) + if iscall(eq.lhs) && operation(eq.lhs) isa Differential + st = var_from_nested_derivative(eq.lhs)[1] + j = variable_index(sys, st) + M[i, j] = 1 + else + _iszero(eq.lhs) || + error("Only semi-explicit constant mass matrices are currently supported. Faulty equation: $eq.") + end + end + M = simplify ? simplify.(M) : M + if isdiag(M) + M = Diagonal(M) + end + # M should only contain concrete numbers + M == I ? I : M +end + +function concrete_massmatrix(M; sparse = false, u0 = nothing) + if sparse && !(u0 === nothing || M === I) + SparseArrays.sparse(M) + elseif u0 === nothing || M === I + M + elseif M isa Diagonal + Diagonal(ArrayInterface.restructure(u0, diag(M))) + else + ArrayInterface.restructure(u0 .* u0', M) + end +end + +function jacobian_sparsity(sys::System) + sparsity = torn_system_jacobian_sparsity(sys) + sparsity === nothing || return sparsity + + Symbolics.jacobian_sparsity([eq.rhs for eq in full_equations(sys)], + [dv for dv in unknowns(sys)]) +end + +function jacobian_dae_sparsity(sys::System) + J1 = jacobian_sparsity([eq.rhs for eq in full_equations(sys)], + [dv for dv in unknowns(sys)]) + derivatives = Differential(get_iv(sys)).(unknowns(sys)) + J2 = jacobian_sparsity([eq.rhs for eq in full_equations(sys)], + [dv for dv in derivatives]) + J1 + J2 +end + +function W_sparsity(sys::System) + jac_sparsity = jacobian_sparsity(sys) + (n, n) = size(jac_sparsity) + M = calculate_massmatrix(sys) + M_sparsity = M isa UniformScaling ? sparse(I(n)) : + SparseMatrixCSC{Bool, Int64}((!iszero).(M)) + jac_sparsity .| M_sparsity +end + +function calculate_W_prototype(W_sparsity; u0 = nothing, sparse = false) + sparse || return nothing + uElType = u0 === nothing ? Float64 : eltype(u0) + return similar(W_sparsity, uElType) +end + +function isautonomous(sys::System) + tgrad = calculate_tgrad(sys; simplify = true) + all(iszero, tgrad) +end + +function get_bv_solution_symbol(ns) + only(@variables BV_SOLUTION(..)[1:ns]) +end + +function get_constraint_unknown_subs!(subs::Dict, cons::Vector, stidxmap::Dict, iv, sol) + vs = vars(cons) + for v in vs + iscall(v) || continue + op = operation(v) + args = arguments(v) + issym(op) && length(args) == 1 || continue + newv = op(iv) + haskey(stidxmap, newv) || continue + subs[v] = sol(args[1])[stidxmap[newv]] + end +end + +function generate_boundary_conditions(sys::System, u0, u0_idxs, t0; expression = Val{true}, + wrap_gfw = Val{false}, eval_expression = false, eval_module = @__MODULE__, + kwargs...) + iv = get_iv(sys) + sts = unknowns(sys) + ps = parameters(sys) + np = length(ps) + ns = length(sts) + stidxmap = Dict([v => i for (i, v) in enumerate(sts)]) + pidxmap = Dict([v => i for (i, v) in enumerate(ps)]) + + # sol = get_bv_solution_symbol(ns) + + cons = [con.lhs - con.rhs for con in constraints(sys)] + # conssubs = Dict() + # get_constraint_unknown_subs!(conssubs, cons, stidxmap, iv, sol) + # cons = map(x -> fast_substitute(x, conssubs), cons) + + init_conds = Any[] + for i in u0_idxs + expr = BVP_SOLUTION(t0)[i] - u0[i] + push!(init_conds, expr) + end + + exprs = vcat(init_conds, cons) + _p = reorder_parameters(sys, ps) + + res = build_function_wrapper(sys, exprs, _p..., iv; output_type = Array, + p_start = 1, histfn = (p, t) -> BVP_SOLUTION(t), + histfn_symbolic = BVP_SOLUTION, wrap_delays = true, kwargs...) + return maybe_compile_function( + expression, wrap_gfw, (2, 3, is_split(sys)), res; eval_expression, eval_module) +end + +function generate_cost(sys::System; expression = Val{true}, wrap_gfw = Val{false}, + eval_expression = false, eval_module = @__MODULE__, kwargs...) + obj = cost(sys) + dvs = unknowns(sys) + ps = reorder_parameters(sys) + res = build_function_wrapper(sys, obj, dvs, ps...; expression = Val{true}, kwargs...) + if expression == Val{true} + return res + end + f_oop = eval_or_rgf(res; eval_expression, eval_module) + return maybe_compile_function( + expression, wrap_gfw, (2, 2, is_split(sys)), res; eval_expression, eval_module) +end + +function calculate_cost_gradient(sys::System; simplify = false) + obj = cost(sys) + dvs = unknowns(sys) + return Symbolics.gradient(obj, dvs; simplify) +end + +function generate_cost_gradient( + sys::System; expression = Val{true}, wrap_gfw = Val{false}, + eval_expression = false, eval_module = @__MODULE__, simplify = false, kwargs...) + obj = cost(sys) + dvs = unknowns(sys) + ps = reorder_parameters(sys) + exprs = calculate_cost_gradient(sys; simplify) + res = build_function_wrapper(sys, exprs, dvs, ps...; expression = Val{true}, kwargs...) + return maybe_compile_function( + expression, wrap_gfw, (2, 2, is_split(sys)), res; eval_expression, eval_module) +end + +function calculate_cost_hessian(sys::System; sparse = false, simplify = false) + obj = cost(sys) + dvs = unknowns(sys) + if sparse + exprs = Symbolics.sparsehessian(obj, dvs; simplify)::AbstractSparseArray + sparsity = similar(exprs, Float64) + else + exprs = Symbolics.hessian(obj, dvs; simplify) + end +end + +function cost_hessian_sparsity(sys::System) + return similar(calculate_cost_hessian(sys; sparse = true), Float64) +end + +function generate_cost_hessian( + sys::System; expression = Val{true}, wrap_gfw = Val{false}, + eval_expression = false, eval_module = @__MODULE__, simplify = false, + sparse = false, return_sparsity = false, kwargs...) + obj = cost(sys) + dvs = unknowns(sys) + ps = reorder_parameters(sys) + sparsity = nothing + exprs = calculate_cost_hessian(sys; sparse, simplify) + if sparse + sparsity = similar(exprs, Float64) + end + res = build_function_wrapper(sys, exprs, dvs, ps...; expression = Val{true}, kwargs...) + fn = maybe_compile_function( + expression, wrap_gfw, (2, 2, is_split(sys)), res; eval_expression, eval_module) + + return return_sparsity ? (fn, sparsity) : fn +end + +function canonical_constraints(sys::System) + return map(constraints(sys)) do cstr + Symbolics.canonical_form(cstr).lhs + end +end + +function generate_cons(sys::System; expression = Val{true}, wrap_gfw = Val{false}, + eval_expression = false, eval_module = @__MODULE__, kwargs...) + cons = canonical_constraints(sys) + dvs = unknowns(sys) + ps = reorder_parameters(sys) + res = build_function_wrapper(sys, cons, dvs, ps...; expression = Val{true}, kwargs...) + return maybe_compile_function( + expression, wrap_gfw, (2, 2, is_split(sys)), res; eval_expression, eval_module) +end + +function generate_constraint_jacobian( + sys::System; expression = Val{true}, wrap_gfw = Val{false}, + eval_expression = false, eval_module = @__MODULE__, return_sparsity = false, + simplify = false, sparse = false, kwargs...) + cons = canonical_constraints(sys) + dvs = unknowns(sys) + ps = reorder_parameters(sys) + sparsity = nothing + if sparse + jac = Symbolics.sparsejacobian(cons, dvs; simplify)::AbstractSparseArray + sparsity = similar(jac, Float64) + else + jac = Symbolics.jacobian(cons, dvs; simplify) + end + res = build_function_wrapper(sys, jac, dvs, ps...; expression = Val{true}, kwargs...) + fn = maybe_compile_function( + expression, wrap_gfw, (2, 2, is_split(sys)), res; eval_expression, eval_module) + return return_sparsity ? (fn, sparsity) : fn +end + +function generate_constraint_hessian( + sys::System; expression = Val{true}, wrap_gfw = Val{false}, + eval_expression = false, eval_module = @__MODULE__, return_sparsity = false, + simplify = false, sparse = false, kwargs...) + cons = canonical_constraints(sys) + dvs = unknowns(sys) + ps = reorder_parameters(sys) + sparsity = nothing + if sparse + hess = map(cons) do cstr + Symbolics.sparsehessian(cstr, dvs; simplify)::AbstractSparseArray + end + sparsity = similar.(hess, Float64) + else + hess = [Symbolics.hessian(cstr, dvs; simplify) for cstr in cons] + end + res = build_function_wrapper(sys, hess, dvs, ps...; expression = Val{true}, kwargs...) + fn = maybe_compile_function( + expression, wrap_gfw, (2, 2, is_split(sys)), res; eval_expression, eval_module) + return return_sparsity ? (fn, sparsity) : fn +end + +function calculate_control_jacobian(sys::AbstractSystem; + sparse = false, simplify = false) + rhs = [eq.rhs for eq in full_equations(sys)] + ctrls = unbound_inputs(sys) + + if sparse + jac = sparsejacobian(rhs, ctrls, simplify = simplify) + else + jac = jacobian(rhs, ctrls, simplify = simplify) + end + + return jac +end + +function generate_control_jacobian(sys::AbstractSystem, dvs = unknowns(sys), + ps = parameters(sys; initial_parameters = true); + expression = Val{true}, wrap_gfw = Val{false}, eval_expression = false, + eval_module = @__MODULE__, simplify = false, sparse = false, kwargs...) + jac = calculate_control_jacobian(sys; simplify = simplify, sparse = sparse) + p = reorder_parameters(sys, ps) + res = build_function_wrapper(sys, jac, dvs, p..., get_iv(sys); kwargs...) + return maybe_compile_function( + expression, wrap_gfw, (2, 3, is_split(sys)), res; eval_expression, eval_module) +end + +function generate_rate_function(js::System, rate) + p = reorder_parameters(js) + build_function_wrapper(js, rate, unknowns(js), p..., + get_iv(js), + expression = Val{true}) +end + +function generate_affect_function(js::System, affect; kwargs...) + compile_equational_affect(affect, js; checkvars = false, kwargs...) +end + +function assemble_vrj( + js, vrj, unknowntoid; eval_expression = false, eval_module = @__MODULE__) + rate = eval_or_rgf(generate_rate_function(js, vrj.rate); eval_expression, eval_module) + 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 = generate_affect_function(js, vrj.affect!; eval_expression, eval_module) + VariableRateJump(rate, affect; save_positions = vrj.save_positions) +end + +function assemble_crj( + js, crj, unknowntoid; eval_expression = false, eval_module = @__MODULE__) + rate = eval_or_rgf(generate_rate_function(js, crj.rate); eval_expression, eval_module) + 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 = generate_affect_function(js, crj.affect!; eval_expression, eval_module) + ConstantRateJump(rate, affect) +end + +# assemble a numeric MassActionJump from a MT symbolics MassActionJumps +function assemble_maj(majv::Vector{U}, unknowntoid, pmapper) where {U <: MassActionJump} + rs = [numericrstoich(maj.reactant_stoch, unknowntoid) for maj in majv] + ns = [numericnstoich(maj.net_stoch, unknowntoid) for maj in majv] + MassActionJump(rs, ns; param_mapper = pmapper, nocopy = true) +end From b174f035a324899c221e1e6073f7272caa4b75b9 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Sun, 20 Apr 2025 14:01:12 +0530 Subject: [PATCH 069/235] refactor: port `build_explicit_observed_function` to `codegen.jl` --- src/systems/codegen.jl | 168 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 168 insertions(+) diff --git a/src/systems/codegen.jl b/src/systems/codegen.jl index 00f5a714f8..acb319377a 100644 --- a/src/systems/codegen.jl +++ b/src/systems/codegen.jl @@ -560,3 +560,171 @@ function assemble_maj(majv::Vector{U}, unknowntoid, pmapper) where {U <: MassAct ns = [numericnstoich(maj.net_stoch, unknowntoid) for maj in majv] MassActionJump(rs, ns; param_mapper = pmapper, nocopy = true) end + +""" + build_explicit_observed_function(sys, ts; kwargs...) -> Function(s) + +Generates a function that computes the observed value(s) `ts` in the system `sys`, while making the assumption that there are no cycles in the equations. + +## Arguments +- `sys`: The system for which to generate the function +- `ts`: The symbolic observed values whose value should be computed + +## Keywords +- `return_inplace = false`: If true and the observed value is a vector, then return both the in place and out of place methods. +- `expression = false`: Generates a Julia `Expr`` computing the observed value if `expression` is true +- `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 +- `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. +- `mkarray`: only used if the output is an array (that is, `!isscalar(ts)` and `ts` is not a tuple, in which case the result will always be a tuple). Called as `mkarray(ts, output_type)` where `ts` are the expressions to put in the array and `output_type` is the argument of the same name passed to build_explicit_observed_function. +- `cse = true`: Whether to use Common Subexpression Elimination (CSE) to generate a more efficient function. + +## Returns + +The return value will be either: +* a single function `f_oop` if the input is a scalar or if the input is a Vector but `return_inplace` is false +* the out of place and in-place functions `(f_ip, f_oop)` if `return_inplace` is true and the input is a `Vector` + +The function(s) `f_oop` (and potentially `f_ip`) will be: +* `RuntimeGeneratedFunction`s by default, +* A Julia `Expr` if `expression` is true, +* A directly evaluated Julia function in the module `eval_module` if `eval_expression` is true and `expression` is false. + +The signatures will be of the form `g(...)` with arguments: + +- `output` for in-place functions +- `unknowns` if `param_only` is `false` +- `inputs` if `inputs` is an array of symbolic inputs that should be available in `ts` +- `p...` unconditionally; note that in the case of `MTKParameters` more than one parameters argument may be present, so it must be splatted +- `t` if the system is time-dependent; for example `NonlinearSystem` will not have `t` + +For example, a function `g(op, unknowns, p..., inputs, t)` will be the in-place function generated if `return_inplace` is true, `ts` is a vector, +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, + disturbance_argument = false, + expression = false, + eval_expression = false, + eval_module = @__MODULE__, + output_type = Array, + checkbounds = true, + ps = parameters(sys; initial_parameters = true), + return_inplace = false, + param_only = false, + op = Operator, + throw = true, + cse = true, + mkarray = nothing) + # TODO: cleanup + is_tuple = ts isa Tuple + if is_tuple + ts = collect(ts) + output_type = Tuple + end + + allsyms = all_symbols(sys) + if symbolic_type(ts) == NotSymbolic() && ts isa AbstractArray + ts = map(x -> symbol_to_symbolic(sys, x; allsyms), ts) + else + ts = symbol_to_symbolic(sys, ts; allsyms) + end + + vs = ModelingToolkit.vars(ts; op) + namespace_subs = Dict() + ns_map = Dict{Any, Any}(renamespace(sys, eq.lhs) => eq.lhs for eq in observed(sys)) + for sym in unknowns(sys) + ns_map[renamespace(sys, sym)] = sym + if iscall(sym) && operation(sym) === getindex + ns_map[renamespace(sys, arguments(sym)[1])] = arguments(sym)[1] + end + end + for sym in full_parameters(sys) + ns_map[renamespace(sys, sym)] = sym + if iscall(sym) && operation(sym) === getindex + ns_map[renamespace(sys, arguments(sym)[1])] = arguments(sym)[1] + end + end + allsyms = Set(all_symbols(sys)) + iv = has_iv(sys) ? get_iv(sys) : nothing + for var in vs + var = unwrap(var) + newvar = get(ns_map, var, nothing) + if newvar !== nothing + namespace_subs[var] = newvar + var = newvar + end + if throw && !var_in_varlist(var, allsyms, iv) + Base.throw(ArgumentError("Symbol $var is not present in the system.")) + end + end + ts = fast_substitute(ts, namespace_subs) + + obsfilter = if param_only + if is_split(sys) + let ic = get_index_cache(sys) + eq -> !(ContinuousTimeseries() in ic.observed_syms_to_timeseries[eq.lhs]) + end + else + Returns(false) + end + else + Returns(true) + end + dvs = if param_only + () + else + (unknowns(sys),) + end + if inputs === nothing + 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 + # Disturbance inputs may or may not be included as inputs, depending on disturbance_argument + ps = setdiff(ps, disturbance_inputs) + end + if disturbance_argument + disturbance_inputs = (disturbance_inputs,) + else + disturbance_inputs = () + end + ps = reorder_parameters(sys, ps) + iv = if is_time_dependent(sys) + (get_iv(sys),) + else + () + end + args = (dvs..., inputs..., ps..., iv..., disturbance_inputs...) + p_start = length(dvs) + length(inputs) + 1 + p_end = length(dvs) + length(inputs) + length(ps) + fns = build_function_wrapper( + sys, ts, args...; p_start, p_end, filter_observed = obsfilter, + output_type, mkarray, try_namespaced = true, expression = Val{true}, cse) + if fns isa Tuple + if expression + return return_inplace ? fns : fns[1] + end + oop, iip = eval_or_rgf.(fns; eval_expression, eval_module) + f = GeneratedFunctionWrapper{( + p_start + is_dde(sys), length(args) - length(ps) + 1 + is_dde(sys), is_split(sys))}( + oop, iip) + return return_inplace ? (f, f) : f + else + if expression + return fns + end + f = eval_or_rgf(fns; eval_expression, eval_module) + f = GeneratedFunctionWrapper{( + p_start + is_dde(sys), length(args) - length(ps) + 1 + is_dde(sys), is_split(sys))}( + f, nothing) + return f + end +end From 6008eba2d062f984d8fef3dc69ffa552b5f65793 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Mon, 14 Apr 2025 23:16:47 +0530 Subject: [PATCH 070/235] feat: add `@fallback_iip_specialize` fix: fix bugs in `@fallback_iip_specialize`, handle static array problems --- src/systems/problem_utils.jl | 91 ++++++++++++++++++++++++++++++++++++ 1 file changed, 91 insertions(+) diff --git a/src/systems/problem_utils.jl b/src/systems/problem_utils.jl index 5ff00b4845..39e9c8e650 100644 --- a/src/systems/problem_utils.jl +++ b/src/systems/problem_utils.jl @@ -1418,6 +1418,97 @@ function SciMLBase.detect_cycles(sys::AbstractSystem, varmap::Dict{Any, Any}, va return !isempty(cycles) end +""" + $(TYPEDSIGNATURES) + +Macro for writing problem/function constructors. Expects a function definition with type +parameters for `iip` and `specialize`. Generates fallbacks with +`specialize = SciMLBase.FullSpecialize` and `iip = true`. +""" +macro fallback_iip_specialize(ex) + @assert Meta.isexpr(ex, :function) + # fnname is ODEProblem{iip, spec}(args...) where {iip, spec} + # body is function body + fnname, body = ex.args + @assert Meta.isexpr(fnname, :where) + # fnname_call is ODEProblem{iip, spec}(args...) + # where_args are `iip, spec` + fnname_call, where_args... = fnname.args + @assert length(where_args) == 2 + iiparg, specarg = where_args + + @assert Meta.isexpr(fnname_call, :call) + # fnname_curly is ODEProblem{iip, spec} + fnname_curly, args... = fnname_call.args + # the function should have keyword arguments + @assert Meta.isexpr(args[1], :parameters) + + # arguments to call with + call_args = map(args) do arg + # keyword args are in `Expr(:parameters)` so any `Expr(:kw)` here + # are optional positional arguments. Analyze `:(f(a, b = 1; k = 1, l...))` + # to understand + Meta.isexpr(arg, :kw) && return arg.args[1] + return arg + end + call_kwargs = map(call_args[1].args) do arg + Meta.isexpr(arg, :...) && return arg + @assert Meta.isexpr(arg, :kw) + return Expr(:kw, arg.args[1], arg.args[1]) + end + call_args[1] = Expr(:parameters, call_kwargs...) + + @assert Meta.isexpr(fnname_curly, :curly) + # fnname_name is `ODEProblem` + # curly_args is `iip, spec` + fnname_name, curly_args... = fnname_curly.args + @assert curly_args == where_args + + # callexpr_iip is `ODEProblem{iip, FullSpecialize}(call_args...)` + callexpr_iip = Expr( + :call, Expr(:curly, fnname_name, curly_args[1], SciMLBase.FullSpecialize), call_args...) + # `ODEProblem{iip}` + fnname_iip = Expr(:curly, fnname_name, curly_args[1]) + # `ODEProblem{iip}(args...)` + fncall_iip = Expr(:call, fnname_iip, args...) + # ODEProblem{iip}(args...) where {iip} + fnwhere_iip = Expr(:where, fncall_iip, where_args[1]) + fn_iip = Expr(:function, fnwhere_iip, callexpr_iip) + + # `ODEProblem{true}(call_args...)` + callexpr_base = Expr(:call, Expr(:curly, fnname_name, true), call_args...) + # `ODEProblem(args...)` + fncall_base = Expr(:call, fnname_name, args...) + fn_base = Expr(:function, fncall_base, callexpr_base) + + # Handle case when this is a problem constructor and `u0map` is a `StaticArray`, + # where `iip` should default to `false`. + fn_sarr = nothing + if occursin("Problem", string(fnname_name)) + # args should at least contain an argument for the `u0map` + @assert length(args) > 3 + u0_arg = args[3] + # should not have a type-annotation + @assert !Meta.isexpr(u0_arg, :(::)) + if Meta.isexpr(u0_arg, :kw) + argname, default = u0_arg.args + u0_arg = Expr(:kw, Expr(:(::), argname, StaticArray), default) + else + u0_arg = Expr(:(::), u0_arg, StaticArray) + end + + callexpr_sarr = Expr(:call, Expr(:curly, fnname_name, false), call_args...) + fncall_sarr = Expr(:call, fnname_name, args[1], args[2], u0_arg, args[4:end]...) + fn_sarr = Expr(:function, fncall_sarr, callexpr_sarr) + end + return quote + $fn_base + $fn_sarr + $fn_iip + Base.@__doc__ $ex + end |> esc +end + ############## # Legacy functions for backward compatibility ############## From 67a598b1bfe09b99e04700a4f4d3d3459639e4e5 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Mon, 14 Apr 2025 23:16:58 +0530 Subject: [PATCH 071/235] feat: add `check_compatible_system` --- src/ModelingToolkit.jl | 1 + src/problems/compatibility.jl | 171 ++++++++++++++++++++++++++++++++++ 2 files changed, 172 insertions(+) create mode 100644 src/problems/compatibility.jl diff --git a/src/ModelingToolkit.jl b/src/ModelingToolkit.jl index 352ac8b68f..c54c4474ba 100644 --- a/src/ModelingToolkit.jl +++ b/src/ModelingToolkit.jl @@ -167,6 +167,7 @@ include("systems/codegen.jl") include("systems/problem_utils.jl") include("linearization.jl") +include("problems/compatibility.jl") include("systems/optimization/modelingtoolkitize.jl") include("systems/nonlinear/homotopy_continuation.jl") diff --git a/src/problems/compatibility.jl b/src/problems/compatibility.jl new file mode 100644 index 0000000000..84c2eefc3f --- /dev/null +++ b/src/problems/compatibility.jl @@ -0,0 +1,171 @@ +""" + function check_compatible_system(T::Type, sys::System) + +Check if `sys` can be used to construct a problem/function of type `T`. +""" +function check_compatible_system end + +struct SystemCompatibilityError <: Exception + msg::String +end + +function Base.showerror(io::IO, err::SystemCompatibilityError) + println(io, err.msg) + println(io) + print(io, "To disable this check, pass `check_compatibility = false`.") +end + +function check_time_dependent(sys::System, T) + if !is_time_dependent(sys) + throw(SystemCompatibilityError(""" + `$T` requires a time-dependent system. + """)) + end +end + +function check_time_independent(sys::System, T) + if is_time_dependent(sys) + throw(SystemCompatibilityError(""" + `$T` requires a time-independent system. + """)) + end +end + +function check_is_dde(sys::System) + altT = get_noise_eqs(sys) === nothing ? ODEProblem : SDEProblem + if !is_dde(sys) + throw(SystemCompatibilityError(""" + The system does not have delays. Consider an `$altT` instead. + """)) + end +end + +function check_not_dde(sys::System) + altT = get_noise_eqs(sys) === nothing ? DDEProblem : SDDEProblem + if is_dde(sys) + throw(SystemCompatibilityError(""" + The system has delays. Consider a `$altT` instead. + """)) + end +end + +function check_no_cost(sys::System, T) + cost = ModelingToolkit.cost(sys) + if !_iszero(cost) + throw(SystemCompatibilityError(""" + `$T` will not optimize solutions of systems that have associated cost \ + functions. Solvers for optimal control problems are forthcoming. + """)) + end +end + +function check_has_cost(sys::System, T) + cost = ModelingToolkit.cost(sys) + if _iszero(cost) + throw(SystemCompatibilityError(""" + A system without cost cannot be used to construct a `$T`. + """)) + end +end + +function check_no_constraints(sys::System, T) + if !isempty(constraints(sys)) + throw(SystemCompatibilityError(""" + A system with constraints cannot be used to construct a `$T`. + """)) + end +end + +function check_has_constraints(sys::System, T) + if isempty(constraints(sys)) + throw(SystemCompatibilityError(""" + A system without constraints cannot be used to construct a `$T`. Consider an \ + `ODEProblem` instead. + """)) + end +end + +function check_no_jumps(sys::System, T) + if !isempty(jumps(sys)) + throw(SystemCompatibilityError(""" + A system with jumps cannot be used to construct a `$T`. Consider a \ + `JumpProblem` instead. + """)) + end +end + +function check_has_jumps(sys::System, T) + if isempty(jumps(sys)) + throw(SystemCompatibilityError("`$T` requires a system with jumps.")) + end +end + +function check_no_noise(sys::System, T) + altT = is_dde(sys) ? SDDEProblem : SDEProblem + if get_noise_eqs(sys) !== nothing + throw(SystemCompatibilityError(""" + A system with noise cannot be used to construct a `$T`. Consider an \ + `$altT` instead. + """)) + end +end + +function check_has_noise(sys::System, T) + altT = is_dde(sys) ? DDEProblem : ODEProblem + if get_noise_eqs(sys) === nothing + msg = """ + A system without noise cannot be used to construct a `$T`. Consider an \ + `$altT` instead. + """ + if !isempty(brownians(sys)) + msg = """ + Systems constructed by defining Brownian variables with `@brownian` must be \ + simplified by calling `structural_simplify` before a `$T` can be constructed. + """ + end + throw(SystemCompatibilityError(msg)) + end +end + +function check_is_discrete(sys::System, T) + if !is_discrete_system(sys) + throw(SystemCompatibilityError(""" + `$T` expects a discrete system. Consider an `ODEProblem` instead. If your system \ + is discrete, ensure `structural_simplify` has been run on it. + """)) + end +end + +function check_is_continuous(sys::System, T) + altT = has_alg_equations(sys) ? ImplicitDiscreteProblem : DiscreteProblem + if is_discrete_system(sys) + throw(SystemCompatibilityError(""" + A discrete system cannot be used to construct a `$T`. Consider a `$altT` instead. + """)) + end +end + +function check_is_explicit(sys::System, T, altT) + if has_alg_equations(sys) + throw(SystemCompatibilityError(""" + `$T` expects an explicit system. Consider a `$altT` instead. + """)) + end +end + +function check_is_implicit(sys::System, T, altT) + if !has_alg_equations(sys) + throw(SystemCompatibilityError(""" + `$T` expects an implicit system. Consider a `$altT` instead. + """)) + end +end + +function check_no_equations(sys::System, T) + if !isempty(equations(sys)) + throw(SystemCompatibilityError(""" + A system with equations cannot be used to construct a `$T`. Consider turning the + equations into constraints instead. + """)) + end +end From 4a96614f761c13579bcf0084c29ab20de485b523 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Mon, 14 Apr 2025 23:53:12 +0530 Subject: [PATCH 072/235] feat: implement `generate_initializesystem` for `System` --- src/systems/nonlinear/initializesystem.jl | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/systems/nonlinear/initializesystem.jl b/src/systems/nonlinear/initializesystem.jl index d2a988dc07..492b52489d 100644 --- a/src/systems/nonlinear/initializesystem.jl +++ b/src/systems/nonlinear/initializesystem.jl @@ -1,9 +1,17 @@ +function generate_initializesystem(sys::AbstractSystem; kwargs...) + if is_time_dependent(sys) + generate_initializesystem_timevarying(sys; kwargs...) + else + generate_initializesystem_timeindependent(sys; kwargs...) + end +end + """ $(TYPEDSIGNATURES) Generate `NonlinearSystem` which initializes a problem from specified initial conditions of an `AbstractTimeDependentSystem`. """ -function generate_initializesystem(sys::AbstractTimeDependentSystem; +function generate_initializesystem_timevarying(sys::AbstractSystem; u0map = Dict(), pmap = Dict(), initialization_eqs = [], @@ -160,7 +168,7 @@ $(TYPEDSIGNATURES) Generate `NonlinearSystem` which initializes a problem from specified initial conditions of an `AbstractTimeDependentSystem`. """ -function generate_initializesystem(sys::AbstractTimeIndependentSystem; +function generate_initializesystem_timeindependent(sys::AbstractSystem; u0map = Dict(), pmap = Dict(), initialization_eqs = [], From 97a6cad07b67542de3e5b3abe6fab2bbdfc339c6 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Wed, 16 Apr 2025 17:07:15 +0530 Subject: [PATCH 073/235] refactor: remove `generate_factorized_W` --- src/systems/abstractsystem.jl | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/src/systems/abstractsystem.jl b/src/systems/abstractsystem.jl index a5567f7743..a1a576f639 100644 --- a/src/systems/abstractsystem.jl +++ b/src/systems/abstractsystem.jl @@ -113,17 +113,6 @@ the arguments to the internal [`build_function`](@ref) call. """ function generate_jacobian end -""" -```julia -generate_factorized_W(sys::AbstractSystem, dvs = unknowns(sys), ps = parameters(sys), - expression = Val{true}; sparse = false, kwargs...) -``` - -Generates a function for the factorized W matrix of a system. Extra arguments control -the arguments to the internal [`build_function`](@ref) call. -""" -function generate_factorized_W end - """ ```julia generate_hessian(sys::AbstractSystem, dvs = unknowns(sys), ps = parameters(sys), From 5ee21c34fc9d76ddff980de159fb0426f2769892 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Wed, 16 Apr 2025 19:58:36 +0530 Subject: [PATCH 074/235] fix: fix `remake` for `IntervalNonlinearProblem` --- src/systems/nonlinear/initializesystem.jl | 1 + 1 file changed, 1 insertion(+) diff --git a/src/systems/nonlinear/initializesystem.jl b/src/systems/nonlinear/initializesystem.jl index 492b52489d..056d35df37 100644 --- a/src/systems/nonlinear/initializesystem.jl +++ b/src/systems/nonlinear/initializesystem.jl @@ -629,6 +629,7 @@ end function SciMLBase.late_binding_update_u0_p( prob, sys::AbstractSystem, u0, p, t0, newu0, newp) supports_initialization(sys) || return newu0, newp + prob isa IntervalNonlinearProblem && return newu0, newp initdata = prob.f.initialization_data meta = initdata === nothing ? nothing : initdata.metadata From 304f6408ce8cafa1dfed45cba570b96212788e26 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Wed, 14 May 2025 11:49:57 +0530 Subject: [PATCH 075/235] feat: add `replace` kwarg to `add_toterms!` --- src/systems/problem_utils.jl | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/systems/problem_utils.jl b/src/systems/problem_utils.jl index 39e9c8e650..6f93f14a2d 100644 --- a/src/systems/problem_utils.jl +++ b/src/systems/problem_utils.jl @@ -32,11 +32,14 @@ is_split(sys::AbstractSystem) = has_index_cache(sys) && get_index_cache(sys) !== """ $(TYPEDSIGNATURES) -Given a variable-value mapping, add mappings for the `toterm` of each of the keys. +Given a variable-value mapping, add mappings for the `toterm` of each of the keys. `replace` controls whether +the old value should be removed. """ -function add_toterms!(varmap::AbstractDict; toterm = default_toterm) +function add_toterms!(varmap::AbstractDict; toterm = default_toterm, replace = false) for k in collect(keys(varmap)) - varmap[toterm(k)] = varmap[k] + ttk = toterm(k) + varmap[ttk] = varmap[k] + !isequal(k, ttk) && replace && delete!(varmap, k) end return nothing end @@ -46,9 +49,9 @@ end Out-of-place version of [`add_toterms!`](@ref). """ -function add_toterms(varmap::AbstractDict; toterm = default_toterm) +function add_toterms(varmap::AbstractDict; kwargs...) cp = copy(varmap) - add_toterms!(cp; toterm) + add_toterms!(cp; kwargs...) return cp end From 4decfa88c0584a691f8bb2bf36e1a4ccc727095e Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Wed, 14 May 2025 12:08:40 +0530 Subject: [PATCH 076/235] refactor: centralize problem kwargs handling --- src/systems/problem_utils.jl | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/systems/problem_utils.jl b/src/systems/problem_utils.jl index 6f93f14a2d..63b8418257 100644 --- a/src/systems/problem_utils.jl +++ b/src/systems/problem_utils.jl @@ -1421,6 +1421,26 @@ function SciMLBase.detect_cycles(sys::AbstractSystem, varmap::Dict{Any, Any}, va return !isempty(cycles) end +function process_kwargs(sys::System; callback = nothing, eval_expression = false, + eval_module = @__MODULE__, kwargs...) + kwargs = filter_kwargs(kwargs) + kwargs1 = (;) + + if is_time_dependent(sys) + cbs = process_events(sys; callback, eval_expression, eval_module, kwargs...) + if cbs !== nothing + kwargs1 = merge(kwargs1, (callback = cbs,)) + end + + tstops = SymbolicTstops(sys; eval_expression, eval_module) + if tstops !== nothing + kwargs1 = merge(kwargs1, (; tstops)) + end + end + + return merge(kwargs1, kwargs) +end + """ $(TYPEDSIGNATURES) From 8d313bfc1bb513f4a8791e9de7e9d0d0c43a49a3 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Mon, 21 Apr 2025 12:44:46 +0530 Subject: [PATCH 077/235] refactor: move `filter_kwargs` and `SymbolicTstops` to `problem_utils.jl` --- src/systems/problem_utils.jl | 41 ++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/src/systems/problem_utils.jl b/src/systems/problem_utils.jl index 63b8418257..a224a10df0 100644 --- a/src/systems/problem_utils.jl +++ b/src/systems/problem_utils.jl @@ -1441,6 +1441,47 @@ function process_kwargs(sys::System; callback = nothing, eval_expression = false return merge(kwargs1, kwargs) end +function filter_kwargs(kwargs) + kwargs = Dict(kwargs) + for key in keys(kwargs) + key in DiffEqBase.allowedkeywords || delete!(kwargs, key) + end + pairs(NamedTuple(kwargs)) +end + +struct SymbolicTstops{F} + fn::F +end + +function (st::SymbolicTstops)(p, tspan) + unique!(sort!(reduce(vcat, st.fn(p, tspan...)))) +end + +function SymbolicTstops( + sys::AbstractSystem; eval_expression = false, eval_module = @__MODULE__) + tstops = symbolic_tstops(sys) + isempty(tstops) && return nothing + t0 = gensym(:t0) + t1 = gensym(:t1) + tstops = map(tstops) do val + if is_array_of_symbolics(val) || val isa AbstractArray + collect(val) + else + term(:, t0, unwrap(val), t1; type = AbstractArray{Real}) + end + end + rps = reorder_parameters(sys) + tstops, _ = build_function_wrapper(sys, tstops, + rps..., + t0, + t1; + expression = Val{true}, + p_start = 1, p_end = length(rps), add_observed = false, force_SA = true) + tstops = eval_or_rgf(tstops; eval_expression, eval_module) + tstops = GeneratedFunctionWrapper{(1, 3, is_split(sys))}(tstops, nothing) + return SymbolicTstops(tstops) +end + """ $(TYPEDSIGNATURES) From d469f7c01a1234038376f38edd31cda57ce708c3 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Mon, 28 Apr 2025 15:09:15 +0530 Subject: [PATCH 078/235] feat: support returning `Expr` from `SymbolicTstops` and `ObservedFunctionCache` --- src/systems/abstractsystem.jl | 11 ++++++++--- src/systems/problem_utils.jl | 14 ++++++++++---- 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/src/systems/abstractsystem.jl b/src/systems/abstractsystem.jl index a1a576f639..2adc879e94 100644 --- a/src/systems/abstractsystem.jl +++ b/src/systems/abstractsystem.jl @@ -1887,10 +1887,15 @@ struct ObservedFunctionCache{S} end function ObservedFunctionCache( - sys; steady_state = false, eval_expression = false, + sys; expression = Val{false}, steady_state = false, eval_expression = false, eval_module = @__MODULE__, checkbounds = true, cse = true) - return ObservedFunctionCache( - sys, Dict(), steady_state, eval_expression, eval_module, checkbounds, cse) + if expression == Val{true} + :($ObservedFunctionCache($sys, Dict(), $steady_state, $eval_expression, + $eval_module, $checkbounds, $cse)) + else + ObservedFunctionCache( + sys, Dict(), steady_state, eval_expression, eval_module, checkbounds, cse) + end end # This is hit because ensemble problems do a deepcopy diff --git a/src/systems/problem_utils.jl b/src/systems/problem_utils.jl index a224a10df0..75342938bb 100644 --- a/src/systems/problem_utils.jl +++ b/src/systems/problem_utils.jl @@ -1458,7 +1458,8 @@ function (st::SymbolicTstops)(p, tspan) end function SymbolicTstops( - sys::AbstractSystem; eval_expression = false, eval_module = @__MODULE__) + sys::AbstractSystem; expression = Val{false}, eval_expression = false, + eval_module = @__MODULE__) tstops = symbolic_tstops(sys) isempty(tstops) && return nothing t0 = gensym(:t0) @@ -1477,9 +1478,14 @@ function SymbolicTstops( t1; expression = Val{true}, p_start = 1, p_end = length(rps), add_observed = false, force_SA = true) - tstops = eval_or_rgf(tstops; eval_expression, eval_module) - tstops = GeneratedFunctionWrapper{(1, 3, is_split(sys))}(tstops, nothing) - return SymbolicTstops(tstops) + tstops = GeneratedFunctionWrapper{(1, 3, is_split(sys))}( + expression, tstops, nothing; eval_expression, eval_module) + + if expression == Val{true} + return :($SymbolicTstops($tstops)) + else + return SymbolicTstops(tstops) + end end """ From 8b9cb5a05190c334e6da2d0dc57ecc0df123d99a Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Wed, 14 May 2025 12:54:51 +0530 Subject: [PATCH 079/235] refactor: pass `u0` and `p` as kwargs in `process_SciMLProblem` feat: handle `expression = Val{true}` in `process_kwargs` --- src/systems/problem_utils.jl | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/systems/problem_utils.jl b/src/systems/problem_utils.jl index 75342938bb..c366bd81b9 100644 --- a/src/systems/problem_utils.jl +++ b/src/systems/problem_utils.jl @@ -1371,7 +1371,7 @@ function process_SciMLProblem( length(eqs), u0, p)))) end - f = constructor(sys, dvs, ps, u0; p = p, + f = constructor(sys; u0 = u0, p = p, eval_expression = eval_expression, eval_module = eval_module, kwargs...) @@ -1421,18 +1421,20 @@ function SciMLBase.detect_cycles(sys::AbstractSystem, varmap::Dict{Any, Any}, va return !isempty(cycles) end -function process_kwargs(sys::System; callback = nothing, eval_expression = false, - eval_module = @__MODULE__, kwargs...) +function process_kwargs(sys::System; expression = Val{false}, callback = nothing, + eval_expression = false, eval_module = @__MODULE__, kwargs...) kwargs = filter_kwargs(kwargs) kwargs1 = (;) if is_time_dependent(sys) - cbs = process_events(sys; callback, eval_expression, eval_module, kwargs...) - if cbs !== nothing - kwargs1 = merge(kwargs1, (callback = cbs,)) + if expression == Val{false} + cbs = process_events(sys; callback, eval_expression, eval_module, kwargs...) + if cbs !== nothing + kwargs1 = merge(kwargs1, (callback = cbs,)) + end end - tstops = SymbolicTstops(sys; eval_expression, eval_module) + tstops = SymbolicTstops(sys; expression, eval_expression, eval_module) if tstops !== nothing kwargs1 = merge(kwargs1, (; tstops)) end From 4c5841aa5d7efe2703563b068f383010eb8154f7 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Mon, 28 Apr 2025 18:43:39 +0530 Subject: [PATCH 080/235] feat: add `maybe_codegen_scimlfn` and `maybe_codegen_scimlproblem` --- src/systems/problem_utils.jl | 94 ++++++++++++++++++++++++++++++++++++ 1 file changed, 94 insertions(+) diff --git a/src/systems/problem_utils.jl b/src/systems/problem_utils.jl index c366bd81b9..3bc8d9d8ac 100644 --- a/src/systems/problem_utils.jl +++ b/src/systems/problem_utils.jl @@ -1581,6 +1581,100 @@ macro fallback_iip_specialize(ex) end |> esc end +""" + $(TYPEDSIGNATURES) + +Turn key-value pairs in `kws` into assignments and appent them to `block.args`. `head` is +the head of the `Expr` used to create the assignment. `filter` is a function that takes the +key and returns whether or not to include it in the assignments. +""" +function namedtuple_to_assignments!( + block, kws::NamedTuple; head = :(=), filter = Returns(true)) + for (k, v) in pairs(kws) + filter(k) || continue + push!(block.args, Expr(head, k, v)) + end +end + +""" + $(TYPEDSIGNATURES) + +Build an expression that constructs SciMLFunction `T`. `args` is a `NamedTuple` mapping +names of positional arguments to `T` to their (expression) values. `kwargs` are parsed +as keyword arguments to the constructor. +""" +function build_scimlfn_expr(T, args::NamedTuple; kwargs...) + kwargs = NamedTuple(kwargs) + let_args = Expr(:block) + namedtuple_to_assignments!(let_args, args) + + kwexpr = Expr(:parameters) + # don't include initialization data in the generated expression + filter = !isequal(:initialization_data) + namedtuple_to_assignments!(let_args, kwargs; filter = filter) + namedtuple_to_assignments!(kwexpr, kwargs; head = :kw, filter) + let_body = Expr(:call, T, kwexpr, keys(args)...) + return Expr(:let, let_args, let_body) +end + +""" + $(TYPEDSIGNATURES) + +Build an expression that constructs SciMLProblem `T`. `args` is a `NamedTuple` mapping +names of positional arguments to `T` to their (expression) values. `kwargs` are parsed +as keyword arguments to the constructor. +""" +function build_scimlproblem_expr(T, args::NamedTuple; kwargs...) + kwargs = NamedTuple(kwargs) + let_args = Expr(:block) + namedtuple_to_assignments!(let_args, args) + + kwexpr = Expr(:parameters) + namedtuple_to_assignments!(let_args, kwargs) + namedtuple_to_assignments!(kwexpr, kwargs; head = :kw) + let_body = Expr(:call, remake, Expr(:call, T, kwexpr, keys(args)...)) + return Expr(:let, let_args, let_body) +end + +""" + $(TYPEDSIGNATURES) + +Return an expression constructing SciMLFunction `T` with positional arguments `args` +and keywords `kwargs`. +""" +function maybe_codegen_scimlfn(::Type{Val{true}}, T, args::NamedTuple; kwargs...) + build_scimlfn_expr(T, args; kwargs...) +end + +""" + $(TYPEDSIGNATURES) + +Construct SciMLFunction `T` with positional arguments `args` and keywords `kwargs`. +""" +function maybe_codegen_scimlfn(::Type{Val{false}}, T, args::NamedTuple; kwargs...) + T(args...; kwargs...) +end + +""" + $(TYPEDSIGNATURES) + +Return an expression constructing SciMLProblem `T` with positional arguments `args` +and keywords `kwargs`. +""" +function maybe_codegen_scimlproblem(::Type{Val{true}}, T, args::NamedTuple; kwargs...) + build_scimlproblem_expr(T, args; kwargs...) +end + +""" + $(TYPEDSIGNATURES) + +Construct SciMLProblem `T` with positional arguments `args` and keywords `kwargs`. +""" +function maybe_codegen_scimlproblem(::Type{Val{false}}, T, args::NamedTuple; kwargs...) + # Call `remake` so it runs initialization if it is trivial + remake(T(args...; kwargs...)) +end + ############## # Legacy functions for backward compatibility ############## From 9d5ebdad8a5c40902c612f7198c56523b1c8e32e Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Mon, 14 Apr 2025 23:17:20 +0530 Subject: [PATCH 081/235] feat: implement `ODEProblem` and `ODEFunction` for `System` refactor: use new compiled `generate_*` functions --- src/ModelingToolkit.jl | 1 + src/problems/odeproblem.jl | 94 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 95 insertions(+) create mode 100644 src/problems/odeproblem.jl diff --git a/src/ModelingToolkit.jl b/src/ModelingToolkit.jl index c54c4474ba..d32d8f7f2d 100644 --- a/src/ModelingToolkit.jl +++ b/src/ModelingToolkit.jl @@ -168,6 +168,7 @@ include("systems/problem_utils.jl") include("linearization.jl") include("problems/compatibility.jl") +include("problems/odeproblem.jl") include("systems/optimization/modelingtoolkitize.jl") include("systems/nonlinear/homotopy_continuation.jl") diff --git a/src/problems/odeproblem.jl b/src/problems/odeproblem.jl new file mode 100644 index 0000000000..42e49a0aea --- /dev/null +++ b/src/problems/odeproblem.jl @@ -0,0 +1,94 @@ +@fallback_iip_specialize function SciMLBase.ODEFunction{iip, spec}( + sys::System; u0 = nothing, p = nothing, tgrad = false, jac = false, + t = nothing, eval_expression = false, eval_module = @__MODULE__, sparse = false, + steady_state = false, checkbounds = false, sparsity = false, analytic = nothing, + simplify = false, cse = true, initialization_data = nothing, expression = Val{false}, + check_compatibility = true, kwargs...) where {iip, spec} + check_complete(sys, ODEFunction) + check_compatibility && check_compatible_system(ODEFunction, sys) + + dvs = unknowns(sys) + ps = parameters(sys) + f = generate_rhs(sys, dvs, ps; expression, wrap_gfw = Val{true}, + eval_expression, eval_module, checkbounds = checkbounds, cse, + kwargs...) + + if spec === SciMLBase.FunctionWrapperSpecialize && iip + if u0 === nothing || p === nothing || t === nothing + error("u0, p, and t must be specified for FunctionWrapperSpecialize on ODEFunction.") + end + if expression == Val{true} + f = :($(SciMLBase.wrapfun_iip)($f, ($u0, $u0, $p, $t))) + else + f = SciMLBase.wrapfun_iip(f, (u0, u0, p, t)) + end + end + + if tgrad + _tgrad = generate_tgrad( + sys, dvs, ps; expression, wrap_gfw = Val{true}, + simplify, cse, eval_expression, eval_module, checkbounds, kwargs...) + else + _tgrad = nothing + end + + if jac + _jac = generate_jacobian( + sys; expression, wrap_gfw = Val{true}, + simplify, sparse, cse, eval_expression, eval_module, checkbounds, kwargs...) + else + _jac = nothing + end + + M = calculate_massmatrix(sys) + _M = concrete_massmatrix(M; sparse, u0) + + observedfun = ObservedFunctionCache( + sys; expression, steady_state, eval_expression, eval_module, checkbounds, cse) + + _W_sparsity = W_sparsity(sys) + W_prototype = calculate_W_prototype(_W_sparsity; u0, sparse) + + args = (; f) + kwargs = (; + sys = sys, + jac = _jac, + tgrad = _tgrad, + mass_matrix = _M, + jac_prototype = W_prototype, + observed = observedfun, + sparsity = sparsity ? _W_sparsity : nothing, + analytic = analytic, + initialization_data) + + maybe_codegen_scimlfn(expression, ODEFunction{iip, spec}, args; kwargs...) +end + +@fallback_iip_specialize function SciMLBase.ODEProblem{iip, spec}( + sys::System, u0map, tspan, parammap = SciMLBase.NullParameters(); + callback = nothing, check_length = true, eval_expression = false, + expression = Val{false}, eval_module = @__MODULE__, check_compatibility = true, + kwargs...) where {iip, spec} + check_complete(sys, ODEProblem) + check_compatibility && check_compatible_system(ODEProblem, sys) + + f, u0, p = process_SciMLProblem(ODEFunction{iip, spec}, sys, u0map, parammap; + t = tspan !== nothing ? tspan[1] : tspan, check_length, eval_expression, + eval_module, expression, check_compatibility, kwargs...) + + kwargs = process_kwargs( + sys; expression, callback, eval_expression, eval_module, kwargs...) + + args = (; f, u0, tspan, p, ptype = StandardODEProblem()) + maybe_codegen_scimlproblem(expression, ODEProblem{iip}, args; kwargs...) +end + +function check_compatible_system(T::Union{Type{ODEFunction}, Type{ODEProblem}}, sys::System) + check_time_dependent(sys, T) + check_not_dde(sys, T) + check_no_cost(sys, T) + check_no_constraints(sys, T) + check_no_jumps(sys, T) + check_no_noise(sys, T) + check_is_continuous(sys, T) +end From 556c0a7761def12d9ef23cbd621c35b83403c779 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Wed, 14 May 2025 11:46:15 +0530 Subject: [PATCH 082/235] feat: implement `DDEFunction`, `DDEProblem` for `System` --- src/ModelingToolkit.jl | 1 + src/problems/ddeproblem.jl | 86 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 87 insertions(+) create mode 100644 src/problems/ddeproblem.jl diff --git a/src/ModelingToolkit.jl b/src/ModelingToolkit.jl index d32d8f7f2d..26a0e16edc 100644 --- a/src/ModelingToolkit.jl +++ b/src/ModelingToolkit.jl @@ -169,6 +169,7 @@ include("linearization.jl") include("problems/compatibility.jl") include("problems/odeproblem.jl") +include("problems/ddeproblem.jl") include("systems/optimization/modelingtoolkitize.jl") include("systems/nonlinear/homotopy_continuation.jl") diff --git a/src/problems/ddeproblem.jl b/src/problems/ddeproblem.jl new file mode 100644 index 0000000000..dfc660effd --- /dev/null +++ b/src/problems/ddeproblem.jl @@ -0,0 +1,86 @@ +@fallback_iip_specialize function SciMLBase.DDEFunction{iip, spec}( + sys::System; u0 = nothing, p = nothing, eval_expression = false, + eval_module = @__MODULE__, expression = Val{false}, checkbounds = false, + initialization_data = nothing, cse = true, check_compatibility = true, + sparse = false, simplify = false, analytic = nothing, kwargs...) where {iip, spec} + check_complete(sys, DDEFunction) + check_compatibility && check_compatible_system(DDEFunction, sys) + + dvs = unknowns(sys) + ps = parameters(sys) + + f = generate_rhs(sys, dvs, ps; expression, wrap_gfw = Val{true}, + eval_expression, eval_module, checkbounds = checkbounds, cse, + kwargs...) + + if spec === SciMLBase.FunctionWrapperSpecialize && iip + if u0 === nothing || p === nothing || t === nothing + error("u0, p, and t must be specified for FunctionWrapperSpecialize on DDEFunction.") + end + if expression == Val{true} + f = :($(SciMLBase.wrapfun_iip)($f, ($u0, $u0, $p, $t))) + else + f = SciMLBase.wrapfun_iip(f, (u0, u0, p, t)) + end + end + + M = calculate_massmatrix(sys) + _M = concrete_massmatrix(M; sparse, u0) + + observedfun = ObservedFunctionCache( + sys; expression, eval_expression, eval_module, checkbounds, cse) + + kwargs = (; + sys = sys, + mass_matrix = _M, + observed = observedfun, + analytic = analytic, + initialization_data) + args = (; f) + + return maybe_codegen_scimlfn(expression, DDEFunction{iip, spec}, args; kwargs...) +end + +@fallback_iip_specialize function SciMLBase.DDEProblem{iip, spec}( + sys::System, u0map, tspan, parammap = SciMLBase.NullParameters(); + callback = nothing, check_length = true, cse = true, checkbounds = false, + eval_expression = false, eval_module = @__MODULE__, check_compatibility = true, + u0_constructor = identity, expression = Val{false}, kwargs...) where {iip, spec} + check_complete(sys, DDEProblem) + check_compatibility && check_compatible_system(DDEProblem, sys) + + f, u0, p = process_SciMLProblem(DDEFunction{iip, spec}, sys, u0map, parammap; + t = tspan !== nothing ? tspan[1] : tspan, check_length, cse, checkbounds, + eval_expression, eval_module, check_compatibility, symbolic_u0 = true, + expression, u0_constructor, kwargs...) + + h = generate_history( + sys, u0; expression, wrap_gfw = Val{true}, cse, eval_expression, eval_module, + checkbounds) + + if expression == Val{true} + if u0 !== nothing + u0 = :($u0_constructor($map($float, h(p, tspan[1])))) + end + else + if u0 !== nothing + u0 = u0_constructor(float.(h(p, tspan[1]))) + end + end + + kwargs = process_kwargs( + sys; expression, callback, eval_expression, eval_module, kwargs...) + args = (; f, u0, h, tspan, p) + + return maybe_codegen_scimlproblem(expression, DDEProblem{iip}, args; kwargs...) +end + +function check_compatible_system(T::Union{Type{DDEFunction}, Type{DDEProblem}}, sys::System) + check_time_dependent(sys, T) + check_is_dde(sys) + check_no_cost(sys, T) + check_no_constraints(sys, T) + check_no_jumps(sys, T) + check_no_noise(sys, T) + check_is_continuous(sys, T) +end From 78b2f1ce9ecec14c9dc6324e4ed05815d08e13fa Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Wed, 14 May 2025 11:47:01 +0530 Subject: [PATCH 083/235] feat: implement `DAEProblem` and `DAEFunction` for `System` --- src/ModelingToolkit.jl | 1 + src/problems/daeproblem.jl | 87 ++++++++++++++++++++++++++++++++++++++ src/problems/odeproblem.jl | 6 ++- 3 files changed, 92 insertions(+), 2 deletions(-) create mode 100644 src/problems/daeproblem.jl diff --git a/src/ModelingToolkit.jl b/src/ModelingToolkit.jl index 26a0e16edc..cee03fb5ff 100644 --- a/src/ModelingToolkit.jl +++ b/src/ModelingToolkit.jl @@ -170,6 +170,7 @@ include("linearization.jl") include("problems/compatibility.jl") include("problems/odeproblem.jl") include("problems/ddeproblem.jl") +include("problems/daeproblem.jl") include("systems/optimization/modelingtoolkitize.jl") include("systems/nonlinear/homotopy_continuation.jl") diff --git a/src/problems/daeproblem.jl b/src/problems/daeproblem.jl new file mode 100644 index 0000000000..d1f8893cd5 --- /dev/null +++ b/src/problems/daeproblem.jl @@ -0,0 +1,87 @@ +@fallback_iip_specialize function SciMLBase.DAEFunction{iip, spec}( + sys::System; u0 = nothing, p = nothing, tgrad = false, jac = false, + t = nothing, eval_expression = false, eval_module = @__MODULE__, sparse = false, + steady_state = false, checkbounds = false, sparsity = false, analytic = nothing, + simplify = false, cse = true, initialization_data = nothing, + expression = Val{false}, check_compatibility = true, kwargs...) where {iip, spec} + check_complete(sys, DAEFunction) + check_compatibility && check_compatible_system(DAEFunction, sys) + + dvs = unknowns(sys) + ps = parameters(sys) + f = generate_rhs(sys, dvs, ps; expression, wrap_gfw = Val{true}, + implicit_dae = true, eval_expression, eval_module, checkbounds = checkbounds, cse, + kwargs...) + + if spec === SciMLBase.FunctionWrapperSpecialize && iip + if u0 === nothing || p === nothing || t === nothing + error("u0, p, and t must be specified for FunctionWrapperSpecialize on ODEFunction.") + end + if expression == Val{true} + f = :($(SciMLBase.wrapfun_iip)($f, ($u0, $u0, $u0, $p, $t))) + else + f = SciMLBase.wrapfun_iip(f, (u0, u0, u0, p, t)) + end + end + + if jac + _jac = generate_dae_jacobian(sys, dvs, ps; expression, + wrap_gfw = Val{true}, simplify, sparse, cse, eval_expression, eval_module, + checkbounds, kwargs...) + else + _jac = nothing + end + + observedfun = ObservedFunctionCache( + sys; expression, steady_state, eval_expression, eval_module, checkbounds, cse) + + jac_prototype = if sparse + uElType = u0 === nothing ? Float64 : eltype(u0) + if jac + J1 = calculate_jacobian(sys, sparse = sparse) + derivatives = Differential(get_iv(sys)).(unknowns(sys)) + J2 = calculate_jacobian(sys; sparse = sparse, dvs = derivatives) + similar(J1 + J2, uElType) + else + similar(jacobian_dae_sparsity(sys), uElType) + end + else + nothing + end + + kwargs = (; + sys = sys, + jac = _jac, + jac_prototype = jac_prototype, + observed = observedfun, + analytic = analytic, + initialization_data) + args = (; f) + + return maybe_codegen_scimlfn(expression, DAEFunction{iip, spec}, args; kwargs...) +end + +@fallback_iip_specialize function SciMLBase.DAEProblem{iip, spec}( + sys::System, du0map, u0map, tspan, parammap = SciMLBase.NullParameters(); + callback = nothing, check_length = true, eval_expression = false, + eval_module = @__MODULE__, check_compatibility = true, + expression = Val{false}, kwargs...) where {iip, spec} + check_complete(sys, DAEProblem) + check_compatibility && check_compatible_system(DAEProblem, sys) + + f, du0, u0, p = process_SciMLProblem(DAEFunction{iip, spec}, sys, u0map, parammap; + du0map, t = tspan !== nothing ? tspan[1] : tspan, check_length, eval_expression, + eval_module, check_compatibility, implicit_dae = true, expression, kwargs...) + + kwargs = process_kwargs(sys; expression, callback, eval_expression, eval_module, + kwargs...) + + diffvars = collect_differential_variables(sys) + sts = unknowns(sys) + differential_vars = map(Base.Fix2(in, diffvars), sts) + + args = (; f, du0, u0, tspan, p) + kwargs = (; differential_vars, kwargs...) + + return maybe_codegen_scimlproblem(expression, DAEProblem{iip}, args; kwargs...) +end diff --git a/src/problems/odeproblem.jl b/src/problems/odeproblem.jl index 42e49a0aea..0d5a146c48 100644 --- a/src/problems/odeproblem.jl +++ b/src/problems/odeproblem.jl @@ -83,9 +83,11 @@ end maybe_codegen_scimlproblem(expression, ODEProblem{iip}, args; kwargs...) end -function check_compatible_system(T::Union{Type{ODEFunction}, Type{ODEProblem}}, sys::System) +function check_compatible_system( + T::Union{Type{ODEFunction}, Type{ODEProblem}, Type{DAEFunction}, Type{DAEProblem}}, + sys::System) check_time_dependent(sys, T) - check_not_dde(sys, T) + check_not_dde(sys) check_no_cost(sys, T) check_no_constraints(sys, T) check_no_jumps(sys, T) From fa7f1f7551fdfea33d854a764517976cd0ccfc17 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Wed, 14 May 2025 11:48:22 +0530 Subject: [PATCH 084/235] feat: implement `SDEProblem` and `SDEFunction` for `System` --- src/ModelingToolkit.jl | 1 + src/problems/sdeproblem.jl | 120 +++++++++++++++++++++++++++++++++++++ 2 files changed, 121 insertions(+) create mode 100644 src/problems/sdeproblem.jl diff --git a/src/ModelingToolkit.jl b/src/ModelingToolkit.jl index cee03fb5ff..4304864087 100644 --- a/src/ModelingToolkit.jl +++ b/src/ModelingToolkit.jl @@ -171,6 +171,7 @@ include("problems/compatibility.jl") include("problems/odeproblem.jl") include("problems/ddeproblem.jl") include("problems/daeproblem.jl") +include("problems/sdeproblem.jl") include("systems/optimization/modelingtoolkitize.jl") include("systems/nonlinear/homotopy_continuation.jl") diff --git a/src/problems/sdeproblem.jl b/src/problems/sdeproblem.jl new file mode 100644 index 0000000000..a6270973f2 --- /dev/null +++ b/src/problems/sdeproblem.jl @@ -0,0 +1,120 @@ +@fallback_iip_specialize function SciMLBase.SDEFunction{iip, spec}( + sys::System; u0 = nothing, p = nothing, tgrad = false, jac = false, + t = nothing, eval_expression = false, eval_module = @__MODULE__, sparse = false, + steady_state = false, checkbounds = false, sparsity = false, analytic = nothing, + simplify = false, cse = true, initialization_data = nothing, + check_compatibility = true, expression = Val{false}, kwargs...) where {iip, spec} + check_complete(sys, SDEFunction) + check_compatibility && check_compatible_system(SDEFunction, sys) + + dvs = unknowns(sys) + ps = parameters(sys) + f = generate_rhs(sys, dvs, ps; expression, wrap_gfw = Val{true}, + eval_expression, eval_module, checkbounds = checkbounds, cse, + kwargs...) + g = generate_diffusion_function(sys, dvs, ps; expression, + wrap_gfw = Val{true}, eval_expression, eval_module, checkbounds, cse, kwargs...) + + if spec === SciMLBase.FunctionWrapperSpecialize && iip + if u0 === nothing || p === nothing || t === nothing + error("u0, p, and t must be specified for FunctionWrapperSpecialize on SDEFunction.") + end + if expression == Val{true} + f = :($(SciMLBase.wrapfun_iip)($f, ($u0, $u0, $p, $t))) + else + f = SciMLBase.wrapfun_iip(f, (u0, u0, p, t)) + end + end + + if tgrad + _tgrad = generate_tgrad(sys, dvs, ps; expression, + wrap_gfw = Val{true}, simplify, cse, eval_expression, eval_module, checkbounds, + kwargs...) + else + _tgrad = nothing + end + + if jac + _jac = generate_jacobian(sys; expression, + wrap_gfw = Val{true}, simplify, sparse, cse, eval_expression, eval_module, + checkbounds, kwargs...) + else + _jac = nothing + end + + M = calculate_massmatrix(sys) + _M = concrete_massmatrix(M; sparse, u0) + + observedfun = ObservedFunctionCache( + sys; expression, steady_state, eval_expression, eval_module, checkbounds, cse) + + _W_sparsity = W_sparsity(sys) + W_prototype = calculate_W_prototype(_W_sparsity; u0, sparse) + + kwargs = (; + sys = sys, + jac = _jac, + tgrad = _tgrad, + mass_matrix = _M, + jac_prototype = W_prototype, + observed = observedfun, + sparsity = sparsity ? _W_sparsity : nothing, + analytic = analytic, + initialization_data) + args = (; f, g) + + return maybe_codegen_scimlfn(expression, SDEFunction{iip, spec}, args; kwargs...) +end + +@fallback_iip_specialize function SciMLBase.SDEProblem{iip, spec}( + sys::System, u0map, tspan, parammap = SciMLBase.NullParameters(); + callback = nothing, check_length = true, eval_expression = false, + eval_module = @__MODULE__, check_compatibility = true, sparse = false, + sparsenoise = sparse, expression = Val{false}, kwargs...) where {iip, spec} + check_complete(sys, SDEProblem) + check_compatibility && check_compatible_system(SDEProblem, sys) + + f, u0, p = process_SciMLProblem(SDEFunction{iip, spec}, sys, u0map, parammap; + t = tspan !== nothing ? tspan[1] : tspan, check_length, eval_expression, + eval_module, check_compatibility, sparse, expression, kwargs...) + + noise, noise_rate_prototype = calculate_noise_and_rate_prototype(sys, u0; sparsenoise) + kwargs = process_kwargs(sys; expression, callback, eval_expression, eval_module, + kwargs...) + + args = (; f, u0, tspan, p) + kwargs = (; noise, noise_rate_prototype, kwargs...) + + return maybe_codegen_scimlproblem(expression, SDEProblem{iip}, args; kwargs...) +end + +function check_compatible_system(T::Union{Type{SDEFunction}, Type{SDEProblem}}, sys::System) + check_time_dependent(sys, T) + check_not_dde(sys) + check_no_cost(sys, T) + check_no_constraints(sys, T) + check_no_jumps(sys, T) + check_has_noise(sys, T) + check_is_continuous(sys, T) +end + +function calculate_noise_and_rate_prototype(sys::System, u0; sparsenoise = false) + noiseeqs = get_noise_eqs(sys) + if noiseeqs isa AbstractVector + # diagonal noise + noise_rate_prototype = nothing + noise = nothing + elseif size(noiseeqs, 2) == 1 + # scalar noise + noise_rate_prototype = nothing + noise = WienerProcess(0.0, 0.0, 0.0) + elseif sparsenoise + I, J, V = findnz(SparseArrays.sparse(noiseeqs)) + noise_rate_prototype = SparseArrays.sparse(I, J, zero(eltype(u0))) + noise = nothing + else + noise_rate_prototype = zeros(eltype(u0), size(noiseeqs)) + noise = nothing + end + return noise, noise_rate_prototype +end From f3433db063d663eef006e491c1952b89d77132dc Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Wed, 16 Apr 2025 17:38:55 +0530 Subject: [PATCH 085/235] feat: implement `SDDEProblem`, `SDDEFunction` for `System` --- src/ModelingToolkit.jl | 1 + src/problems/sddeproblem.jl | 96 +++++++++++++++++++++++++++++++++++++ 2 files changed, 97 insertions(+) create mode 100644 src/problems/sddeproblem.jl diff --git a/src/ModelingToolkit.jl b/src/ModelingToolkit.jl index 4304864087..67148369e5 100644 --- a/src/ModelingToolkit.jl +++ b/src/ModelingToolkit.jl @@ -172,6 +172,7 @@ include("problems/odeproblem.jl") include("problems/ddeproblem.jl") include("problems/daeproblem.jl") include("problems/sdeproblem.jl") +include("problems/sddeproblem.jl") include("systems/optimization/modelingtoolkitize.jl") include("systems/nonlinear/homotopy_continuation.jl") diff --git a/src/problems/sddeproblem.jl b/src/problems/sddeproblem.jl new file mode 100644 index 0000000000..3bc20c0412 --- /dev/null +++ b/src/problems/sddeproblem.jl @@ -0,0 +1,96 @@ +@fallback_iip_specialize function SciMLBase.SDDEFunction{iip, spec}( + sys::System; u0 = nothing, p = nothing, expression = Val{false}, + eval_expression = false, eval_module = @__MODULE__, checkbounds = false, + initialization_data = nothing, cse = true, check_compatibility = true, + sparse = false, simplify = false, analytic = nothing, kwargs...) where {iip, spec} + check_complete(sys, SDDEFunction) + check_compatibility && check_compatible_system(SDDEFunction, sys) + + dvs = unknowns(sys) + ps = parameters(sys) + + f = generate_rhs(sys, dvs, ps; expression, wrap_gfw = Val{true}, + eval_expression, eval_module, checkbounds = checkbounds, cse, kwargs...) + g = generate_diffusion_function(sys, dvs, ps; expression, + wrap_gfw = Val{true}, eval_expression, eval_module, checkbounds, cse, kwargs...) + + if spec === SciMLBase.FunctionWrapperSpecialize && iip + if u0 === nothing || p === nothing || t === nothing + error("u0, p, and t must be specified for FunctionWrapperSpecialize on SDDEFunction.") + end + if expression == Val{true} + f = :($(SciMLBase.wrapfun_iip)($f, ($u0, $u0, $p, $t))) + else + f = SciMLBase.wrapfun_iip(f, (u0, u0, p, t)) + end + end + + M = calculate_massmatrix(sys) + _M = concrete_massmatrix(M; sparse, u0) + + observedfun = ObservedFunctionCache( + sys; expression, eval_expression, eval_module, checkbounds, cse) + + kwargs = (; + sys = sys, + mass_matrix = _M, + observed = observedfun, + analytic = analytic, + initialization_data) + args = (; f, g) + + return maybe_codegen_scimlfn(expression, SDDEFunction{iip, spec}, args; kwargs...) +end + +@fallback_iip_specialize function SciMLBase.SDDEProblem{iip, spec}( + sys::System, u0map, tspan, parammap = SciMLBase.NullParameters(); + callback = nothing, check_length = true, cse = true, checkbounds = false, + eval_expression = false, eval_module = @__MODULE__, check_compatibility = true, + u0_constructor = identity, sparse = false, sparsenoise = sparse, + expression = Val{false}, kwargs...) where {iip, spec} + check_complete(sys, SDDEProblem) + check_compatibility && check_compatible_system(SDDEProblem, sys) + + f, u0, p = process_SciMLProblem(SDDEFunction{iip, spec}, sys, u0map, parammap; + t = tspan !== nothing ? tspan[1] : tspan, check_length, cse, checkbounds, + eval_expression, eval_module, check_compatibility, sparse, symbolic_u0 = true, + expression, u0_constructor, kwargs...) + + h = generate_history( + sys, u0; expression, wrap_gfw = Val{true}, cse, eval_expression, eval_module, + checkbounds) + + if expression == Val{true} + if u0 !== nothing + u0 = :($u0_constructor($map($float, h(p, tspan[1])))) + end + else + if u0 !== nothing + u0 = u0_constructor(float.(h(p, tspan[1]))) + end + end + + noise, noise_rate_prototype = calculate_noise_and_rate_prototype(sys, u0; sparsenoise) + kwargs = process_kwargs(sys; callback, eval_expression, eval_module, kwargs...) + + if expression == Val{true} + g = :(f.g) + else + g = f.g + end + args = (; f, g, u0, h, tspan, p) + kwargs = (; noise, noise_rate_prototype, kwargs...) + + return maybe_codegen_scimlproblem(expression, SDDEProblem{iip}, args; kwargs...) +end + +function check_compatible_system( + T::Union{Type{SDDEFunction}, Type{SDDEProblem}}, sys::System) + check_time_dependent(sys, T) + check_is_dde(sys) + check_no_cost(sys, T) + check_no_constraints(sys, T) + check_no_jumps(sys, T) + check_has_noise(sys, T) + check_is_continuous(sys, T) +end From aeb23165fa39a34c7d94e9c465a83133cb5a67d7 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Wed, 14 May 2025 11:49:06 +0530 Subject: [PATCH 086/235] feat: implement `NonlinearProblem`, `NonlinearFunction` for `System` --- src/ModelingToolkit.jl | 1 + src/problems/nonlinearproblem.jl | 85 ++++++++++++++++++++++++++++++++ 2 files changed, 86 insertions(+) create mode 100644 src/problems/nonlinearproblem.jl diff --git a/src/ModelingToolkit.jl b/src/ModelingToolkit.jl index 67148369e5..75ff8a0711 100644 --- a/src/ModelingToolkit.jl +++ b/src/ModelingToolkit.jl @@ -173,6 +173,7 @@ include("problems/ddeproblem.jl") include("problems/daeproblem.jl") include("problems/sdeproblem.jl") include("problems/sddeproblem.jl") +include("problems/nonlinearproblem.jl") include("systems/optimization/modelingtoolkitize.jl") include("systems/nonlinear/homotopy_continuation.jl") diff --git a/src/problems/nonlinearproblem.jl b/src/problems/nonlinearproblem.jl new file mode 100644 index 0000000000..b4f846f3a4 --- /dev/null +++ b/src/problems/nonlinearproblem.jl @@ -0,0 +1,85 @@ +@fallback_iip_specialize function SciMLBase.NonlinearFunction{iip, spec}( + sys::System; u0 = nothing, p = nothing, jac = false, + eval_expression = false, eval_module = @__MODULE__, sparse = false, + checkbounds = false, sparsity = false, analytic = nothing, + simplify = false, cse = true, initialization_data = nothing, + resid_prototype = nothing, check_compatibility = true, expression = Val{false}, + kwargs...) where {iip, spec} + check_complete(sys, NonlinearFunction) + check_compatibility && check_compatible_system(NonlinearFunction, sys) + + dvs = unknowns(sys) + ps = parameters(sys) + f = generate_rhs(sys, dvs, ps; expression, wrap_gfw = Val{true}, + eval_expression, eval_module, checkbounds = checkbounds, cse, + kwargs...) + + if spec === SciMLBase.FunctionWrapperSpecialize && iip + if u0 === nothing || p === nothing + error("u0, and p must be specified for FunctionWrapperSpecialize on NonlinearFunction.") + end + if expression == Val{true} + f = :($(SciMLBase.wrapfun_iip)($f, ($u0, $u0, $p))) + else + f = SciMLBase.wrapfun_iip(f, (u0, u0, p)) + end + end + + if jac + _jac = generate_jacobian(sys; expression, + wrap_gfw = Val{true}, simplify, sparse, cse, eval_expression, eval_module, + checkbounds, kwargs...) + else + _jac = nothing + end + + observedfun = ObservedFunctionCache( + sys; steady_state = false, expression, eval_expression, eval_module, checkbounds, + cse) + + if sparse + jac_prototype = similar(calculate_jacobian(sys; sparse), eltype(u0)) + else + jac_prototype = nothing + end + + kwargs = (; + sys = sys, + jac = _jac, + observed = observedfun, + analytic = analytic, + jac_prototype, + resid_prototype, + initialization_data) + args = (; f) + + return maybe_codegen_scimlfn(expression, NonlinearFunction{iip, spec}, args; kwargs...) +end + +@fallback_iip_specialize function SciMLBase.NonlinearProblem{iip, spec}( + sys::System, u0map, parammap = SciMLBase.NullParameters(); expression = Val{false}, + check_length = true, check_compatibility = true, kwargs...) where {iip, spec} + check_complete(sys, NonlinearProblem) + if is_time_dependent(sys) + sys = NonlinearSystem(sys) + end + check_compatibility && check_compatible_system(NonlinearProblem, sys) + + f, u0, p = process_SciMLProblem(NonlinearFunction{iip, spec}, sys, u0map, parammap; + check_length, check_compatibility, expression, kwargs...) + + kwargs = process_kwargs(sys; kwargs...) + args = (; f, u0, p, ptype = StandardNonlinearProblem()) + + return maybe_codegen_scimlproblem(expression, NonlinearProblem{iip}, args; kwargs...) +end + +function check_compatible_system( + T::Union{Type{NonlinearFunction}, Type{NonlinearProblem}}, sys::System) + check_time_independent(sys, T) + check_not_dde(sys) + check_no_cost(sys, T) + check_no_constraints(sys, T) + check_no_jumps(sys, T) + check_no_noise(sys, T) +end From 575e302311438c3d21e29d0a7b75e5e1e516a810 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Wed, 16 Apr 2025 19:58:55 +0530 Subject: [PATCH 087/235] feat: implement `IntervalNonlinearProblem`, `IntervalNonlinearFunction` for `System` --- src/ModelingToolkit.jl | 1 + src/problems/intervalnonlinearproblem.jl | 62 ++++++++++++++++++++++++ 2 files changed, 63 insertions(+) create mode 100644 src/problems/intervalnonlinearproblem.jl diff --git a/src/ModelingToolkit.jl b/src/ModelingToolkit.jl index 75ff8a0711..5bcfc09119 100644 --- a/src/ModelingToolkit.jl +++ b/src/ModelingToolkit.jl @@ -174,6 +174,7 @@ include("problems/daeproblem.jl") include("problems/sdeproblem.jl") include("problems/sddeproblem.jl") include("problems/nonlinearproblem.jl") +include("problems/intervalnonlinearproblem.jl") include("systems/optimization/modelingtoolkitize.jl") include("systems/nonlinear/homotopy_continuation.jl") diff --git a/src/problems/intervalnonlinearproblem.jl b/src/problems/intervalnonlinearproblem.jl new file mode 100644 index 0000000000..9c9e5d8688 --- /dev/null +++ b/src/problems/intervalnonlinearproblem.jl @@ -0,0 +1,62 @@ +function SciMLBase.IntervalNonlinearFunction( + sys::System; u0 = nothing, p = nothing, eval_expression = false, + eval_module = @__MODULE__, expression = Val{false}, checkbounds = false, + analytic = nothing, cse = true, initialization_data = nothing, + check_compatibility = true, kwargs...) + check_complete(sys, IntervalNonlinearFunction) + check_compatibility && check_compatible_system(IntervalNonlinearFunction, sys) + + dvs = unknowns(sys) + ps = parameters(sys) + f = generate_rhs(sys, dvs, ps; expression, wrap_gfw = Val{true}, + scalar = true, eval_expression, eval_module, checkbounds, cse, kwargs...) + + observedfun = ObservedFunctionCache( + sys; steady_state = false, expression, eval_expression, eval_module, checkbounds, + cse) + + args = (; f) + kwargs = (; + sys = sys, + observed = observedfun, + analytic = analytic, + initialization_data) + + return maybe_codegen_scimlfn( + expression, IntervalNonlinearFunction{false}, args; kwargs...) +end + +function SciMLBase.IntervalNonlinearProblem( + sys::System, uspan::NTuple{2}, parammap = SciMLBase.NullParameters(); + check_compatibility = true, expression = Val{false}, kwargs...) + check_complete(sys, IntervalNonlinearProblem) + check_compatibility && check_compatible_system(IntervalNonlinearProblem, sys) + + u0map = unknowns(sys) .=> uspan[1] + f, u0, p = process_SciMLProblem(IntervalNonlinearFunction, sys, u0map, parammap; + check_compatibility, expression, kwargs...) + + kwargs = process_kwargs(sys; kwargs...) + args = (; f, uspan, p) + return maybe_codegen_scimlproblem(expression, IntervalNonlinearProblem, args; kwargs...) +end + +function check_compatible_system( + T::Union{Type{IntervalNonlinearFunction}, Type{IntervalNonlinearProblem}}, sys::System) + check_time_independent(sys, T) + if !isone(length(unknowns(sys))) + throw(SystemCompatibilityError(""" + `$T` requires a system with a single unknown. Found `$(unknowns(sys))`. + """)) + end + if !isone(length(equations(sys))) + throw(SystemCompatibilityError(""" + `$T` requires a system with a single equation. Found `$(equations(sys))`. + """)) + end + check_not_dde(sys) + check_no_cost(sys, T) + check_no_constraints(sys, T) + check_no_jumps(sys, T) + check_no_noise(sys, T) +end From 90baa3d76a8f2e37cd2aaa1db6d5061d3e2b4a72 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Wed, 14 May 2025 11:51:24 +0530 Subject: [PATCH 088/235] feat: implement `ImplicitDiscreteProblem` `ImplicitDiscreteFunction` for `System` --- src/ModelingToolkit.jl | 1 + src/problems/implicitdiscreteproblem.jl | 74 +++++++++++++++++++++++++ 2 files changed, 75 insertions(+) create mode 100644 src/problems/implicitdiscreteproblem.jl diff --git a/src/ModelingToolkit.jl b/src/ModelingToolkit.jl index 5bcfc09119..308d17e21f 100644 --- a/src/ModelingToolkit.jl +++ b/src/ModelingToolkit.jl @@ -175,6 +175,7 @@ include("problems/sdeproblem.jl") include("problems/sddeproblem.jl") include("problems/nonlinearproblem.jl") include("problems/intervalnonlinearproblem.jl") +include("problems/implicitdiscreteproblem.jl") include("systems/optimization/modelingtoolkitize.jl") include("systems/nonlinear/homotopy_continuation.jl") diff --git a/src/problems/implicitdiscreteproblem.jl b/src/problems/implicitdiscreteproblem.jl new file mode 100644 index 0000000000..476265e049 --- /dev/null +++ b/src/problems/implicitdiscreteproblem.jl @@ -0,0 +1,74 @@ +@fallback_iip_specialize function SciMLBase.ImplicitDiscreteFunction{iip, spec}( + sys::System; u0 = nothing, p = nothing, t = nothing, eval_expression = false, + eval_module = @__MODULE__, expression = Val{false}, + checkbounds = false, analytic = nothing, simplify = false, cse = true, + initialization_data = nothing, check_compatibility = true, kwargs...) where { + iip, spec} + check_complete(sys, ImplicitDiscreteFunction) + check_compatibility && check_compatible_system(ImplicitDiscreteFunction, sys) + + iv = get_iv(sys) + dvs = unknowns(sys) + ps = parameters(sys) + f = generate_rhs(sys, dvs, ps; expression, wrap_gfw = Val{true}, + implicit_dae = true, eval_expression, eval_module, checkbounds = checkbounds, cse, + override_discrete = true, kwargs...) + + if spec === SciMLBase.FunctionWrapperSpecialize && iip + if u0 === nothing || p === nothing || t === nothing + error("u0, p, and t must be specified for FunctionWrapperSpecialize on ImplicitDiscreteFunction.") + end + f = SciMLBase.wrapfun_iip(f, (u0, u0, u0, p, t)) + end + + if length(dvs) == length(equations(sys)) + resid_prototype = nothing + else + resid_prototype = calculate_resid_prototype(length(equations(sys)), u0, p) + end + + observedfun = ObservedFunctionCache( + sys; steady_state = false, expression, eval_expression, eval_module, checkbounds, cse) + + args = (; f) + kwargs = (; + sys = sys, + observed = observedfun, + analytic = analytic, + initialization_data, + resid_prototype) + + return maybe_codegen_scimlfn( + expression, ImplicitDiscreteFunction{iip, spec}, args; kwargs...) +end + +@fallback_iip_specialize function SciMLBase.ImplicitDiscreteProblem{iip, spec}( + sys::System, u0map, tspan, parammap = SciMLBase.NullParameters(); + check_compatibility = true, expression = Val{false}, kwargs...) where {iip, spec} + check_complete(sys, ImplicitDiscreteProblem) + check_compatibility && check_compatible_system(ImplicitDiscreteProblem, sys) + + dvs = unknowns(sys) + u0map = to_varmap(u0map, dvs) + add_toterms!(u0map; replace = true) + f, u0, p = process_SciMLProblem( + ImplicitDiscreteFunction{iip, spec}, sys, u0map, parammap; + t = tspan !== nothing ? tspan[1] : tspan, check_compatibility, + expression, kwargs...) + + kwargs = process_kwargs(sys; kwargs...) + args = (; f, u0, tspan, p) + return maybe_codegen_scimlproblem( + expression, ImplicitDiscreteProblem{iip}, args; kwargs...) +end + +function check_compatible_system( + T::Union{Type{ImplicitDiscreteFunction}, Type{ImplicitDiscreteProblem}}, sys::System) + check_time_dependent(sys, T) + check_not_dde(sys) + check_no_cost(sys, T) + check_no_constraints(sys, T) + check_no_jumps(sys, T) + check_no_noise(sys, T) + check_is_discrete(sys, T) +end From 025e319a4114e5b3651554abcaab2b4a5e660b4d Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Wed, 14 May 2025 11:50:09 +0530 Subject: [PATCH 089/235] feat: implement `DiscreteProblem` and `DiscreteFunction` for `System` refactor: move `shift_u0map_forward` to `discreteproblem.jl` --- src/ModelingToolkit.jl | 1 + src/problems/discreteproblem.jl | 104 ++++++++++++++++++++++++++++++++ 2 files changed, 105 insertions(+) create mode 100644 src/problems/discreteproblem.jl diff --git a/src/ModelingToolkit.jl b/src/ModelingToolkit.jl index 308d17e21f..7a50089985 100644 --- a/src/ModelingToolkit.jl +++ b/src/ModelingToolkit.jl @@ -176,6 +176,7 @@ include("problems/sddeproblem.jl") include("problems/nonlinearproblem.jl") include("problems/intervalnonlinearproblem.jl") include("problems/implicitdiscreteproblem.jl") +include("problems/discreteproblem.jl") include("systems/optimization/modelingtoolkitize.jl") include("systems/nonlinear/homotopy_continuation.jl") diff --git a/src/problems/discreteproblem.jl b/src/problems/discreteproblem.jl new file mode 100644 index 0000000000..d77efed225 --- /dev/null +++ b/src/problems/discreteproblem.jl @@ -0,0 +1,104 @@ +@fallback_iip_specialize function SciMLBase.DiscreteFunction{iip, spec}( + sys::System; u0 = nothing, p = nothing, t = nothing, + eval_expression = false, eval_module = @__MODULE__, expression = Val{false}, + checkbounds = false, analytic = nothing, simplify = false, cse = true, + initialization_data = nothing, check_compatibility = true, + kwargs...) where {iip, spec} + check_complete(sys, DiscreteFunction) + check_compatibility && check_compatible_system(DiscreteFunction, sys) + + dvs = unknowns(sys) + ps = parameters(sys) + f = generate_rhs(sys, dvs, ps; expression, wrap_gfw = Val{true}, + eval_expression, eval_module, checkbounds = checkbounds, cse, + kwargs...) + + if spec === SciMLBase.FunctionWrapperSpecialize && iip + if u0 === nothing || p === nothing || t === nothing + error("u0, p, and t must be specified for FunctionWrapperSpecialize on DiscreteFunction.") + end + if expression == Val{true} + f = :($(SciMLBase.wrapfun_iip)($f, ($u0, $u0, $p, $t))) + else + f = SciMLBase.wrapfun_iip(f, (u0, u0, p, t)) + end + end + + observedfun = ObservedFunctionCache( + sys; steady_state = false, expression, eval_expression, eval_module, checkbounds, + cse) + + kwargs = (; + sys = sys, + observed = observedfun, + analytic = analytic, + initialization_data) + args = (; f) + + return maybe_codegen_scimlfn(expression, DiscreteFunction{iip, spec}, args; kwargs...) +end + +@fallback_iip_specialize function SciMLBase.DiscreteProblem{iip, spec}( + sys::System, u0map, tspan, parammap = SciMLBase.NullParameters(); + check_compatibility = true, expression = Val{false}, kwargs...) where {iip, spec} + check_complete(sys, DiscreteProblem) + check_compatibility && check_compatible_system(DiscreteProblem, sys) + + dvs = unknowns(sys) + u0map = to_varmap(u0map, dvs) + add_toterms!(u0map; replace = true) + f, u0, p = process_SciMLProblem(DiscreteFunction{iip, spec}, sys, u0map, parammap; + t = tspan !== nothing ? tspan[1] : tspan, check_compatibility, expression, + kwargs...) + + if expression == Val{true} + u0 = :(f($u0, p, tspan[1])) + else + u0 = f(u0, p, tspan[1]) + end + + kwargs = process_kwargs(sys; kwargs...) + args = (; f, u0, tspan, p) + + return maybe_codegen_scimlproblem(expression, DiscreteProblem{iip}, args; kwargs...) +end + +function check_compatible_system( + T::Union{Type{DiscreteFunction}, Type{DiscreteProblem}}, sys::System) + check_time_dependent(sys, T) + check_not_dde(sys) + check_no_cost(sys, T) + check_no_constraints(sys, T) + check_no_jumps(sys, T) + check_no_noise(sys, T) + check_is_discrete(sys, T) + check_is_explicit(sys, T, ImplicitDiscreteProblem) +end + +function shift_u0map_forward(sys::System, u0map, defs) + iv = get_iv(sys) + updated = AnyDict() + for k in collect(keys(u0map)) + 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)).") + + updated[Shift(iv, 1)(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)))).") + else + updated[Shift(iv, op.steps + 1)(only(arguments(k)))] = v + end + end + for var in unknowns(sys) + op = operation(var) + root = getunshifted(var) + shift = getshift(var) + isnothing(root) && continue + (haskey(updated, Shift(iv, shift)(root)) || haskey(updated, var)) && continue + haskey(defs, root) || error("Initial condition for $var not provided.") + updated[var] = defs[root] + end + return updated +end From cd19b841ac0cdf4065bf84c6afe64abd89552a1e Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Thu, 17 Apr 2025 19:45:39 +0530 Subject: [PATCH 090/235] feat: allow manually choosing time-independent initialization --- src/systems/nonlinear/initializesystem.jl | 11 +++++++---- src/systems/problem_utils.jl | 23 ++++++++++++++++------- 2 files changed, 23 insertions(+), 11 deletions(-) diff --git a/src/systems/nonlinear/initializesystem.jl b/src/systems/nonlinear/initializesystem.jl index 056d35df37..0396074300 100644 --- a/src/systems/nonlinear/initializesystem.jl +++ b/src/systems/nonlinear/initializesystem.jl @@ -1,5 +1,6 @@ -function generate_initializesystem(sys::AbstractSystem; kwargs...) - if is_time_dependent(sys) +function generate_initializesystem( + sys::AbstractSystem; time_dependent_init = is_time_dependent(sys), kwargs...) + if time_dependent_init generate_initializesystem_timevarying(sys; kwargs...) else generate_initializesystem_timeindependent(sys; kwargs...) @@ -544,6 +545,7 @@ function SciMLBase.remake_initialization_data( merge!(guesses, meta.guesses) use_scc = meta.use_scc initialization_eqs = meta.additional_initialization_eqs + time_dependent_init = meta.time_dependent_init else # there is no initializeprob, so the original problem construction # had no solvable parameters and had the differential variables @@ -599,8 +601,9 @@ function SciMLBase.remake_initialization_data( typeof(newp.initials), floatT, Val(1), Val(length(vals)), vals...) end kws = maybe_build_initialization_problem( - sys, SciMLBase.isinplace(odefn), op, u0map, pmap, t0, defs, guesses, missing_unknowns; - use_scc, initialization_eqs, floatT, u0_constructor, p_constructor, allow_incomplete = true) + sys, SciMLBase.isinplace(odefn), op, u0map, pmap, t0, defs, guesses, + missing_unknowns; time_dependent_init, use_scc, initialization_eqs, floatT, + u0_constructor, p_constructor, allow_incomplete = true) odefn = remake(odefn; kws...) return SciMLBase.remake_initialization_data(sys, odefn, newu0, t0, newp, newu0, newp) diff --git a/src/systems/problem_utils.jl b/src/systems/problem_utils.jl index 3bc8d9d8ac..867ee80ddb 100644 --- a/src/systems/problem_utils.jl +++ b/src/systems/problem_utils.jl @@ -911,6 +911,10 @@ struct InitializationMetadata{R <: ReconstructInitializeprob, GUU, SIU} """ use_scc::Bool """ + Whether the initialization uses the independent variable. + """ + time_dependent_init::Bool + """ `ReconstructInitializeprob` for this initialization problem. """ oop_reconstruct_u0_p::R @@ -1015,7 +1019,8 @@ All other keyword arguments are forwarded to `InitializationProblem`. """ function maybe_build_initialization_problem( sys::AbstractSystem, iip, op::AbstractDict, u0map, pmap, t, defs, - guesses, missing_unknowns; implicit_dae = false, u0_constructor = identity, + guesses, missing_unknowns; implicit_dae = false, + time_dependent_init = is_time_dependent(sys), u0_constructor = identity, p_constructor = identity, floatT = Float64, initialization_eqs = [], use_scc = true, kwargs...) guesses = merge(ModelingToolkit.guesses(sys), todict(guesses)) @@ -1025,7 +1030,7 @@ function maybe_build_initialization_problem( end initializeprob = ModelingToolkit.InitializationProblem{iip}( - sys, t, u0map, pmap; guesses, initialization_eqs, + sys, t, u0map, pmap; guesses, time_dependent_init, initialization_eqs, use_scc, u0_constructor, p_constructor, kwargs...) if state_values(initializeprob) !== nothing _u0 = state_values(initializeprob) @@ -1060,11 +1065,15 @@ function maybe_build_initialization_problem( end meta = InitializationMetadata( u0map, pmap, guesses, Vector{Equation}(initialization_eqs), - use_scc, ReconstructInitializeprob( + use_scc, time_dependent_init, + ReconstructInitializeprob( sys, initializeprob.f.sys; u0_constructor, p_constructor), get_initial_unknowns, SetInitialUnknowns(sys)) - if is_time_dependent(sys) + if time_dependent_init === nothing + time_dependent_init = is_time_dependent(sys) + end + if time_dependent_init all_init_syms = Set(all_symbols(initializeprob)) solved_unknowns = filter(var -> var in all_init_syms, unknowns(sys)) initializeprobmap = u0_constructor ∘ getu(initializeprob, solved_unknowns) @@ -1101,7 +1110,7 @@ function maybe_build_initialization_problem( end end - if is_time_dependent(sys) + if time_dependent_init for v in missing_unknowns op[v] = getu(initializeprob, v)(initializeprob) end @@ -1221,7 +1230,7 @@ function process_SciMLProblem( check_length = true, symbolic_u0 = false, warn_cyclic_dependency = false, circular_dependency_max_cycle_length = length(all_symbols(sys)), circular_dependency_max_cycles = 10, - substitution_limit = 100, use_scc = true, + substitution_limit = 100, use_scc = true, time_dependent_init = is_time_dependent(sys), force_initialization_time_independent = false, algebraic_only = false, allow_incomplete = false, is_initializeprob = false, kwargs...) dvs = unknowns(sys) @@ -1282,7 +1291,7 @@ function process_SciMLProblem( warn_cyclic_dependency, check_units = check_initialization_units, circular_dependency_max_cycle_length, circular_dependency_max_cycles, use_scc, force_time_independent = force_initialization_time_independent, algebraic_only, allow_incomplete, - u0_constructor, p_constructor, floatT) + u0_constructor, p_constructor, floatT, time_dependent_init) kwargs = merge(kwargs, kws) end From 3d5e4c08972bee90d6eda2544187f24006358e45 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Wed, 14 May 2025 11:54:53 +0530 Subject: [PATCH 091/235] feat: implement `OptimizationProblem` and `OptimizationFunction` for `System` --- src/ModelingToolkit.jl | 1 + src/problems/optimizationproblem.jl | 149 ++++++++++++++++++++++++++++ 2 files changed, 150 insertions(+) create mode 100644 src/problems/optimizationproblem.jl diff --git a/src/ModelingToolkit.jl b/src/ModelingToolkit.jl index 7a50089985..f9a0423e21 100644 --- a/src/ModelingToolkit.jl +++ b/src/ModelingToolkit.jl @@ -177,6 +177,7 @@ include("problems/nonlinearproblem.jl") include("problems/intervalnonlinearproblem.jl") include("problems/implicitdiscreteproblem.jl") include("problems/discreteproblem.jl") +include("problems/optimizationproblem.jl") include("systems/optimization/modelingtoolkitize.jl") include("systems/nonlinear/homotopy_continuation.jl") diff --git a/src/problems/optimizationproblem.jl b/src/problems/optimizationproblem.jl new file mode 100644 index 0000000000..243d453ada --- /dev/null +++ b/src/problems/optimizationproblem.jl @@ -0,0 +1,149 @@ +function SciMLBase.OptimizationFunction(sys::System, args...; kwargs...) + return OptimizationFunction{true}(sys, args...; kwargs...) +end + +function SciMLBase.OptimizationFunction{iip}(sys::System; + u0 = nothing, p = nothing, grad = false, hess = false, + sparse = false, cons_j = false, cons_h = false, cons_sparse = false, + linenumbers = true, eval_expression = false, eval_module = @__MODULE__, + simplify = false, check_compatibility = true, checkbounds = false, cse = true, + expression = Val{false}, kwargs...) where {iip} + check_complete(sys, OptimizationFunction) + check_compatibility && check_compatible_system(OptimizationFunction, sys) + dvs = unknowns(sys) + ps = parameters(sys) + cstr = constraints(sys) + + f = generate_cost(sys; expression, wrap_gfw = Val{true}, eval_expression, + eval_module, checkbounds, cse, kwargs...) + + if grad + _grad = generate_cost_gradient(sys; expression, wrap_gfw = Val{true}, + eval_expression, eval_module, checkbounds, cse, kwargs...) + else + _grad = nothing + end + if hess + _hess, hess_prototype = generate_cost_hessian( + sys; expression, wrap_gfw = Val{true}, eval_expression, + eval_module, checkbounds, cse, sparse, simplify, return_sparsity = true, + kwargs...) + else + _hess = hess_prototype = nothing + if sparse + hess_prototype = cost_hessian_sparsity(sys) + end + end + if isempty(cstr) + cons = _cons_j = cons_jac_prototype = _cons_h = nothing + cons_hess_prototype = cons_expr = nothing + else + cons = generate_cons(sys; expression, wrap_gfw = Val{true}, + eval_expression, eval_module, checkbounds, cse, kwargs...) + if cons_j + _cons_j, cons_jac_prototype = generate_constraint_jacobian( + sys; expression, wrap_gfw = Val{true}, eval_expression, + eval_module, checkbounds, cse, simplify, sparse = cons_sparse, + return_sparsity = true, kwargs...) + else + _cons_j = cons_jac_prototype = nothing + end + if cons_h + _cons_h, cons_hess_prototype = generate_constraint_hessian( + sys; expression, wrap_gfw = Val{true}, eval_expression, + eval_module, checkbounds, cse, simplify, sparse = cons_sparse, + return_sparsity = true, kwargs...) + else + _cons_h = cons_hess_prototype = nothing + end + cons_expr = subs_constants(cstr) + end + + obj_expr = subs_constants(cost(sys)) + + observedfun = ObservedFunctionCache( + sys; expression, eval_expression, eval_module, checkbounds, cse) + + args = (; f, ad = SciMLBase.NoAD()) + kwargs = (; + sys = sys, + grad = _grad, + hess = _hess, + hess_prototype = hess_prototype, + cons = cons, + cons_j = _cons_j, + cons_jac_prototype = cons_jac_prototype, + cons_h = _cons_h, + cons_hess_prototype = cons_hess_prototype, + cons_expr = cons_expr, + expr = obj_expr, + observed = observedfun) + + return maybe_codegen_scimlfn(expression, OptimizationFunction{iip}, args; kwargs...) +end + +function SciMLBase.OptimizationProblem(sys::System, args...; kwargs...) + return OptimizationProblem{true}(sys, args...; kwargs...) +end + +function SciMLBase.OptimizationProblem{iip}( + sys::System, u0map, parammap = SciMLBase.NullParameters(); lb = nothing, + ub = nothing, check_compatibility = true, expression = Val{false}, + kwargs...) where {iip} + check_complete(sys, OptimizationProblem) + check_compatibility && check_compatible_system(OptimizationProblem, sys) + + f, u0, p = process_SciMLProblem(OptimizationFunction{iip}, sys, u0map, parammap; + check_compatibility, tofloat = false, check_length = false, expression, kwargs...) + + dvs = unknowns(sys) + int = symtype.(unwrap.(dvs)) .<: Integer + if lb === nothing && ub === nothing + lb = first.(getbounds.(dvs)) + ub = last.(getbounds.(dvs)) + isboolean = symtype.(unwrap.(dvs)) .<: Bool + lb[isboolean] .= 0 + ub[isboolean] .= 1 + else + xor(isnothing(lb), isnothing(ub)) && + throw(ArgumentError("Expected both `lb` and `ub` to be supplied")) + !isnothing(lb) && length(lb) != length(dvs) && + throw(ArgumentError("Expected both `lb` to be of the same length as the vector of optimization variables")) + !isnothing(ub) && length(ub) != length(dvs) && + throw(ArgumentError("Expected both `ub` to be of the same length as the vector of optimization variables")) + end + + ps = parameters(sys) + defs = merge(defaults(sys), to_varmap(parammap, ps), to_varmap(u0map, dvs)) + lb = varmap_to_vars(dvs .=> lb, dvs; defaults = defs, tofloat = false) + ub = varmap_to_vars(dvs .=> ub, dvs; defaults = defs, tofloat = false) + + if !isnothing(lb) && all(lb .== -Inf) && !isnothing(ub) && all(ub .== Inf) + lb = nothing + ub = nothing + end + + cstr = constraints(sys) + if isempty(cstr) + lcons = ucons = nothing + else + lcons = fill(-Inf, length(cstr)) + ucons = zeros(length(cstr)) + lcons[findall(Base.Fix2(isa, Equation), cstr)] .= 0.0 + end + + kwargs = process_kwargs(sys; kwargs...) + kwargs = (; lb, ub, int, lcons, ucons, kwargs...) + args = (; f, u0, p) + return maybe_codegen_scimlproblem(expression, OptimizationProblem{iip}, args; kwargs...) +end + +function check_compatible_system( + T::Union{Type{OptimizationFunction}, Type{OptimizationProblem}}, sys::System) + check_time_independent(sys, T) + check_not_dde(sys) + check_has_cost(sys, T) + check_no_jumps(sys, T) + check_no_noise(sys, T) + check_no_equations(sys, T) +end From b875120db2d930f20d44ba9b2921bbc9925a53b4 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Wed, 14 May 2025 12:10:33 +0530 Subject: [PATCH 092/235] feat: implement `JumpProblem` for `System` refactor: move jumpsystem functions to generalized locations --- src/ModelingToolkit.jl | 1 + src/problems/jumpproblem.jl | 223 ++++++++++++++++++++++++++++++++++++ src/systems/codegen.jl | 25 ++++ 3 files changed, 249 insertions(+) create mode 100644 src/problems/jumpproblem.jl diff --git a/src/ModelingToolkit.jl b/src/ModelingToolkit.jl index f9a0423e21..7e5b7e3caa 100644 --- a/src/ModelingToolkit.jl +++ b/src/ModelingToolkit.jl @@ -178,6 +178,7 @@ include("problems/intervalnonlinearproblem.jl") include("problems/implicitdiscreteproblem.jl") include("problems/discreteproblem.jl") include("problems/optimizationproblem.jl") +include("problems/jumpproblem.jl") include("systems/optimization/modelingtoolkitize.jl") include("systems/nonlinear/homotopy_continuation.jl") diff --git a/src/problems/jumpproblem.jl b/src/problems/jumpproblem.jl new file mode 100644 index 0000000000..242b9549fa --- /dev/null +++ b/src/problems/jumpproblem.jl @@ -0,0 +1,223 @@ +@fallback_iip_specialize function JumpProcesses.JumpProblem{iip, spec}( + sys::System, u0map, tspan::Union{Tuple, Nothing}, pmap = SciMLBase.NullParameters(); + check_compatibility = true, eval_expression = false, eval_module = @__MODULE__, + checkbounds = false, cse = true, aggregator = JumpProcesses.NullAggregator(), + callback = nothing, rng = nothing, kwargs...) where {iip, spec} + check_complete(sys, JumpProblem) + check_compatibility && check_compatible_system(JumpProblem, sys) + + has_vrjs = any(x -> x isa VariableRateJump, jumps(sys)) + has_eqs = !isempty(equations(sys)) + has_noise = get_noise_eqs(sys) !== nothing + + if (has_vrjs || has_eqs) + if has_eqs && has_noise + prob = SDEProblem{iip, spec}( + sys, u0map, tspan, pmap; check_compatibility = false, + build_initializeprob = false, checkbounds, cse, check_length = false, + kwargs...) + elseif has_eqs + prob = ODEProblem{iip, spec}( + sys, u0map, tspan, pmap; check_compatibility = false, + build_initializeprob = false, checkbounds, cse, check_length = false, + kwargs...) + else + _, u0, p = process_SciMLProblem(EmptySciMLFunction{iip}, sys, u0map, pmap; + t = tspan === nothing ? nothing : tspan[1], tofloat = false, + check_length = false, build_initializeprob = false) + observedfun = ObservedFunctionCache(sys; eval_expression, eval_module, + checkbounds, cse) + f = (du, u, p, t) -> (du .= 0; nothing) + df = ODEFunction{true, spec}(f; sys, observed = observedfun) + prob = ODEProblem{true}(df, u0, tspan, p; kwargs...) + end + else + _f, u0, p = process_SciMLProblem(EmptySciMLFunction{iip}, sys, u0map, pmap; + t = tspan === nothing ? nothing : tspan[1], tofloat = false, check_length = false, build_initializeprob = false, cse) + f = DiffEqBase.DISCRETE_INPLACE_DEFAULT + + observedfun = ObservedFunctionCache( + sys; eval_expression, eval_module, checkbounds, cse) + + df = DiscreteFunction{true, true}(f; sys = sys, observed = observedfun, + initialization_data = get(_f.kwargs, :initialization_data, nothing)) + prob = DiscreteProblem(df, u0, tspan, p; kwargs...) + end + + dvs = unknowns(sys) + unknowntoid = Dict(value(unknown) => i for (i, unknown) in enumerate(dvs)) + js = jumps(sys) + invttype = prob.tspan[1] === nothing ? Float64 : typeof(1 / prob.tspan[2]) + + # handling parameter substitution and empty param vecs + p = (prob.p isa DiffEqBase.NullParameters || prob.p === nothing) ? Num[] : prob.p + + majpmapper = JumpSysMajParamMapper(sys, p; jseqs = js, rateconsttype = invttype) + _majs = Vector{MassActionJump}(filter(x -> x isa MassActionJump, js)) + _crjs = Vector{ConstantRateJump}(filter(x -> x isa ConstantRateJump, js)) + vrjs = Vector{VariableRateJump}(filter(x -> x isa VariableRateJump, js)) + majs = isempty(_majs) ? nothing : assemble_maj(_majs, unknowntoid, majpmapper) + crjs = ConstantRateJump[assemble_crj(sys, j, unknowntoid; eval_expression, eval_module) + for j in _crjs] + vrjs = VariableRateJump[assemble_vrj(sys, j, unknowntoid; eval_expression, eval_module) + for j in vrjs] + jset = JumpSet(Tuple(vrjs), Tuple(crjs), nothing, majs) + + # dep graphs are only for constant rate jumps + nonvrjs = ArrayPartition(_majs, _crjs) + if needs_vartojumps_map(aggregator) || needs_depgraph(aggregator) || + (aggregator isa JumpProcesses.NullAggregator) + jdeps = asgraph(sys; eqs = nonvrjs) + vdeps = variable_dependencies(sys; eqs = nonvrjs) + vtoj = jdeps.badjlist + jtov = vdeps.badjlist + jtoj = needs_depgraph(aggregator) ? eqeq_dependencies(jdeps, vdeps).fadjlist : + nothing + else + vtoj = nothing + jtov = nothing + jtoj = nothing + end + + # handle events, making sure to reset aggregators in the generated affect functions + cbs = process_events(sys; callback, eval_expression, eval_module, reset_jumps = true) + + if rng !== nothing + kwargs = (; kwargs..., rng) + end + return JumpProblem(prob, aggregator, jset; dep_graph = jtoj, vartojumps_map = vtoj, + jumptovars_map = jtov, scale_rates = false, nocopy = true, + callback = cbs, kwargs...) +end + +function check_compatible_system(T::Union{Type{JumpProblem}}, sys::System) + check_time_dependent(sys, T) + check_not_dde(sys) + check_no_cost(sys, T) + check_no_constraints(sys, T) + check_has_jumps(sys, T) + check_is_continuous(sys, T) +end + +###################### parameter mapper ########################### +struct JumpSysMajParamMapper{U, V, W} + paramexprs::U # the parameter expressions to use for each jump rate constant + sympars::V # parameters(sys) from the underlying JumpSystem + subdict::Any # mapping from an element of parameters(sys) to its current numerical value +end + +function JumpSysMajParamMapper(js::System, p; jseqs = nothing, rateconsttype = Float64) + eqs = (jseqs === nothing) ? jumps(js) : jseqs + majs = MassActionJump[x for x in eqs if x isa MassActionJump] + paramexprs = [maj.scaled_rates for maj in majs] + psyms = reduce(vcat, reorder_parameters(js); init = []) + paramdict = Dict(value(k) => value(v) for (k, v) in zip(psyms, vcat(p...))) + JumpSysMajParamMapper{typeof(paramexprs), typeof(psyms), rateconsttype}(paramexprs, + psyms, + paramdict) +end + +function updateparams!(ratemap::JumpSysMajParamMapper{U, V, W}, + params) where {U <: AbstractArray, V <: AbstractArray, W} + for (i, p) in enumerate(params) + sympar = ratemap.sympars[i] + ratemap.subdict[sympar] = p + end + nothing +end + +function updateparams!(ratemap::JumpSysMajParamMapper{U, V, W}, + params::MTKParameters) where {U <: AbstractArray, V <: AbstractArray, W} + for (i, p) in enumerate(ArrayPartition(params...)) + sympar = ratemap.sympars[i] + ratemap.subdict[sympar] = p + end + nothing +end + +function updateparams!(::JumpSysMajParamMapper{U, V, W}, + params::Nothing) where {U <: AbstractArray, V <: AbstractArray, W} + nothing +end + +# update a maj with parameter vectors +function (ratemap::JumpSysMajParamMapper{U, V, W})(maj::MassActionJump, newparams; + scale_rates, + kwargs...) where {U <: AbstractArray, + V <: AbstractArray, W} + updateparams!(ratemap, newparams) + for i in 1:get_num_majumps(maj) + maj.scaled_rates[i] = convert(W, + value(substitute(ratemap.paramexprs[i], + ratemap.subdict))) + end + scale_rates && JumpProcesses.scalerates!(maj.scaled_rates, maj.reactant_stoch) + nothing +end + +# create the initial parameter vector for use in a MassActionJump +function (ratemap::JumpSysMajParamMapper{ + U, + V, + W +})(params) where {U <: AbstractArray, + V <: AbstractArray, W} + updateparams!(ratemap, params) + [convert(W, value(substitute(paramexpr, ratemap.subdict))) + for paramexpr in ratemap.paramexprs] +end + +##### MTK dispatches for Symbolic jumps ##### +eqtype_supports_collect_vars(j::MassActionJump) = true +function collect_vars!(unknowns, parameters, j::MassActionJump, iv; depth = 0, + op = Differential) + collect_vars!(unknowns, parameters, j.scaled_rates, iv; depth, op) + for field in (j.reactant_stoch, j.net_stoch) + for el in field + collect_vars!(unknowns, parameters, el, iv; depth, op) + end + end + return nothing +end + +eqtype_supports_collect_vars(j::Union{ConstantRateJump, VariableRateJump}) = true +function collect_vars!(unknowns, parameters, j::Union{ConstantRateJump, VariableRateJump}, + iv; depth = 0, op = Differential) + collect_vars!(unknowns, parameters, j.rate, iv; depth, op) + for eq in j.affect! + (eq isa Equation) && collect_vars!(unknowns, parameters, eq, iv; depth, op) + end + return nothing +end + +### Functions to determine which unknowns a jump depends on +function get_variables!(dep, jump::Union{ConstantRateJump, VariableRateJump}, variables) + jr = value(jump.rate) + (jr isa Symbolic) && get_variables!(dep, jr, variables) + dep +end + +function get_variables!(dep, jump::MassActionJump, variables) + sr = value(jump.scaled_rates) + (sr isa Symbolic) && get_variables!(dep, sr, variables) + for varasop in jump.reactant_stoch + any(isequal(varasop[1]), variables) && push!(dep, varasop[1]) + end + dep +end + +### Functions to determine which unknowns are modified by a given jump +function modified_unknowns!(munknowns, jump::Union{ConstantRateJump, VariableRateJump}, sts) + for eq in jump.affect! + st = eq.lhs + any(isequal(st), sts) && push!(munknowns, st) + end + munknowns +end + +function modified_unknowns!(munknowns, jump::MassActionJump, sts) + for (unknown, stoich) in jump.net_stoch + any(isequal(unknown), sts) && push!(munknowns, unknown) + end + munknowns +end diff --git a/src/systems/codegen.jl b/src/systems/codegen.jl index acb319377a..60d3c4dd2d 100644 --- a/src/systems/codegen.jl +++ b/src/systems/codegen.jl @@ -561,6 +561,31 @@ function assemble_maj(majv::Vector{U}, unknowntoid, pmapper) where {U <: MassAct MassActionJump(rs, ns; param_mapper = pmapper, nocopy = true) end +function numericrstoich(mtrs::Vector{Pair{V, W}}, unknowntoid) where {V, W} + rs = Vector{Pair{Int, W}}() + for (wspec, stoich) in mtrs + spec = value(wspec) + if !iscall(spec) && _iszero(spec) + push!(rs, 0 => stoich) + else + push!(rs, unknowntoid[spec] => stoich) + end + end + sort!(rs) + rs +end + +function numericnstoich(mtrs::Vector{Pair{V, W}}, unknowntoid) where {V, W} + ns = Vector{Pair{Int, W}}() + for (wspec, stoich) in mtrs + spec = value(wspec) + !iscall(spec) && _iszero(spec) && + error("Net stoichiometry can not have a species labelled 0.") + push!(ns, unknowntoid[spec] => stoich) + end + sort!(ns) +end + """ build_explicit_observed_function(sys, ts; kwargs...) -> Function(s) From e973971d94acee4e521fb96a532ff7dacfe8f09e Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Wed, 14 May 2025 12:11:23 +0530 Subject: [PATCH 093/235] feat: add `InitializationProblem` --- src/ModelingToolkit.jl | 1 + src/problems/initializationproblem.jl | 155 ++++++++++++++++++++++++++ 2 files changed, 156 insertions(+) create mode 100644 src/problems/initializationproblem.jl diff --git a/src/ModelingToolkit.jl b/src/ModelingToolkit.jl index 7e5b7e3caa..f11bb7ad76 100644 --- a/src/ModelingToolkit.jl +++ b/src/ModelingToolkit.jl @@ -179,6 +179,7 @@ include("problems/implicitdiscreteproblem.jl") include("problems/discreteproblem.jl") include("problems/optimizationproblem.jl") include("problems/jumpproblem.jl") +include("problems/initializationproblem.jl") include("systems/optimization/modelingtoolkitize.jl") include("systems/nonlinear/homotopy_continuation.jl") diff --git a/src/problems/initializationproblem.jl b/src/problems/initializationproblem.jl new file mode 100644 index 0000000000..132376e3e6 --- /dev/null +++ b/src/problems/initializationproblem.jl @@ -0,0 +1,155 @@ +struct InitializationProblem{iip, specialization} end + +""" +```julia +InitializationProblem{iip}(sys::AbstractSystem, t, u0map, + parammap = DiffEqBase.NullParameters(); + version = nothing, tgrad = false, + jac = false, + checkbounds = false, sparse = false, + simplify = false, + linenumbers = true, parallel = SerialForm(), + initialization_eqs = [], + fully_determined = false, + kwargs...) where {iip} +``` + +Generates a NonlinearProblem or NonlinearLeastSquaresProblem from a System +which represents the initialization, i.e. the calculation of the consistent +initial conditions for the given DAE. +""" +@fallback_iip_specialize function InitializationProblem{iip, specialize}( + sys::AbstractSystem, + t, u0map = [], + parammap = DiffEqBase.NullParameters(); + guesses = [], + check_length = true, + warn_initialize_determined = true, + initialization_eqs = [], + fully_determined = nothing, + check_units = true, + use_scc = true, + allow_incomplete = false, + force_time_independent = false, + algebraic_only = false, + time_dependent_init = is_time_dependent(sys), + 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`") + end + if isempty(u0map) && get_initializesystem(sys) !== nothing + isys = get_initializesystem(sys; initialization_eqs, check_units) + simplify_system = false + elseif isempty(u0map) && get_initializesystem(sys) === nothing + isys = generate_initializesystem( + sys; initialization_eqs, check_units, pmap = parammap, + guesses, algebraic_only) + simplify_system = true + else + isys = generate_initializesystem( + sys; u0map, initialization_eqs, check_units, time_dependent_init, + pmap = parammap, guesses, algebraic_only) + simplify_system = true + end + + # useful for `SteadyStateProblem` since `f` has to be autonomous and the + # initialization should be too + if force_time_independent + idx = findfirst(isequal(get_iv(sys)), get_ps(isys)) + idx === nothing || deleteat!(get_ps(isys), idx) + end + + if simplify_system + isys = structural_simplify(isys; fully_determined, split = is_split(sys)) + end + + ts = get_tearing_state(isys) + unassigned_vars = StructuralTransformations.singular_check(ts) + if warn_initialize_determined && !isempty(unassigned_vars) + errmsg = """ + The initialization system is structurally singular. Guess values may \ + significantly affect the initial values of the ODE. The problematic variables \ + are $unassigned_vars. + + Note that the identification of problematic variables is a best-effort heuristic. + """ + @warn errmsg + end + + uninit = setdiff(unknowns(sys), [unknowns(isys); observables(isys)]) + + # TODO: throw on uninitialized arrays + filter!(x -> !(x isa Symbolics.Arr), uninit) + if time_dependent_init && !isempty(uninit) + allow_incomplete || throw(IncompleteInitializationError(uninit)) + # for incomplete initialization, we will add the missing variables as parameters. + # they will be updated by `update_initializeprob!` and `initializeprobmap` will + # use them to construct the new `u0`. + newparams = map(toparam, uninit) + append!(get_ps(isys), newparams) + isys = complete(isys) + end + + neqs = length(equations(isys)) + nunknown = length(unknowns(isys)) + + if use_scc + scc_message = "`SCCNonlinearProblem` can only be used for initialization of fully determined systems and hence will not be used here. " + else + scc_message = "" + end + + if warn_initialize_determined && neqs > nunknown + @warn "Initialization system is overdetermined. $neqs equations for $nunknown unknowns. Initialization will default to using least squares. $(scc_message)To suppress this warning pass warn_initialize_determined = false. To make this warning into an error, pass fully_determined = true" + end + if warn_initialize_determined && neqs < nunknown + @warn "Initialization system is underdetermined. $neqs equations for $nunknown unknowns. Initialization will default to using least squares. $(scc_message)To suppress this warning pass warn_initialize_determined = false. To make this warning into an error, pass fully_determined = true" + end + + parammap = recursive_unwrap(anydict(parammap)) + if t !== nothing + parammap[get_iv(sys)] = t + end + filter!(kvp -> kvp[2] !== missing, parammap) + + u0map = to_varmap(u0map, unknowns(sys)) + if isempty(guesses) + guesses = Dict() + end + + filter_missing_values!(u0map) + filter_missing_values!(parammap) + u0map = merge(ModelingToolkit.guesses(sys), todict(guesses), u0map) + + TProb = if neqs == nunknown && isempty(unassigned_vars) + if use_scc && neqs > 0 + if is_split(isys) + SCCNonlinearProblem + else + @warn "`SCCNonlinearProblem` can only be used with `split = true` systems. Simplify your `ODESystem` with `split = true` or pass `use_scc = false` to disable this warning" + NonlinearProblem + end + else + NonlinearProblem + end + else + NonlinearLeastSquaresProblem + end + TProb{iip}(isys, u0map, parammap; kwargs..., + build_initializeprob = false, is_initializeprob = true) +end + +const INCOMPLETE_INITIALIZATION_MESSAGE = """ + Initialization incomplete. Not all of the state variables of the + DAE system can be determined by the initialization. Missing + variables: + """ + +struct IncompleteInitializationError <: Exception + uninit::Any +end + +function Base.showerror(io::IO, e::IncompleteInitializationError) + println(io, INCOMPLETE_INITIALIZATION_MESSAGE) + println(io, e.uninit) +end From b4aa4b01ed3c07c503a4ea527b8e8b4d97389716 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Sun, 20 Apr 2025 21:50:27 +0530 Subject: [PATCH 094/235] feat: implement `NonlinearLeastSquaresProblem` for `System` --- src/problems/nonlinearproblem.jl | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/src/problems/nonlinearproblem.jl b/src/problems/nonlinearproblem.jl index b4f846f3a4..0d3ffad17f 100644 --- a/src/problems/nonlinearproblem.jl +++ b/src/problems/nonlinearproblem.jl @@ -74,8 +74,25 @@ end return maybe_codegen_scimlproblem(expression, NonlinearProblem{iip}, args; kwargs...) end +@fallback_iip_specialize function SciMLBase.NonlinearLeastSquaresProblem{iip, spec}( + sys::System, u0map, parammap = DiffEqBase.NullParameters(); check_length = false, + check_compatibility = true, expression = Val{false}, kwargs...) where {iip, spec} + check_complete(sys, NonlinearLeastSquaresProblem) + check_compatibility && check_compatible_system(NonlinearLeastSquaresProblem, sys) + + f, u0, p = process_SciMLProblem(NonlinearFunction{iip}, sys, u0map, parammap; + check_length, expression, kwargs...) + + kwargs = process_kwargs(sys; kwargs...) + args = (; f, u0, p) + + return maybe_codegen_scimlproblem( + expression, NonlinearLeastSquaresProblem{iip}, args; kwargs...) +end + function check_compatible_system( - T::Union{Type{NonlinearFunction}, Type{NonlinearProblem}}, sys::System) + T::Union{Type{NonlinearFunction}, Type{NonlinearProblem}, + Type{NonlinearLeastSquaresProblem}}, sys::System) check_time_independent(sys, T) check_not_dde(sys) check_no_cost(sys, T) @@ -83,3 +100,13 @@ function check_compatible_system( check_no_jumps(sys, T) check_no_noise(sys, T) end + +function calculate_resid_prototype(N, u0, p) + u0ElType = u0 === nothing ? Float64 : eltype(u0) + if SciMLStructures.isscimlstructure(p) + u0ElType = promote_type( + eltype(SciMLStructures.canonicalize(SciMLStructures.Tunable(), p)[1]), + u0ElType) + end + return zeros(u0ElType, N) +end From c3034b3d1ba53f8710684ad3b0decf609a5db170 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Sun, 20 Apr 2025 21:52:29 +0530 Subject: [PATCH 095/235] refactor: port `SCCNonlinearProblem` to separate file --- src/ModelingToolkit.jl | 1 + src/problems/sccnonlinearproblem.jl | 248 ++++++++++++++++++++++++++++ 2 files changed, 249 insertions(+) create mode 100644 src/problems/sccnonlinearproblem.jl diff --git a/src/ModelingToolkit.jl b/src/ModelingToolkit.jl index f11bb7ad76..2c8a86bbb2 100644 --- a/src/ModelingToolkit.jl +++ b/src/ModelingToolkit.jl @@ -180,6 +180,7 @@ include("problems/discreteproblem.jl") include("problems/optimizationproblem.jl") include("problems/jumpproblem.jl") include("problems/initializationproblem.jl") +include("problems/sccnonlinearproblem.jl") include("systems/optimization/modelingtoolkitize.jl") include("systems/nonlinear/homotopy_continuation.jl") diff --git a/src/problems/sccnonlinearproblem.jl b/src/problems/sccnonlinearproblem.jl new file mode 100644 index 0000000000..0b4174f552 --- /dev/null +++ b/src/problems/sccnonlinearproblem.jl @@ -0,0 +1,248 @@ +const TypeT = Union{DataType, UnionAll} + +struct CacheWriter{F} + fn::F +end + +function (cw::CacheWriter)(p, sols) + cw.fn(p.caches, sols, p) +end + +function CacheWriter(sys::AbstractSystem, buffer_types::Vector{TypeT}, + exprs::Dict{TypeT, Vector{Any}}, solsyms, obseqs::Vector{Equation}; + eval_expression = false, eval_module = @__MODULE__, cse = true) + ps = parameters(sys; initial_parameters = true) + rps = reorder_parameters(sys, ps) + obs_assigns = [eq.lhs ← eq.rhs for eq in obseqs] + body = map(eachindex(buffer_types), buffer_types) do i, T + Symbol(:tmp, i) ← SetArray(true, :(out[$i]), get(exprs, T, [])) + end + + function argument_name(i::Int) + if i <= length(solsyms) + return :($(generated_argument_name(1))[$i]) + end + return generated_argument_name(i - length(solsyms)) + end + array_assignments = array_variable_assignments(solsyms...; argument_name) + fn = build_function_wrapper( + sys, nothing, :out, + DestructuredArgs(DestructuredArgs.(solsyms), generated_argument_name(1)), + rps...; p_start = 3, p_end = length(rps) + 2, + expression = Val{true}, add_observed = false, cse, + extra_assignments = [array_assignments; obs_assigns; body]) + fn = eval_or_rgf(fn; eval_expression, eval_module) + fn = GeneratedFunctionWrapper{(3, 3, is_split(sys))}(fn, nothing) + return CacheWriter(fn) +end + +struct SCCNonlinearFunction{iip} end + +function SCCNonlinearFunction{iip}( + sys::System, _eqs, _dvs, _obs, cachesyms; eval_expression = false, + eval_module = @__MODULE__, cse = true, kwargs...) where {iip} + ps = parameters(sys; initial_parameters = true) + rps = reorder_parameters(sys, ps) + + obs_assignments = [eq.lhs ← eq.rhs for eq in _obs] + + rhss = [eq.rhs - eq.lhs for eq in _eqs] + f_gen = build_function_wrapper(sys, + rhss, _dvs, rps..., cachesyms...; p_start = 2, + p_end = length(rps) + length(cachesyms) + 1, add_observed = false, + extra_assignments = obs_assignments, expression = Val{true}, cse) + f_oop, f_iip = eval_or_rgf.(f_gen; eval_expression, eval_module) + f = GeneratedFunctionWrapper{(2, 2, is_split(sys))}(f_oop, f_iip) + + subsys = NonlinearSystem(_eqs, _dvs, ps; observed = _obs, + parameter_dependencies = parameter_dependencies(sys), name = nameof(sys)) + if get_index_cache(sys) !== nothing + @set! subsys.index_cache = subset_unknowns_observed( + get_index_cache(sys), sys, _dvs, getproperty.(_obs, (:lhs,))) + @set! subsys.complete = true + end + + return NonlinearFunction{iip}(f; sys = subsys) +end + +function SciMLBase.SCCNonlinearProblem(sys::NonlinearSystem, args...; kwargs...) + SCCNonlinearProblem{true}(sys, args...; kwargs...) +end + +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`.") + 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`.") + end + + ts = get_tearing_state(sys) + var_eq_matching, var_sccs = StructuralTransformations.algebraic_variables_scc(ts) + + if length(var_sccs) == 1 + return NonlinearProblem{iip}( + sys, u0map, parammap; eval_expression, eval_module, kwargs...) + end + + condensed_graph = MatchedCondensationGraph( + DiCMOBiGraph{true}(complete(ts.structure.graph), + complete(var_eq_matching)), + var_sccs) + toporder = topological_sort_by_dfs(condensed_graph) + var_sccs = var_sccs[toporder] + eq_sccs = map(Base.Fix1(getindex, var_eq_matching), var_sccs) + + dvs = unknowns(sys) + ps = parameters(sys) + eqs = equations(sys) + obs = observed(sys) + + _, u0, p = process_SciMLProblem( + EmptySciMLFunction{iip}, sys, u0map, parammap; eval_expression, eval_module, kwargs...) + + explicitfuns = [] + nlfuns = [] + prevobsidxs = BlockArray(undef_blocks, Vector{Int}, Int[]) + # Cache buffer types and corresponding sizes. Stored as a pair of arrays instead of a + # dict to maintain a consistent order of buffers across SCCs + cachetypes = TypeT[] + cachesizes = Int[] + # explicitfun! related information for each SCC + # We need to compute buffer sizes before doing any codegen + scc_cachevars = Dict{TypeT, Vector{Any}}[] + scc_cacheexprs = Dict{TypeT, Vector{Any}}[] + scc_eqs = Vector{Equation}[] + scc_obs = Vector{Equation}[] + # variables solved in previous SCCs + available_vars = Set() + for (i, (escc, vscc)) in enumerate(zip(eq_sccs, var_sccs)) + # subset unknowns and equations + _dvs = dvs[vscc] + _eqs = eqs[escc] + # get observed equations required by this SCC + union!(available_vars, _dvs) + obsidxs = observed_equations_used_by(sys, _eqs; available_vars) + # the ones used by previous SCCs can be precomputed into the cache + setdiff!(obsidxs, prevobsidxs) + _obs = obs[obsidxs] + union!(available_vars, getproperty.(_obs, (:lhs,))) + + # get all subexpressions in the RHS which we can precompute in the cache + # precomputed subexpressions should not contain `banned_vars` + banned_vars = Set{Any}(vcat(_dvs, getproperty.(_obs, (:lhs,)))) + state = Dict() + for i in eachindex(_obs) + _obs[i] = _obs[i].lhs ~ subexpressions_not_involving_vars!( + _obs[i].rhs, banned_vars, state) + end + for i in eachindex(_eqs) + _eqs[i] = _eqs[i].lhs ~ subexpressions_not_involving_vars!( + _eqs[i].rhs, banned_vars, state) + end + + # map from symtype to cached variables and their expressions + cachevars = Dict{Union{DataType, UnionAll}, Vector{Any}}() + cacheexprs = Dict{Union{DataType, UnionAll}, Vector{Any}}() + # observed of previous SCCs are in the cache + # NOTE: When we get proper CSE, we can substitute these + # and then use `subexpressions_not_involving_vars!` + for i in prevobsidxs + T = symtype(obs[i].lhs) + buf = get!(() -> Any[], cachevars, T) + push!(buf, obs[i].lhs) + + buf = get!(() -> Any[], cacheexprs, T) + push!(buf, obs[i].lhs) + end + + for (k, v) in state + k = unwrap(k) + v = unwrap(v) + T = symtype(k) + buf = get!(() -> Any[], cachevars, T) + push!(buf, v) + buf = get!(() -> Any[], cacheexprs, T) + push!(buf, k) + end + + # update the sizes of cache buffers + for (T, buf) in cachevars + idx = findfirst(isequal(T), cachetypes) + if idx === nothing + push!(cachetypes, T) + push!(cachesizes, 0) + idx = lastindex(cachetypes) + end + cachesizes[idx] = max(cachesizes[idx], length(buf)) + end + + push!(scc_cachevars, cachevars) + push!(scc_cacheexprs, cacheexprs) + push!(scc_eqs, _eqs) + push!(scc_obs, _obs) + blockpush!(prevobsidxs, obsidxs) + end + + for (i, (escc, vscc)) in enumerate(zip(eq_sccs, var_sccs)) + _dvs = dvs[vscc] + _eqs = scc_eqs[i] + _prevobsidxs = reduce(vcat, blocks(prevobsidxs)[1:(i - 1)]; init = Int[]) + _obs = scc_obs[i] + cachevars = scc_cachevars[i] + cacheexprs = scc_cacheexprs[i] + available_vars = [dvs[reduce(vcat, var_sccs[1:(i - 1)]; init = Int[])]; + getproperty.( + reduce(vcat, scc_obs[1:(i - 1)]; init = []), (:lhs,))] + _prevobsidxs = vcat(_prevobsidxs, + observed_equations_used_by( + sys, reduce(vcat, values(cacheexprs); init = []); available_vars)) + if isempty(cachevars) + push!(explicitfuns, Returns(nothing)) + else + solsyms = getindex.((dvs,), view(var_sccs, 1:(i - 1))) + push!(explicitfuns, + CacheWriter(sys, cachetypes, cacheexprs, solsyms, obs[_prevobsidxs]; + eval_expression, eval_module, cse)) + end + + cachebufsyms = Tuple(map(cachetypes) do T + get(cachevars, T, []) + end) + f = SCCNonlinearFunction{iip}( + sys, _eqs, _dvs, _obs, cachebufsyms; eval_expression, eval_module, cse, kwargs...) + push!(nlfuns, f) + end + + if !isempty(cachetypes) + templates = map(cachetypes, cachesizes) do T, n + # Real refers to `eltype(u0)` + if T == Real + T = eltype(u0) + elseif T <: Array && eltype(T) == Real + T = Array{eltype(u0), ndims(T)} + end + BufferTemplate(T, n) + end + p = rebuild_with_caches(p, templates...) + end + + subprobs = [] + for (f, vscc) in zip(nlfuns, var_sccs) + _u0 = SymbolicUtils.Code.create_array( + typeof(u0), eltype(u0), Val(1), Val(length(vscc)), u0[vscc]...) + prob = NonlinearProblem(f, _u0, p) + push!(subprobs, prob) + end + + new_dvs = dvs[reduce(vcat, var_sccs)] + new_eqs = eqs[reduce(vcat, eq_sccs)] + @set! sys.unknowns = new_dvs + @set! sys.eqs = new_eqs + @set! sys.index_cache = subset_unknowns_observed( + get_index_cache(sys), sys, new_dvs, getproperty.(obs, (:lhs,))) + return SCCNonlinearProblem(subprobs, explicitfuns, p, true; sys) +end From 6a5d841bb8acf04f08fc62ae00e48aefef745966 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Wed, 14 May 2025 11:52:22 +0530 Subject: [PATCH 096/235] feat: implement `BVProblem` for `System` docs: add docstring for new `BVProblem` constructor refactor: improve `BVProblem` validation --- src/ModelingToolkit.jl | 1 + src/problems/bvproblem.jl | 93 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 94 insertions(+) create mode 100644 src/problems/bvproblem.jl diff --git a/src/ModelingToolkit.jl b/src/ModelingToolkit.jl index 2c8a86bbb2..92e139b3e7 100644 --- a/src/ModelingToolkit.jl +++ b/src/ModelingToolkit.jl @@ -181,6 +181,7 @@ include("problems/optimizationproblem.jl") include("problems/jumpproblem.jl") include("problems/initializationproblem.jl") include("problems/sccnonlinearproblem.jl") +include("problems/bvproblem.jl") include("systems/optimization/modelingtoolkitize.jl") include("systems/nonlinear/homotopy_continuation.jl") diff --git a/src/problems/bvproblem.jl b/src/problems/bvproblem.jl new file mode 100644 index 0000000000..b0ad28a842 --- /dev/null +++ b/src/problems/bvproblem.jl @@ -0,0 +1,93 @@ +""" +```julia +SciMLBase.BVProblem{iip}(sys::AbstractSystem, u0map, tspan, + parammap = DiffEqBase.NullParameters(); + constraints = nothing, guesses = nothing, + version = nothing, tgrad = false, + jac = true, sparse = true, + simplify = false, + kwargs...) where {iip} +``` + +Create a boundary value problem from the [`System`](@ref). + +`u0map` is used to specify fixed initial values for the states. Every variable +must have either an initial guess supplied using `guesses` or a fixed initial +value specified using `u0map`. + +Boundary value conditions are supplied to Systems in the form of a list of constraints. +These equations should specify values that state variables should take at specific points, +as in `x(0.5) ~ 1`). More general constraints that should hold over the entire solution, +such as `x(t)^2 + y(t)^2`, should be specified as one of the equations used to build the +`System`. + +If a `System` without `constraints` is specified, it will be treated as an initial value problem. + +```julia + @parameters g t_c = 0.5 + @variables x(..) y(t) λ(t) + eqs = [D(D(x(t))) ~ λ * x(t) + D(D(y)) ~ λ * y - g + x(t)^2 + y^2 ~ 1] + cstr = [x(0.5) ~ 1] + @mtkbuild pend = System(eqs, t; constraints = cstrs) + + tspan = (0.0, 1.5) + u0map = [x(t) => 0.6, y => 0.8] + parammap = [g => 1] + guesses = [λ => 1] + + bvp = SciMLBase.BVProblem{true, SciMLBase.AutoSpecialize}(pend, u0map, tspan, parammap; guesses, check_length = false) +``` + +If the `System` has algebraic equations, like `x(t)^2 + y(t)^2`, the resulting +`BVProblem` must be solved using BVDAE solvers, such as Ascher. +""" +@fallback_iip_specialize function SciMLBase.BVProblem{iip, spec}( + sys::System, u0map, tspan, parammap = SciMLBase.NullParameters(); + check_compatibility = true, cse = true, + checkbounds = false, eval_expression = false, eval_module = @__MODULE__, + expression = Val{false}, guesses = Dict(), callback = nothing, + kwargs...) where {iip, spec} + check_complete(sys, BVProblem) + check_compatibility && check_compatible_system(BVProblem, sys) + isnothing(callback) || error("BVP solvers do not support callbacks.") + + # Systems without algebraic equations should use both fixed values + guesses + # for initialization. + _u0map = has_alg_eqs(sys) ? u0map : merge(Dict(u0map), Dict(guesses)) + + fode, u0, p = process_SciMLProblem( + ODEFunction{iip, spec}, sys, _u0map, parammap; guesses, + t = tspan !== nothing ? tspan[1] : tspan, check_compatibility = false, cse, + checkbounds, time_dependent_init = false, expression, kwargs...) + + dvs = unknowns(sys) + stidxmap = Dict([v => i for (i, v) in enumerate(dvs)]) + u0_idxs = has_alg_eqs(sys) ? collect(1:length(dvs)) : [stidxmap[k] for (k, v) in u0map] + fbc = generate_boundary_conditions( + sys, u0, u0_idxs, tspan[1]; expression = Val{false}, + wrap_gfw = Val{true}, cse, checkbounds) + + if (length(constraints(sys)) + length(u0map) > length(dvs)) + @warn "The BVProblem is overdetermined. The total number of conditions (# constraints + # fixed initial values given by u0map) exceeds the total number of states. The BVP solvers will default to doing a nonlinear least-squares optimization." + end + + kwargs = process_kwargs(sys; expression, kwargs...) + args = (; fode, fbc, u0, tspan, p) + + return maybe_codegen_scimlproblem(expression, BVProblem{iip}, args; kwargs...) +end + +function check_compatible_system(T::Type{BVProblem}, sys::System) + check_time_dependent(sys, T) + check_not_dde(sys) + check_no_cost(sys, T) + check_no_jumps(sys, T) + check_no_noise(sys, T) + check_is_continuous(sys, T) + + if !isempty(discrete_events(sys)) || !isempty(continuous_events(sys)) + throw(SystemCompatibilityError("BVP solvers do not support events.")) + end +end From 4e52917b39e524638c5fda59ceb7dfccfcb6ebdf Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Mon, 21 Apr 2025 12:41:46 +0530 Subject: [PATCH 097/235] feat: implement `SteadyStateProblem` for `System` --- src/problems/odeproblem.jl | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/src/problems/odeproblem.jl b/src/problems/odeproblem.jl index 0d5a146c48..99cff129e0 100644 --- a/src/problems/odeproblem.jl +++ b/src/problems/odeproblem.jl @@ -83,8 +83,39 @@ end maybe_codegen_scimlproblem(expression, ODEProblem{iip}, args; kwargs...) end +""" +```julia +SciMLBase.SteadyStateProblem(sys::System, u0map, + parammap = DiffEqBase.NullParameters(); + version = nothing, tgrad = false, + jac = false, + checkbounds = false, sparse = false, + linenumbers = true, parallel = SerialForm(), + kwargs...) where {iip} +``` + +Generates an SteadyStateProblem from a `System` of ODEs and allows for automatically +symbolically calculating numerical enhancements. +""" +@fallback_iip_specialize function DiffEqBase.SteadyStateProblem{iip, spec}( + sys::System, u0map, parammap; check_length = true, check_compatibility = true, + expression = Val{false}, kwargs...) where {iip, spec} + check_complete(sys, SteadyStateProblem) + check_compatibility && check_compatible_system(SteadyStateProblem, sys) + + f, u0, p = process_SciMLProblem(ODEFunction{iip}, sys, u0map, parammap; + steady_state = true, check_length, check_compatibility, expression, + force_initialization_time_independent = true, kwargs...) + + kwargs = process_kwargs(sys; expression, kwargs...) + args = (; f, u0, p) + + maybe_codegen_scimlproblem(expression, SteadyStateProblem{iip}, args; kwargs...) +end + function check_compatible_system( - T::Union{Type{ODEFunction}, Type{ODEProblem}, Type{DAEFunction}, Type{DAEProblem}}, + T::Union{Type{ODEFunction}, Type{ODEProblem}, Type{DAEFunction}, + Type{DAEProblem}, Type{SteadyStateProblem}}, sys::System) check_time_dependent(sys, T) check_not_dde(sys) From 37d7f6ad51c673c032af2e526a0deaba02d86d8f Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Mon, 21 Apr 2025 12:42:52 +0530 Subject: [PATCH 098/235] fix: fix `toexpr(::AbstractSystem)` --- src/systems/abstractsystem.jl | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/systems/abstractsystem.jl b/src/systems/abstractsystem.jl index 2adc879e94..5d9a80d6d1 100644 --- a/src/systems/abstractsystem.jl +++ b/src/systems/abstractsystem.jl @@ -1990,8 +1990,8 @@ function push_eqs!(stmt, eqs, var2name) return eqs_name end -function push_defaults!(stmt, defs, var2name) - defs_name = gensym(:defs) +function push_defaults!(stmt, defs, var2name; name = :defs) + defs_name = gensym(name) defs_expr = Expr(:call, Dict) defs_blk = Expr(:(=), defs_name, defs_expr) for d in defs @@ -2039,23 +2039,23 @@ function toexpr(sys::AbstractSystem) eqs_name = push_eqs!(stmt, full_equations(sys), var2name) filtered_defs = filter( kvp -> !(iscall(kvp[1]) && operation(kvp[1]) isa Initial), defaults(sys)) + filtered_guesses = filter( + kvp -> !(iscall(kvp[1]) && operation(kvp[1]) isa Initial), guesses(sys)) defs_name = push_defaults!(stmt, filtered_defs, var2name) + guesses_name = push_defaults!(stmt, filtered_guesses, var2name; name = :guesses) obs_name = push_eqs!(stmt, obs, var2name) - if sys isa ODESystem - iv = get_iv(sys) + iv = get_iv(sys) + if iv === nothing + ivname = nothing + else ivname = gensym(:iv) push!(stmt, :($ivname = (@variables $(getname(iv)))[1])) - push!(stmt, - :($ODESystem($eqs_name, $ivname, $stsname, $psname; defaults = $defs_name, - observed = $obs_name, - name = $name, checks = false))) - elseif sys isa NonlinearSystem - push!(stmt, - :($NonlinearSystem($eqs_name, $stsname, $psname; defaults = $defs_name, - observed = $obs_name, - name = $name, checks = false))) end + push!(stmt, + :($System($eqs_name, $ivname, $stsname, $psname; defaults = $defs_name, + guesses = $guesses_name, observed = $obs_name, + name = $name, checks = false))) expr = :(let $expr From 60d73b984941e1c4d2a61f5cd56140ac753bbf05 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Mon, 21 Apr 2025 12:43:12 +0530 Subject: [PATCH 099/235] fix: fix `extend(::AbstractSystem)` --- src/systems/abstractsystem.jl | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/systems/abstractsystem.jl b/src/systems/abstractsystem.jl index 5d9a80d6d1..f013c786b5 100644 --- a/src/systems/abstractsystem.jl +++ b/src/systems/abstractsystem.jl @@ -2693,11 +2693,9 @@ function extend(sys::AbstractSystem, basesys::AbstractSystem; name = name, description = description, gui_metadata = gui_metadata) # collect fields specific to some system types - if basesys isa ODESystem - ieqs = union(get_initialization_eqs(basesys), get_initialization_eqs(sys)) - guesses = merge(get_guesses(basesys), get_guesses(sys)) # prefer `sys` - kwargs = merge(kwargs, (initialization_eqs = ieqs, guesses = guesses)) - end + ieqs = union(get_initialization_eqs(basesys), get_initialization_eqs(sys)) + guesses = merge(get_guesses(basesys), get_guesses(sys)) # prefer `sys` + kwargs = merge(kwargs, (initialization_eqs = ieqs, guesses = guesses)) if has_assertions(basesys) kwargs = merge( From 81e8c98a484c59e842da30a1a2c6e54ef9ad7695 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Mon, 21 Apr 2025 12:43:45 +0530 Subject: [PATCH 100/235] fix: fix `substitute(::AbstractSystem, _...)` --- src/systems/abstractsystem.jl | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/systems/abstractsystem.jl b/src/systems/abstractsystem.jl index f013c786b5..a314e20449 100644 --- a/src/systems/abstractsystem.jl +++ b/src/systems/abstractsystem.jl @@ -2796,7 +2796,7 @@ function Symbolics.substitute(sys::AbstractSystem, rules::Union{Vector{<:Pair}, # post-walk to avoid infinite recursion @set! sys.systems = map(Base.Fix2(substitute, dict), systems) something(get(rules, nameof(sys), nothing), sys) - elseif sys isa ODESystem + elseif sys isa System rules = todict(map(r -> Symbolics.unwrap(r[1]) => Symbolics.unwrap(r[2]), collect(rules))) eqs = fast_substitute(get_eqs(sys), rules) @@ -2805,9 +2805,15 @@ function Symbolics.substitute(sys::AbstractSystem, rules::Union{Vector{<:Pair}, for (k, v) in get_defaults(sys)) guess = Dict(fast_substitute(k, rules) => fast_substitute(v, rules) for (k, v) in get_guesses(sys)) + noise_eqs = fast_substitute(get_noise_eqs(sys), rules) + costs = fast_substitute(get_costs(sys), rules) + observed = fast_substitute(get_observed(sys), rules) + initialization_eqs = fast_substitute(get_initialization_eqs(sys), rules) + cstrs = fast_substitute(get_constraints(sys), rules) subsys = map(s -> substitute(s, rules), get_systems(sys)) - ODESystem(eqs, get_iv(sys); name = nameof(sys), defaults = defs, - guesses = guess, parameter_dependencies = pdeps, systems = subsys) + System(eqs, get_iv(sys); name = nameof(sys), defaults = defs, + guesses = guess, parameter_dependencies = pdeps, systems = subsys, noise_eqs, + observed, initialization_eqs, constraints = cstrs) else error("substituting symbols is not supported for $(typeof(sys))") end From 3f5428ad7fe23263e6b77c05515408d55fb325a8 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Mon, 21 Apr 2025 12:44:12 +0530 Subject: [PATCH 101/235] fix: construct `System` in `@mtkmodel` --- src/systems/model_parsing.jl | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/systems/model_parsing.jl b/src/systems/model_parsing.jl index 2a852813a2..fdb26ba7ec 100644 --- a/src/systems/model_parsing.jl +++ b/src/systems/model_parsing.jl @@ -72,8 +72,7 @@ function _model_macro(mod, fullname::Union{Expr, Symbol}, expr, isconnector) push!(exprs.args, :(variables = [])) push!(exprs.args, :(parameters = [])) - # We build `System` by default; vectors can't be created for `System` as it is - # a function. + # We build `System` by default push!(exprs.args, :(systems = ModelingToolkit.AbstractSystem[])) push!(exprs.args, :(equations = Union{Equation, Vector{Equation}}[])) push!(exprs.args, :(defaults = Dict{Num, Union{Number, Symbol, Function}}())) From 8556dee7d8168845435d5212e53648028e9153cc Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Mon, 21 Apr 2025 12:44:31 +0530 Subject: [PATCH 102/235] docs: fix docstring of `process_SciMLProblem` --- src/systems/problem_utils.jl | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/systems/problem_utils.jl b/src/systems/problem_utils.jl index 867ee80ddb..df59dc201c 100644 --- a/src/systems/problem_utils.jl +++ b/src/systems/problem_utils.jl @@ -1173,12 +1173,11 @@ Initial values provided in terms of other variables will be symbolically evaluat type of the containers (if parameters are not in an `MTKParameters` object). `Dict`s will be turned into `Array`s. -If `sys isa ODESystem`, this will also build the initialization problem and related objects -and pass them to the SciMLFunction as keyword arguments. +This will also build the initialization problem and related objects and pass them to the +SciMLFunction as keyword arguments. Keyword arguments: -- `build_initializeprob`: If `false`, avoids building the initialization problem for an - `ODESystem`. +- `build_initializeprob`: If `false`, avoids building the initialization problem. - `t`: The initial time of the `ODEProblem`. If this is not provided, the initialization problem cannot be built. - `implicit_dae`: Also build a mapping of derivatives of states to values for implicit DAEs, From 1048557043711f5dd4f13d04ebff3ec27813243c Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Fri, 25 Apr 2025 19:10:09 +0530 Subject: [PATCH 103/235] refactor: allow real values in `costs` --- src/systems/system.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/systems/system.jl b/src/systems/system.jl index d89bdb0f8e..a638debfb4 100644 --- a/src/systems/system.jl +++ b/src/systems/system.jl @@ -19,7 +19,7 @@ struct System <: AbstractSystem noise_eqs::Union{Nothing, AbstractVector, AbstractMatrix} jumps::Vector{JumpType} constraints::Vector{Union{Equation, Inequality}} - costs::Vector{<:BasicSymbolic} + costs::Vector{<:Union{BasicSymbolic, Real}} consolidate::Any unknowns::Vector ps::Vector From fcc91421489e489b9166392b4019ae40ba7fc8ed Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Mon, 21 Apr 2025 12:54:14 +0530 Subject: [PATCH 104/235] refactor: remove old clock handling code, retain error messages --- src/systems/systemstructure.jl | 66 +++++++++++----------------------- 1 file changed, 20 insertions(+), 46 deletions(-) diff --git a/src/systems/systemstructure.jl b/src/systems/systemstructure.jl index 1b561f97b4..c6f023e0e1 100644 --- a/src/systems/systemstructure.jl +++ b/src/systems/systemstructure.jl @@ -662,54 +662,28 @@ function structural_simplify!(state::TearingState; simplify = false, inputs = Any[], outputs = Any[], disturbance_inputs = Any[], 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, 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, - inputs = cont_inputs, outputs, disturbance_inputs, - kwargs...) - if length(tss) > 1 - if continuous_id > 0 - throw(HybridSystemNotSupportedException("Hybrid continuous-discrete systems are currently not supported with the standard MTK compiler. This system requires JuliaSimCompiler.jl, see https://help.juliahub.com/juliasimcompiler/stable/")) - end - # TODO: rename it to something else - discrete_subsystems = Vector{ODESystem}(undef, length(tss)) - # Note that the appended_parameters must agree with - # `generate_discrete_affect`! - appended_parameters = parameters(sys) - for (i, state) in enumerate(tss) - if i == continuous_id - discrete_subsystems[i] = sys - continue - end - 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 - end - @set! sys.discrete_subsystems = discrete_subsystems, inputs, continuous_id, - id_to_clock - @set! sys.ps = appended_parameters - @set! sys.defaults = merge(ModelingToolkit.defaults(sys), - Dict(v => 0.0 for v in Iterators.flatten(inputs))) + 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, clocked_inputs, continuous_id, id_to_clock = ModelingToolkit.split_system(ci) + if length(tss) > 1 + if continuous_id == 0 + throw(HybridSystemNotSupportedException(""" + Discrete systems with multiple clocks are not supported with the standard \ + MTK compiler. + """)) + else + throw(HybridSystemNotSupportedException(""" + Hybrid continuous-discrete systems are currently not supported with \ + the standard MTK compiler. This system requires JuliaSimCompiler.jl, \ + see https://help.juliahub.com/juliasimcompiler/stable/ + """)) end - ps = [sym isa CallWithMetadata ? sym : - setmetadata( - sym, VariableTimeDomain, get(time_domains, sym, ContinuousClock())) - for sym in get_ps(sys)] - @set! sys.ps = ps - else - sys = _structural_simplify!(state; simplify, check_consistency, - inputs, outputs, disturbance_inputs, - fully_determined, kwargs...) end + sys = _structural_simplify!(state; simplify, check_consistency, + inputs, outputs, disturbance_inputs, + fully_determined, kwargs...) return sys end From 1b1ea3d59a2494818c029975b80c99b841e65006 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Mon, 21 Apr 2025 12:54:47 +0530 Subject: [PATCH 105/235] fix: allow simplifying systems with noise --- src/systems/systems.jl | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/systems/systems.jl b/src/systems/systems.jl index 399b263178..f6992a2d03 100644 --- a/src/systems/systems.jl +++ b/src/systems/systems.jl @@ -67,6 +67,11 @@ function __structural_simplify(sys::AbstractSystem; simplify = false, disturbance_inputs = Any[], sort_eqs = true, kwargs...) + # TODO: convert noise_eqs to brownians for simplification + if has_noise_eqs(sys) && get_noise_eqs(sys) !== nothing + sys = noise_to_brownians(sys; names = :αₘₜₖ) + end + sys = expand_connections(sys) state = TearingState(sys; sort_eqs) From 239664678683f6a3877e3dacb8977a99647bd58f Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Mon, 21 Apr 2025 12:57:44 +0530 Subject: [PATCH 106/235] refactor: remove `schedule(sys)` --- src/systems/abstractsystem.jl | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/src/systems/abstractsystem.jl b/src/systems/abstractsystem.jl index a314e20449..ebd314f82a 100644 --- a/src/systems/abstractsystem.jl +++ b/src/systems/abstractsystem.jl @@ -604,17 +604,6 @@ end """ $(TYPEDSIGNATURES) -Mark a system as scheduled. It is only intended in compiler internals. A system -is scheduled after tearing based simplifications where equations are converted -into assignments. -""" -function schedule(sys::AbstractSystem) - has_schedule(sys) ? sys : (@set! sys.isscheduled = true) -end - -""" -$(TYPEDSIGNATURES) - If a system is scheduled, then changing its equations, variables, and parameters is no longer legal. """ From 001f2b7fc4564431d8ed805bc7871d1cf14a9edf Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Mon, 21 Apr 2025 12:58:17 +0530 Subject: [PATCH 107/235] feat: set system scheduling information in `structural_simplify` --- src/structural_transformation/symbolics_tearing.jl | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/structural_transformation/symbolics_tearing.jl b/src/structural_transformation/symbolics_tearing.jl index d41cbf12d2..ac37c918dd 100644 --- a/src/structural_transformation/symbolics_tearing.jl +++ b/src/structural_transformation/symbolics_tearing.jl @@ -756,11 +756,13 @@ function update_simplified_system!( @set! sys.substitutions = Substitutions(subeqs, deps) # Only makes sense for time-dependent - # TODO: generalize to SDE - if sys isa ODESystem + if ModelingToolkit.has_schedule(sys) @set! sys.schedule = Schedule(var_eq_matching, dummy_sub) end - sys = schedule(sys) + if ModelingToolkit.has_isscheduled(sys) + @set! sys.isscheduled = true + end + return sys end """ From 260d2b19a616c83ba714d9adb1ba91734b89da56 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Mon, 21 Apr 2025 13:01:03 +0530 Subject: [PATCH 108/235] refactor: remove references to `ODESystem` in source code --- ext/MTKBifurcationKitExt.jl | 10 +++--- ext/MTKFMIExt.jl | 2 +- ext/MTKInfiniteOptExt.jl | 14 ++++---- src/ModelingToolkit.jl | 2 +- src/inputoutput.jl | 12 +++---- src/linearization.jl | 10 +++--- .../StructuralTransformations.jl | 2 +- src/structural_transformation/pantelides.jl | 6 ++-- .../symbolics_tearing.jl | 4 +-- src/systems/abstractsystem.jl | 36 +++++++++---------- src/systems/analysis_points.jl | 6 ++-- src/systems/diffeqs/basic_transformations.jl | 16 ++++----- src/systems/diffeqs/first_order_transform.jl | 6 ++-- src/systems/diffeqs/modelingtoolkitize.jl | 4 +-- src/systems/if_lifting.jl | 5 ++- src/systems/optimal_control_interface.jl | 8 ++--- src/systems/systems.jl | 6 +--- src/systems/systemstructure.jl | 2 +- src/utils.jl | 5 +-- 19 files changed, 78 insertions(+), 78 deletions(-) diff --git a/ext/MTKBifurcationKitExt.jl b/ext/MTKBifurcationKitExt.jl index 0b9f104d9b..bcb3152702 100644 --- a/ext/MTKBifurcationKitExt.jl +++ b/ext/MTKBifurcationKitExt.jl @@ -23,7 +23,7 @@ struct ObservableRecordFromSolution{S, T} # A Vector of pairs (Symbolic => value) with the default values of all system variables and parameters. subs_vals::T - function ObservableRecordFromSolution(nsys::NonlinearSystem, + function ObservableRecordFromSolution(nsys::System, plot_var, bif_idx, u0_vals, @@ -82,7 +82,7 @@ end ### Creates BifurcationProblem Overloads ### # When input is a NonlinearSystem. -function BifurcationKit.BifurcationProblem(nsys::NonlinearSystem, +function BifurcationKit.BifurcationProblem(nsys::System, u0_bif, ps, bif_par, @@ -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 `System` 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. @@ -144,11 +144,11 @@ function BifurcationKit.BifurcationProblem(nsys::NonlinearSystem, end # When input is a ODESystem. -function BifurcationKit.BifurcationProblem(osys::ODESystem, args...; kwargs...) +function BifurcationKit.BifurcationProblem(osys::System, args...; kwargs...) if !ModelingToolkit.iscomplete(osys) 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)], + nsys = System([0 ~ eq.rhs for eq in full_equations(osys)], unknowns(osys), parameters(osys); observed = observed(osys), diff --git a/ext/MTKFMIExt.jl b/ext/MTKFMIExt.jl index d155544ce9..1c70a385a6 100644 --- a/ext/MTKFMIExt.jl +++ b/ext/MTKFMIExt.jl @@ -289,7 +289,7 @@ function MTK.FMIComponent(::Val{Ver}; fmu = nothing, tolerance = 1e-6, end eqs = [observed; diffeqs] - return ODESystem(eqs, t, states, params; parameter_dependencies, defaults = defs, + return System(eqs, t, states, params; parameter_dependencies, defaults = defs, discrete_events = [instance_management_callback], name, initialization_eqs) end diff --git a/ext/MTKInfiniteOptExt.jl b/ext/MTKInfiniteOptExt.jl index e36b9c2a3e..dd7da24672 100644 --- a/ext/MTKInfiniteOptExt.jl +++ b/ext/MTKInfiniteOptExt.jl @@ -39,9 +39,9 @@ struct InfiniteOptDynamicOptProblem{uType, tType, isinplace, P, F, K} <: end """ - JuMPDynamicOptProblem(sys::ODESystem, u0, tspan, p; dt) + JuMPDynamicOptProblem(sys::System, u0, tspan, p; dt) -Convert an ODESystem representing an optimal control system into a JuMP model +Convert a System representing an optimal control system into a JuMP model for solving using optimization. Must provide either `dt`, the timestep between collocation points (which, along with the timespan, determines the number of points), or directly provide the number of points as `steps`. @@ -51,10 +51,10 @@ The optimization variables: - a vector-of-vectors V representing the controls as an interpolation array The constraints are: -- The set of user constraints passed to the ODESystem via `constraints` +- The set of user constraints passed to the System via `constraints` - The solver constraints that encode the time-stepping used by the solver """ -function MTK.JuMPDynamicOptProblem(sys::ODESystem, u0map, tspan, pmap; +function MTK.JuMPDynamicOptProblem(sys::System, u0map, tspan, pmap; dt = nothing, steps = nothing, guesses = Dict(), kwargs...) @@ -71,16 +71,16 @@ function MTK.JuMPDynamicOptProblem(sys::ODESystem, u0map, tspan, pmap; end """ - InfiniteOptDynamicOptProblem(sys::ODESystem, u0map, tspan, pmap; dt) + InfiniteOptDynamicOptProblem(sys::System, u0map, tspan, pmap; dt) -Convert an ODESystem representing an optimal control system into a InfiniteOpt model +Convert System representing an optimal control system into a InfiniteOpt model for solving using optimization. Must provide `dt` for determining the length of the interpolation arrays. Related to `JuMPDynamicOptProblem`, but directly adds the differential equations of the system as derivative constraints, rather than using a solver tableau. """ -function MTK.InfiniteOptDynamicOptProblem(sys::ODESystem, u0map, tspan, pmap; +function MTK.InfiniteOptDynamicOptProblem(sys::System, u0map, tspan, pmap; dt = nothing, steps = nothing, guesses = Dict(), kwargs...) diff --git a/src/ModelingToolkit.jl b/src/ModelingToolkit.jl index 92e139b3e7..6d567c0625 100644 --- a/src/ModelingToolkit.jl +++ b/src/ModelingToolkit.jl @@ -236,7 +236,7 @@ const D = Differential(t) PrecompileTools.@compile_workload begin using ModelingToolkit @variables x(ModelingToolkit.t_nounits) - @named sys = ODESystem([ModelingToolkit.D_nounits(x) ~ -x], ModelingToolkit.t_nounits) + @named sys = System([ModelingToolkit.D_nounits(x) ~ -x], ModelingToolkit.t_nounits) prob = ODEProblem(structural_simplify(sys), [x => 30.0], (0, 100), [], jac = true) @mtkmodel __testmod__ begin @constants begin diff --git a/src/inputoutput.jl b/src/inputoutput.jl index 283be6ed92..1beb229664 100644 --- a/src/inputoutput.jl +++ b/src/inputoutput.jl @@ -161,7 +161,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, + sys::System, inputs = unbound_inputs(sys), disturbance_inputs = disturbances(sys); implicit_dae = false, @@ -191,7 +191,7 @@ t = 0 f[1](x, inputs, p, t) ``` """ -function generate_control_function(sys::AbstractODESystem, inputs = unbound_inputs(sys), +function generate_control_function(sys::AbstractSystem, inputs = unbound_inputs(sys), disturbance_inputs = disturbances(sys); disturbance_argument = false, implicit_dae = false, @@ -333,7 +333,7 @@ The structure represents a model of a disturbance, along with the input variable # Fields: - `input`: The variable affected by the disturbance. - - `model::M`: A model of the disturbance. This is typically an `ODESystem`, but type that implements [`ModelingToolkit.get_disturbance_system`](@ref)`(dist::DisturbanceModel) -> ::ODESystem` is supported. + - `model::M`: A model of the disturbance. This is typically a `System`, but type that implements [`ModelingToolkit.get_disturbance_system`](@ref)`(dist::DisturbanceModel) -> ::System` is supported. """ struct DisturbanceModel{M} input::Any @@ -343,7 +343,7 @@ end DisturbanceModel(input, model; name) = DisturbanceModel(input, model, name) # Point of overloading for libraries, e.g., to be able to support disturbance models from ControlSystemsBase -function get_disturbance_system(dist::DisturbanceModel{<:ODESystem}) +function get_disturbance_system(dist::DisturbanceModel{System}) dist.model end @@ -384,7 +384,7 @@ c = 10 # Damping coefficient eqs = [connect(torque.flange, inertia1.flange_a) connect(inertia1.flange_b, spring.flange_a, damper.flange_a) connect(inertia2.flange_a, spring.flange_b, damper.flange_b)] -model = ODESystem(eqs, t; systems = [torque, inertia1, inertia2, spring, damper], +model = System(eqs, t; systems = [torque, inertia1, inertia2, spring, damper], name = :model) model = complete(model) model_outputs = [model.inertia1.w, model.inertia2.w, model.inertia1.phi, model.inertia2.phi] @@ -416,7 +416,7 @@ function add_input_disturbance(sys, dist::DisturbanceModel, inputs = Any[]; kwar eqs = [dsys.input.u[1] ~ d dist.input ~ u + dsys.output.u[1]] - augmented_sys = ODESystem(eqs, t, systems = [dsys], name = gensym(:outer)) + augmented_sys = System(eqs, t, systems = [dsys], name = gensym(:outer)) augmented_sys = extend(augmented_sys, sys) ssys = structural_simplify(augmented_sys, inputs = all_inputs, disturbance_inputs = [d]) diff --git a/src/linearization.jl b/src/linearization.jl index dd474ea1a0..8d6edf8f07 100644 --- a/src/linearization.jl +++ b/src/linearization.jl @@ -19,7 +19,7 @@ The `simplified_sys` has undergone [`structural_simplify`](@ref) and had any occ # Arguments: - - `sys`: An [`ODESystem`](@ref). This function will automatically apply simplification passes on `sys` and return the resulting `simplified_sys`. + - `sys`: A [`System`](@ref) of ODEs. 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. @@ -671,7 +671,7 @@ function plant(; name) @variables u(t)=0 y(t)=0 eqs = [D(x) ~ -x + u y ~ x] - ODESystem(eqs, t; name = name) + System(eqs, t; name = name) end function ref_filt(; name) @@ -679,7 +679,7 @@ function ref_filt(; name) @variables u(t)=0 [input = true] eqs = [D(x) ~ -2 * x + u y ~ x] - ODESystem(eqs, t, name = name) + System(eqs, t, name = name) end function controller(kp; name) @@ -688,7 +688,7 @@ function controller(kp; name) eqs = [ u ~ kp * (r - y), ] - ODESystem(eqs, t; name = name) + System(eqs, t; name = name) end @named f = ref_filt() @@ -699,7 +699,7 @@ connections = [f.y ~ c.r # filtered reference to controller reference c.u ~ p.u # controller output to plant input p.y ~ c.y] -@named cl = ODESystem(connections, t, systems = [f, c, p]) +@named cl = System(connections, t, systems = [f, c, p]) lsys0, ssys = linearize(cl, [f.u], [p.x]) desired_order = [f.x, p.x] diff --git a/src/structural_transformation/StructuralTransformations.jl b/src/structural_transformation/StructuralTransformations.jl index 4adc817ef8..16d3a75464 100644 --- a/src/structural_transformation/StructuralTransformations.jl +++ b/src/structural_transformation/StructuralTransformations.jl @@ -11,7 +11,7 @@ using SymbolicUtils.Rewriters using SymbolicUtils: maketerm, iscall using ModelingToolkit -using ModelingToolkit: ODESystem, AbstractSystem, var_from_nested_derivative, Differential, +using ModelingToolkit: System, AbstractSystem, var_from_nested_derivative, Differential, unknowns, equations, vars, Symbolic, diff2term_with_unit, shift2term_with_unit, value, operation, arguments, Sym, Term, simplify, symbolic_linear_solve, diff --git a/src/structural_transformation/pantelides.jl b/src/structural_transformation/pantelides.jl index 585c4a29d1..53c790863f 100644 --- a/src/structural_transformation/pantelides.jl +++ b/src/structural_transformation/pantelides.jl @@ -196,7 +196,7 @@ function pantelides!( eq′ = eq_to_diff[eq′] end # for _ in 1:maxiters pathfound || - error("maxiters=$maxiters reached! File a bug report if your system has a reasonable index (<100), and you are using the default `maxiters`. Try to increase the maxiters by `pantelides(sys::ODESystem; maxiters=1_000_000)` if your system has an incredibly high index and it is truly extremely large.") + error("maxiters=$maxiters reached! File a bug report if your system has a reasonable index (<100), and you are using the default `maxiters`. Try to increase the maxiters by `pantelides(sys::System; maxiters=1_000_000)` if your system has an incredibly high index and it is truly extremely large.") end # for k in 1:neqs′ finalize && for var in 1:ndsts(graph) @@ -207,13 +207,13 @@ function pantelides!( end """ - dae_index_lowering(sys::ODESystem; kwargs...) -> ODESystem + dae_index_lowering(sys::System; kwargs...) -> System 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) instead, which calls this function internally. """ -function dae_index_lowering(sys::ODESystem; kwargs...) +function dae_index_lowering(sys::System; kwargs...) state = TearingState(sys) var_eq_matching = pantelides!(state; finalize = false, kwargs...) return invalidate_cache!(pantelides_reassemble(state, var_eq_matching)) diff --git a/src/structural_transformation/symbolics_tearing.jl b/src/structural_transformation/symbolics_tearing.jl index ac37c918dd..60d24e69ce 100644 --- a/src/structural_transformation/symbolics_tearing.jl +++ b/src/structural_transformation/symbolics_tearing.jl @@ -40,7 +40,7 @@ function var_derivative_graph!(s::SystemStructure, v::Int) return var_diff end -function var_derivative!(ts::TearingState{ODESystem}, v::Int) +function var_derivative!(ts::TearingState, v::Int) s = ts.structure var_diff = var_derivative_graph!(s, v) sys = ts.sys @@ -58,7 +58,7 @@ function eq_derivative_graph!(s::SystemStructure, eq::Int) return eq_diff end -function eq_derivative!(ts::TearingState{ODESystem}, ieq::Int; kwargs...) +function eq_derivative!(ts::TearingState, ieq::Int; kwargs...) s = ts.structure eq_diff = eq_derivative_graph!(s, ieq) diff --git a/src/systems/abstractsystem.jl b/src/systems/abstractsystem.jl index ebd314f82a..f28b0f1559 100644 --- a/src/systems/abstractsystem.jl +++ b/src/systems/abstractsystem.jl @@ -1673,7 +1673,7 @@ function defaults(sys::AbstractSystem) # `mapfoldr` is really important!!! We should prefer the base model for # defaults, because people write: # - # `compose(ODESystem(...; defaults=defs), ...)` + # `compose(System(...; defaults=defs), ...)` # # Thus, right associativity is required and crucial for correctness. isempty(systems) ? defs : mapfoldr(namespace_defaults, merge, systems; init = defs) @@ -2869,7 +2869,7 @@ using ModelingToolkit: t, D @parameters p = 1.0, [description = "My parameter", tunable = false] q = 2.0, [description = "Other parameter"] @variables x(t) = 3.0 [unit = u"m"] -@named sys = ODESystem(Equation[], t, [x], [p, q]) +@named sys = System(Equation[], t, [x], [p, q]) ModelingToolkit.dump_parameters(sys) ``` @@ -2910,7 +2910,7 @@ using ModelingToolkit: t, D @parameters p = 1.0, [description = "My parameter", tunable = false] q = 2.0, [description = "Other parameter"] @variables x(t) = 3.0 [unit = u"m"] -@named sys = ODESystem(Equation[], t, [x], [p, q]) +@named sys = System(Equation[], t, [x], [p, q]) ModelingToolkit.dump_unknowns(sys) ``` @@ -3088,7 +3088,7 @@ using ModelingToolkit: t_nounits as t, D_nounits as D @variables X(t) eq1 = D(X) ~ p - d*X eq2 = 0 ~ p - d*X -@named osys = ODESystem([eq1, eq2], t) +@named osys = System([eq1, eq2], t) alg_equations(osys) # returns `[0 ~ p - d*X(t)]`. """ @@ -3107,7 +3107,7 @@ using ModelingToolkit: t_nounits as t, D_nounits as D @variables X(t) eq1 = D(X) ~ p - d*X eq2 = 0 ~ p - d*X -@named osys = ODESystem([eq1, eq2], t) +@named osys = System([eq1, eq2], t) diff_equations(osys) # returns `[Differential(t)(X(t)) ~ p - d*X(t)]`. """ @@ -3127,8 +3127,8 @@ using ModelingToolkit: t_nounits as t, D_nounits as D @variables X(t) eq1 = D(X) ~ p - d*X eq2 = 0 ~ p - d*X -@named osys1 = ODESystem([eq1], t) -@named osys2 = ODESystem([eq2], t) +@named osys1 = System([eq1], t) +@named osys2 = System([eq2], t) has_alg_equations(osys1) # returns `false`. has_alg_equations(osys2) # returns `true`. @@ -3149,8 +3149,8 @@ using ModelingToolkit: t_nounits as t, D_nounits as D @variables X(t) eq1 = D(X) ~ p - d*X eq2 = 0 ~ p - d*X -@named osys1 = ODESystem([eq1], t) -@named osys2 = ODESystem([eq2], t) +@named osys1 = System([eq1], t) +@named osys2 = System([eq2], t) has_diff_equations(osys1) # returns `true`. has_diff_equations(osys2) # returns `false`. @@ -3172,9 +3172,9 @@ using ModelingToolkit: t_nounits as t, D_nounits as D @variables X(t) eq1 = D(X) ~ p - d*X eq2 = 0 ~ p - d*X -@named osys1 = ODESystem([eq1], t) -@named osys2 = ODESystem([eq2], t) -osys12 = compose(osys1, [osys2]) +@named osys1 = ([eq1], t) +@named osys2 = ([eq2], t) +osys12 = compose(sys1, [osys2]) osys21 = compose(osys2, [osys1]) get_alg_eqs(osys12) # returns `Equation[]`. @@ -3197,8 +3197,8 @@ using ModelingToolkit: t_nounits as t, D_nounits as D @variables X(t) eq1 = D(X) ~ p - d*X eq2 = 0 ~ p - d*X -@named osys1 = ODESystem([eq1], t) -@named osys2 = ODESystem([eq2], t) +@named osys1 = tem([eq1], t) +@named osys2 = tem([eq2], t) osys12 = compose(osys1, [osys2]) osys21 = compose(osys2, [osys1]) @@ -3222,8 +3222,8 @@ using ModelingToolkit: t_nounits as t, D_nounits as D @variables X(t) eq1 = D(X) ~ p - d*X eq2 = 0 ~ p - d*X -@named osys1 = ODESystem([eq1], t) -@named osys2 = ODESystem([eq2], t) +@named osys1 = System([eq1], t) +@named osys2 = System([eq2], t) osys12 = compose(osys1, [osys2]) osys21 = compose(osys2, [osys1]) @@ -3248,8 +3248,8 @@ using ModelingToolkit: t_nounits as t, D_nounits as D @variables X(t) eq1 = D(X) ~ p - d*X eq2 = 0 ~ p - d*X -@named osys1 = ODESystem([eq1], t) -@named osys2 = ODESystem([eq2], t) +@named osys1 = tem([eq1], t) +@named osys2 = tem([eq2], t) osys12 = compose(osys1, [osys2]) osys21 = compose(osys2, [osys1]) diff --git a/src/systems/analysis_points.jl b/src/systems/analysis_points.jl index 0d1a2830cf..27a0204cb8 100644 --- a/src/systems/analysis_points.jl +++ b/src/systems/analysis_points.jl @@ -31,7 +31,7 @@ t = ModelingToolkit.get_iv(P) eqs = [connect(P.output, C.input) connect(C.output, :plant_input, P.input)] -sys = ODESystem(eqs, t, systems = [P, C], name = :feedback_system) +sys = System(eqs, t, systems = [P, C], name = :feedback_system) matrices_S, _ = get_sensitivity(sys, :plant_input) # Compute the matrices of a state-space representation of the (input) sensitivity function. matrices_T, _ = get_comp_sensitivity(sys, :plant_input) @@ -1007,12 +1007,12 @@ See also [`get_sensitivity`](@ref), [`get_comp_sensitivity`](@ref), [`open_loop` # """ - generate_control_function(sys::ModelingToolkit.AbstractODESystem, input_ap_name::Union{Symbol, Vector{Symbol}, AnalysisPoint, Vector{AnalysisPoint}}, dist_ap_name::Union{Symbol, Vector{Symbol}, AnalysisPoint, Vector{AnalysisPoint}}; system_modifier = identity, kwargs) + generate_control_function(sys::ModelingToolkit.AbstractSystem, input_ap_name::Union{Symbol, Vector{Symbol}, AnalysisPoint, Vector{AnalysisPoint}}, dist_ap_name::Union{Symbol, Vector{Symbol}, AnalysisPoint, Vector{AnalysisPoint}}; system_modifier = identity, kwargs) When called with analysis points as input arguments, we assume that all analysis points corresponds to connections that should be opened (broken). The use case for this is to get rid of input signal blocks, such as `Step` or `Sine`, since these are useful for simulation but are not needed when using the plant model in a controller or state estimator. """ function generate_control_function( - sys::ModelingToolkit.AbstractODESystem, input_ap_name::Union{ + sys::ModelingToolkit.AbstractSystem, input_ap_name::Union{ Symbol, Vector{Symbol}, AnalysisPoint, Vector{AnalysisPoint}}, dist_ap_name::Union{ Nothing, Symbol, Vector{Symbol}, AnalysisPoint, Vector{AnalysisPoint}} = nothing; diff --git a/src/systems/diffeqs/basic_transformations.jl b/src/systems/diffeqs/basic_transformations.jl index 3c1081fcbe..9fa90681cd 100644 --- a/src/systems/diffeqs/basic_transformations.jl +++ b/src/systems/diffeqs/basic_transformations.jl @@ -21,7 +21,7 @@ using ModelingToolkit, OrdinaryDiffEq @variables x(t) y(t) D = Differential(t) eqs = [D(x) ~ α*x - β*x*y, D(y) ~ -δ*y + γ*x*y] -@named sys = ODESystem(eqs, t) +@named sys = System(eqs, t) sys2 = liouville_transform(sys) sys2 = complete(sys2) @@ -40,14 +40,14 @@ Optimal Transport Approach Abhishek Halder, Kooktae Lee, and Raktim Bhattacharya https://abhishekhalder.bitbucket.io/F16ACC2013Final.pdf """ -function liouville_transform(sys::AbstractODESystem; kwargs...) +function liouville_transform(sys::System; kwargs...) t = get_iv(sys) @variables trJ - D = ModelingToolkit.Differential(t) + D = Differential(t) neweq = D(trJ) ~ trJ * -tr(calculate_jacobian(sys)) neweqs = [equations(sys); neweq] vars = [unknowns(sys); trJ] - ODESystem( + System( neweqs, t, vars, parameters(sys); checks = false, name = nameof(sys), kwargs... ) @@ -55,7 +55,7 @@ end """ change_independent_variable( - sys::AbstractODESystem, iv, eqs = []; + sys::System, iv, eqs = []; add_old_diff = false, simplify = true, fold = false ) @@ -95,7 +95,7 @@ By changing the independent variable, it can be reformulated for vertical positi ```julia julia> @variables x(t) y(t); -julia> @named M = ODESystem([D(D(y)) ~ -9.81, D(D(x)) ~ 0.0], t); +julia> @named M = System([D(D(y)) ~ -9.81, D(D(x)) ~ 0.0], t); julia> M = change_independent_variable(M, x); @@ -109,7 +109,7 @@ julia> unknowns(M) ``` """ function change_independent_variable( - sys::AbstractODESystem, iv, eqs = []; + sys::System, iv, eqs = []; add_old_diff = false, simplify = true, fold = false ) iv2_of_iv1 = unwrap(iv) # e.g. u(t) @@ -201,7 +201,7 @@ function change_independent_variable( end # Use the utility function to transform everything in the system! - function transform(sys::AbstractODESystem) + function transform(sys::System) systems = map(transform, get_systems(sys)) # recurse through subsystems # transform equations and connections systems_map = Dict(get_name(s) => s for s in systems) diff --git a/src/systems/diffeqs/first_order_transform.jl b/src/systems/diffeqs/first_order_transform.jl index 97fd6460d9..d017ea362b 100644 --- a/src/systems/diffeqs/first_order_transform.jl +++ b/src/systems/diffeqs/first_order_transform.jl @@ -1,10 +1,10 @@ """ $(TYPEDSIGNATURES) -Takes a Nth order ODESystem and returns a new ODESystem written in first order +Takes a Nth order System and returns a new System written in first order form by defining new variables which represent the N-1 derivatives. """ -function ode_order_lowering(sys::ODESystem) +function ode_order_lowering(sys::System) iv = get_iv(sys) eqs_lowered, new_vars = ode_order_lowering(equations(sys), iv, unknowns(sys)) @set! sys.eqs = eqs_lowered @@ -12,7 +12,7 @@ function ode_order_lowering(sys::ODESystem) return sys end -function dae_order_lowering(sys::ODESystem) +function dae_order_lowering(sys::System) iv = get_iv(sys) eqs_lowered, new_vars = dae_order_lowering(equations(sys), iv, unknowns(sys)) @set! sys.eqs = eqs_lowered diff --git a/src/systems/diffeqs/modelingtoolkitize.jl b/src/systems/diffeqs/modelingtoolkitize.jl index b2954f81e4..ab13ecca29 100644 --- a/src/systems/diffeqs/modelingtoolkitize.jl +++ b/src/systems/diffeqs/modelingtoolkitize.jl @@ -1,7 +1,7 @@ """ $(TYPEDSIGNATURES) -Generate `ODESystem`, dependent variables, and parameters from an `ODEProblem`. +Generate `System`, dependent variables, and parameters from an `ODEProblem`. """ function modelingtoolkitize( prob::DiffEqBase.ODEProblem; u_names = nothing, p_names = nothing, kwargs...) @@ -95,7 +95,7 @@ function modelingtoolkitize( end filter!(x -> !iscall(x) || !(operation(x) isa Initial), params) filter!(x -> !iscall(x[1]) || !(operation(x[1]) isa Initial), default_p) - de = ODESystem(eqs, t, sts, params, + de = System(eqs, t, sts, params, defaults = merge(default_u0, default_p); name = gensym(:MTKizedODE), tspan = prob.tspan, diff --git a/src/systems/if_lifting.jl b/src/systems/if_lifting.jl index aeb8afc0a8..130a4bdab4 100644 --- a/src/systems/if_lifting.jl +++ b/src/systems/if_lifting.jl @@ -411,7 +411,10 @@ Lifting proceeds through the following process: * rewrite comparisons to be of the form eqn [op] 0; subtract the RHS from the LHS * replace comparisons with generated parameters; for each comparison eqn [op] 0, generate an event (dependent on op) that sets the parameter """ -function IfLifting(sys::ODESystem) +function IfLifting(sys::System) + if !is_time_dependent(sys) + throw(ArgumentError("IfLifting is only supported for time-dependent systems.")) + end cw = CondRewriter(get_iv(sys)) eqs = copy(equations(sys)) diff --git a/src/systems/optimal_control_interface.jl b/src/systems/optimal_control_interface.jl index fa1157dca8..2ee2d0e9ca 100644 --- a/src/systems/optimal_control_interface.jl +++ b/src/systems/optimal_control_interface.jl @@ -51,7 +51,7 @@ is_explicit(tableau) = tableau isa DiffEqBase.ExplicitRKTableau Generate the control function f(x, u, p, t) from the ODESystem. Input variables are automatically inferred but can be manually specified. """ -function SciMLBase.ODEInputFunction{iip, specialize}(sys::ODESystem, +function SciMLBase.ODEInputFunction{iip, specialize}(sys::System, dvs = unknowns(sys), ps = parameters(sys), u0 = nothing, inputs = unbound_inputs(sys), @@ -147,16 +147,16 @@ function SciMLBase.ODEInputFunction{iip, specialize}(sys::ODESystem, initialization_data) end -function SciMLBase.ODEInputFunction(sys::AbstractODESystem, args...; kwargs...) +function SciMLBase.ODEInputFunction(sys::System, args...; kwargs...) ODEInputFunction{true}(sys, args...; kwargs...) end -function SciMLBase.ODEInputFunction{true}(sys::AbstractODESystem, args...; +function SciMLBase.ODEInputFunction{true}(sys::System, args...; kwargs...) ODEInputFunction{true, SciMLBase.AutoSpecialize}(sys, args...; kwargs...) end -function SciMLBase.ODEInputFunction{false}(sys::AbstractODESystem, args...; +function SciMLBase.ODEInputFunction{false}(sys::System, args...; kwargs...) ODEInputFunction{false, SciMLBase.FullSpecialize}(sys, args...; kwargs...) end diff --git a/src/systems/systems.jl b/src/systems/systems.jl index f6992a2d03..29584825fe 100644 --- a/src/systems/systems.jl +++ b/src/systems/systems.jl @@ -42,7 +42,7 @@ function structural_simplify( for pass in additional_passes newsys = pass(newsys) end - if newsys isa ODESystem || has_parent(newsys) + if has_parent(newsys) @set! newsys.parent = complete(sys; split = false, flatten = false) end newsys = complete(newsys; split) @@ -58,10 +58,6 @@ function __structural_simplify(sys::JumpSystem, args...; kwargs...) return sys end -function __structural_simplify(sys::SDESystem, args...; kwargs...) - return __structural_simplify(ODESystem(sys), args...; kwargs...) -end - function __structural_simplify(sys::AbstractSystem; simplify = false, inputs = Any[], outputs = Any[], disturbance_inputs = Any[], diff --git a/src/systems/systemstructure.jl b/src/systems/systemstructure.jl index c6f023e0e1..611ff72095 100644 --- a/src/systems/systemstructure.jl +++ b/src/systems/systemstructure.jl @@ -9,7 +9,7 @@ import ..ModelingToolkit: isdiffeq, var_from_nested_derivative, vars!, flatten, independent_variables, SparseMatrixCLIL, AbstractSystem, equations, isirreducible, input_timedomain, TimeDomain, InferredTimeDomain, - VariableType, getvariabletype, has_equations, ODESystem + VariableType, getvariabletype, has_equations, System using ..BipartiteGraphs import ..BipartiteGraphs: invview, complete using Graphs diff --git a/src/utils.jl b/src/utils.jl index be28fa3401..33856c71dc 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -1208,7 +1208,8 @@ end """ $(TYPEDSIGNATURES) -Find all the unknowns and parameters from the equations of a SDESystem or ODESystem. Return re-ordered equations, differential variables, all variables, and parameters. +Find all the unknowns and parameters from the equations of a System. Return re-ordered +equations, differential variables, all variables, and parameters. """ function process_equations(eqs, iv) if eltype(eqs) <: AbstractVector @@ -1244,7 +1245,7 @@ function process_equations(eqs, iv) diffvar, _ = var_from_nested_derivative(eq.lhs) if check_scope_depth(getmetadata(diffvar, SymScope, LocalScope()), 0) isequal(iv, iv_from_nested_derivative(eq.lhs)) || - throw(ArgumentError("An ODESystem can only have one independent variable.")) + throw(ArgumentError("A system of differential equations can only have one independent variable.")) diffvar in diffvars && throw(ArgumentError("The differential variable $diffvar is not unique in the system of equations.")) !has_diffvar_type(diffvar) && From 28999f316113d4a9962396ca060389e40af8fa71 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Mon, 21 Apr 2025 13:01:25 +0530 Subject: [PATCH 109/235] refactor: remove `__structural_simplify(::JumpSystem)` --- src/systems/systems.jl | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/systems/systems.jl b/src/systems/systems.jl index 29584825fe..4e70476f94 100644 --- a/src/systems/systems.jl +++ b/src/systems/systems.jl @@ -54,10 +54,6 @@ function structural_simplify( end end -function __structural_simplify(sys::JumpSystem, args...; kwargs...) - return sys -end - function __structural_simplify(sys::AbstractSystem; simplify = false, inputs = Any[], outputs = Any[], disturbance_inputs = Any[], From 330d0e76d6260c509122d45dde0a35df71a35a8c Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Mon, 21 Apr 2025 15:30:21 +0530 Subject: [PATCH 110/235] refactor: remove references to `SDESystem` in source code --- src/systems/diffeqs/basic_transformations.jl | 4 ++-- src/systems/diffeqs/modelingtoolkitize.jl | 4 ++-- src/systems/nonlinear/initializesystem.jl | 1 + src/systems/systems.jl | 4 ++-- src/utils.jl | 2 +- 5 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/systems/diffeqs/basic_transformations.jl b/src/systems/diffeqs/basic_transformations.jl index 9fa90681cd..21dcc7977a 100644 --- a/src/systems/diffeqs/basic_transformations.jl +++ b/src/systems/diffeqs/basic_transformations.jl @@ -297,7 +297,7 @@ Measure transformation method that allows for a reduction in the variance of an Input: Original SDE system and symbolic function `u(t,x)` with scalar output that defines the adjustable parameters `d` in the Girsanov transformation. Optional: initial condition for `θ0`. -Output: Modified SDESystem with additional component `θ_t` and initial value `θ0`, as well as +Output: Modified SDE System with additional component `θ_t` and initial value `θ0`, as well as the weight `θ_t/θ0` as observed equation, such that the estimator `Exp(g(X_t)θ_t/θ0)` has a smaller variance. @@ -317,7 +317,7 @@ using ModelingToolkit: t_nounits as t, D_nounits as D eqs = [D(x) ~ α*x] noiseeqs = [β*x] -@named de = SDESystem(eqs,noiseeqs,t,[x],[α,β]) +@named de = System(eqs,t,[x],[α,β]; noise_eqs = noiseeqs) # define u (user choice) u = x diff --git a/src/systems/diffeqs/modelingtoolkitize.jl b/src/systems/diffeqs/modelingtoolkitize.jl index ab13ecca29..cfa3ac43dd 100644 --- a/src/systems/diffeqs/modelingtoolkitize.jl +++ b/src/systems/diffeqs/modelingtoolkitize.jl @@ -226,7 +226,7 @@ end """ $(TYPEDSIGNATURES) -Generate `SDESystem`, dependent variables, and parameters from an `SDEProblem`. +Generate `System`, dependent variables, and parameters from an `SDEProblem`. """ function modelingtoolkitize(prob::DiffEqBase.SDEProblem; kwargs...) prob.f isa DiffEqBase.AbstractParameterizedFunction && @@ -293,7 +293,7 @@ function modelingtoolkitize(prob::DiffEqBase.SDEProblem; kwargs...) Dict() end - de = SDESystem(deqs, neqs, t, sts, params; + de = System(deqs, t, sts, params; noise_eqs = neqs, name = gensym(:MTKizedSDE), tspan = prob.tspan, defaults = merge(default_u0, default_p), diff --git a/src/systems/nonlinear/initializesystem.jl b/src/systems/nonlinear/initializesystem.jl index 0396074300..0c60ac2ca1 100644 --- a/src/systems/nonlinear/initializesystem.jl +++ b/src/systems/nonlinear/initializesystem.jl @@ -90,6 +90,7 @@ function generate_initializesystem_timevarying(sys::AbstractSystem; end end else + # TODO: Check if this is still necessary # 2) System doesn't have a schedule, so dummy derivatives don't exist/aren't handled (SDESystem) for (k, v) in u0map defs[k] = v diff --git a/src/systems/systems.jl b/src/systems/systems.jl index 4e70476f94..1aaa07eaea 100644 --- a/src/systems/systems.jl +++ b/src/systems/systems.jl @@ -145,8 +145,8 @@ function __structural_simplify(sys::AbstractSystem; simplify = false, end noise_eqs = StructuralTransformations.tearing_substitute_expr(ode_sys, noise_eqs) - ssys = SDESystem(Vector{Equation}(full_equations(ode_sys)), noise_eqs, - get_iv(ode_sys), unknowns(ode_sys), parameters(ode_sys); + ssys = System(Vector{Equation}(full_equations(ode_sys)), + get_iv(ode_sys), unknowns(ode_sys), parameters(ode_sys); noise_eqs, 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), diff --git a/src/utils.jl b/src/utils.jl index 33856c71dc..a12b251384 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -1314,7 +1314,7 @@ have different multiplicities in `a` and `b`. """ function _eq_unordered(a::AbstractArray, b::AbstractArray) # a and b may be multidimensional - # e.g. comparing noiseeqs of SDESystem + # e.g. comparing noiseeqs of SDEs a = vec(a) b = vec(b) length(a) === length(b) || return false From 35b0ae3b34decab3523bedf9d0db1e137245bd98 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Mon, 21 Apr 2025 15:35:37 +0530 Subject: [PATCH 111/235] refactor: remove references to `NonlinearSystem` --- src/problems/sccnonlinearproblem.jl | 8 ++--- src/systems/codegen.jl | 2 +- .../nonlinear/homotopy_continuation.jl | 30 +++++++++---------- src/systems/nonlinear/initializesystem.jl | 10 +++---- src/systems/nonlinear/modelingtoolkitize.jl | 4 +-- 5 files changed, 26 insertions(+), 28 deletions(-) diff --git a/src/problems/sccnonlinearproblem.jl b/src/problems/sccnonlinearproblem.jl index 0b4174f552..36330e339f 100644 --- a/src/problems/sccnonlinearproblem.jl +++ b/src/problems/sccnonlinearproblem.jl @@ -54,7 +54,7 @@ function SCCNonlinearFunction{iip}( f_oop, f_iip = eval_or_rgf.(f_gen; eval_expression, eval_module) f = GeneratedFunctionWrapper{(2, 2, is_split(sys))}(f_oop, f_iip) - subsys = NonlinearSystem(_eqs, _dvs, ps; observed = _obs, + subsys = System(_eqs, _dvs, ps; observed = _obs, parameter_dependencies = parameter_dependencies(sys), name = nameof(sys)) if get_index_cache(sys) !== nothing @set! subsys.index_cache = subset_unknowns_observed( @@ -65,15 +65,15 @@ function SCCNonlinearFunction{iip}( return NonlinearFunction{iip}(f; sys = subsys) end -function SciMLBase.SCCNonlinearProblem(sys::NonlinearSystem, args...; kwargs...) +function SciMLBase.SCCNonlinearProblem(sys::System, args...; kwargs...) SCCNonlinearProblem{true}(sys, args...; kwargs...) end -function SciMLBase.SCCNonlinearProblem{iip}(sys::NonlinearSystem, u0map, +function SciMLBase.SCCNonlinearProblem{iip}(sys::System, 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 `System` is required. Call `structural_simplify` on the system before creating an `SCCNonlinearProblem`.") end if !is_split(sys) diff --git a/src/systems/codegen.jl b/src/systems/codegen.jl index 60d3c4dd2d..1ca879322a 100644 --- a/src/systems/codegen.jl +++ b/src/systems/codegen.jl @@ -625,7 +625,7 @@ The signatures will be of the form `g(...)` with arguments: - `unknowns` if `param_only` is `false` - `inputs` if `inputs` is an array of symbolic inputs that should be available in `ts` - `p...` unconditionally; note that in the case of `MTKParameters` more than one parameters argument may be present, so it must be splatted -- `t` if the system is time-dependent; for example `NonlinearSystem` will not have `t` +- `t` if the system is time-dependent; for example systems of nonlinear equations will not have `t` For example, a function `g(op, unknowns, p..., inputs, t)` will be the in-place function generated if `return_inplace` is true, `ts` is a vector, an array of inputs `inputs` is given, and `param_only` is false for a time-dependent system. diff --git a/src/systems/nonlinear/homotopy_continuation.jl b/src/systems/nonlinear/homotopy_continuation.jl index 9a77779103..e68737c19b 100644 --- a/src/systems/nonlinear/homotopy_continuation.jl +++ b/src/systems/nonlinear/homotopy_continuation.jl @@ -211,7 +211,7 @@ end """ $(TYPEDEF) -Information representing how to transform a `NonlinearSystem` into a polynomial +Information representing how to transform a `System` into a polynomial system. """ struct PolynomialTransformation @@ -236,7 +236,7 @@ struct PolynomialTransformation polydata::Vector{PolynomialData} end -function PolynomialTransformation(sys::NonlinearSystem) +function PolynomialTransformation(sys::System) # we need to consider `full_equations` because observed also should be # polynomials (if used in equations) and we don't know if observed is used # in denominator. @@ -342,7 +342,7 @@ using the appropriate `PolynomialTransformation`. Also contains the denominators in the equations, to rule out invalid roots. """ struct PolynomialTransformationResult - sys::NonlinearSystem + sys::System denominators::Vector{BasicSymbolic} end @@ -353,7 +353,7 @@ Transform the system `sys` with `transformation` and return a `PolynomialTransformationResult`, or a `NotPolynomialError` if the system cannot be transformed. """ -function transform_system(sys::NonlinearSystem, transformation::PolynomialTransformation; +function transform_system(sys::System, transformation::PolynomialTransformation; fraction_cancel_fn = simplify_fractions) subrules = transformation.substitution_rules dvs = unknowns(sys) @@ -473,26 +473,26 @@ function handle_rational_polynomials(x, wrt; fraction_cancel_fn = simplify_fract return num, den end -function SciMLBase.HomotopyNonlinearFunction(sys::NonlinearSystem, args...; kwargs...) +function SciMLBase.HomotopyNonlinearFunction(sys::System, args...; kwargs...) ODEFunction{true}(sys, args...; kwargs...) end -function SciMLBase.HomotopyNonlinearFunction{true}(sys::NonlinearSystem, args...; +function SciMLBase.HomotopyNonlinearFunction{true}(sys::System, args...; kwargs...) ODEFunction{true, SciMLBase.AutoSpecialize}(sys, args...; kwargs...) end -function SciMLBase.HomotopyNonlinearFunction{false}(sys::NonlinearSystem, args...; +function SciMLBase.HomotopyNonlinearFunction{false}(sys::System, args...; kwargs...) ODEFunction{false, SciMLBase.FullSpecialize}(sys, args...; kwargs...) end function SciMLBase.HomotopyNonlinearFunction{iip, specialize}( - sys::NonlinearSystem, args...; eval_expression = false, eval_module = @__MODULE__, + sys::System, args...; eval_expression = false, eval_module = @__MODULE__, 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 `System` is required. Call `complete` or `structural_simplify` on the system before creating a `HomotopyContinuationFunction`") end transformation = PolynomialTransformation(sys) if transformation isa NotPolynomialError @@ -529,11 +529,11 @@ end struct HomotopyContinuationProblem{iip, specialization} end -function HomotopyContinuationProblem(sys::NonlinearSystem, args...; kwargs...) +function HomotopyContinuationProblem(sys::System, args...; kwargs...) HomotopyContinuationProblem{true}(sys, args...; kwargs...) end -function HomotopyContinuationProblem(sys::NonlinearSystem, t, +function HomotopyContinuationProblem(sys::System, t, u0map::StaticArray, args...; kwargs...) @@ -541,19 +541,19 @@ function HomotopyContinuationProblem(sys::NonlinearSystem, t, sys, t, u0map, args...; kwargs...) end -function HomotopyContinuationProblem{true}(sys::NonlinearSystem, args...; kwargs...) +function HomotopyContinuationProblem{true}(sys::System, args...; kwargs...) HomotopyContinuationProblem{true, SciMLBase.AutoSpecialize}(sys, args...; kwargs...) end -function HomotopyContinuationProblem{false}(sys::NonlinearSystem, args...; kwargs...) +function HomotopyContinuationProblem{false}(sys::System, args...; kwargs...) HomotopyContinuationProblem{false, SciMLBase.FullSpecialize}(sys, args...; kwargs...) end function HomotopyContinuationProblem{iip, spec}( - sys::NonlinearSystem, u0map, pmap = SciMLBase.NullParameters(); + sys::System, 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 `System` 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/initializesystem.jl b/src/systems/nonlinear/initializesystem.jl index 0c60ac2ca1..1e7e4dc85c 100644 --- a/src/systems/nonlinear/initializesystem.jl +++ b/src/systems/nonlinear/initializesystem.jl @@ -10,7 +10,7 @@ end """ $(TYPEDSIGNATURES) -Generate `NonlinearSystem` which initializes a problem from specified initial conditions of an `AbstractTimeDependentSystem`. +Generate `System` of nonlinear equations which initializes a problem from specified initial conditions of an `AbstractTimeDependentSystem`. """ function generate_initializesystem_timevarying(sys::AbstractSystem; u0map = Dict(), @@ -153,8 +153,7 @@ function generate_initializesystem_timevarying(sys::AbstractSystem; for k in keys(defs) defs[k] = substitute(defs[k], paramsubs) end - - return NonlinearSystem(eqs_ics, + return System(eqs_ics, vars, pars; defaults = defs, @@ -168,7 +167,7 @@ end """ $(TYPEDSIGNATURES) -Generate `NonlinearSystem` which initializes a problem from specified initial conditions of an `AbstractTimeDependentSystem`. +Generate `System` of nonlinear equations which initializes a problem from specified initial conditions of an `AbstractTimeDependentSystem`. """ function generate_initializesystem_timeindependent(sys::AbstractSystem; u0map = Dict(), @@ -252,8 +251,7 @@ function generate_initializesystem_timeindependent(sys::AbstractSystem; for k in keys(defs) defs[k] = substitute(defs[k], paramsubs) end - - return NonlinearSystem(eqs_ics, + return System(eqs_ics, vars, pars; defaults = defs, diff --git a/src/systems/nonlinear/modelingtoolkitize.jl b/src/systems/nonlinear/modelingtoolkitize.jl index 2f12157884..4ce3769ef7 100644 --- a/src/systems/nonlinear/modelingtoolkitize.jl +++ b/src/systems/nonlinear/modelingtoolkitize.jl @@ -1,7 +1,7 @@ """ $(TYPEDSIGNATURES) -Generate `NonlinearSystem`, dependent variables, and parameters from an `NonlinearProblem`. +Generate `System`, dependent variables, and parameters from an `NonlinearProblem`. """ function modelingtoolkitize( prob::Union{NonlinearProblem, NonlinearLeastSquaresProblem}; @@ -76,7 +76,7 @@ function modelingtoolkitize( Dict() end - de = NonlinearSystem(eqs, sts, params, + de = System(eqs, sts, params, defaults = merge(default_u0, default_p); name = gensym(:MTKizedNonlinProb), kwargs...) From 25808618899fe4d313f086f26f93cced16326134 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Mon, 21 Apr 2025 16:25:24 +0530 Subject: [PATCH 112/235] refactor: remove references to `DiscreteSystem` --- src/ModelingToolkit.jl | 1 - src/systems/abstractsystem.jl | 2 +- src/systems/systems.jl | 3 --- src/systems/systemstructure.jl | 10 ++++++---- 4 files changed, 7 insertions(+), 9 deletions(-) diff --git a/src/ModelingToolkit.jl b/src/ModelingToolkit.jl index 6d567c0625..23741dfe43 100644 --- a/src/ModelingToolkit.jl +++ b/src/ModelingToolkit.jl @@ -132,7 +132,6 @@ abstract type AbstractTimeDependentSystem <: AbstractSystem end abstract type AbstractTimeIndependentSystem <: AbstractSystem end abstract type AbstractMultivariateSystem <: AbstractSystem end abstract type AbstractOptimizationSystem <: AbstractTimeIndependentSystem end -abstract type AbstractDiscreteSystem <: AbstractTimeDependentSystem end function independent_variable end diff --git a/src/systems/abstractsystem.jl b/src/systems/abstractsystem.jl index f28b0f1559..099d569d2f 100644 --- a/src/systems/abstractsystem.jl +++ b/src/systems/abstractsystem.jl @@ -699,7 +699,7 @@ function add_initialization_parameters(sys::AbstractSystem; split = true) end # add derivatives of all variables for steady-state initial conditions - if is_time_dependent(sys) && !(sys isa AbstractDiscreteSystem) + if is_time_dependent(sys) && !is_discrete_system(sys) D = Differential(get_iv(sys)) union!(all_initialvars, [D(v) for v in all_initialvars if iscall(v)]) end diff --git a/src/systems/systems.jl b/src/systems/systems.jl index 1aaa07eaea..44a31ab921 100644 --- a/src/systems/systems.jl +++ b/src/systems/systems.jl @@ -36,9 +36,6 @@ function structural_simplify( else newsys = newsys′ end - if newsys isa DiscreteSystem && - any(eq -> symbolic_type(eq.lhs) == NotSymbolic(), equations(newsys)) - end for pass in additional_passes newsys = pass(newsys) end diff --git a/src/systems/systemstructure.jl b/src/systems/systemstructure.jl index 611ff72095..144aad148e 100644 --- a/src/systems/systemstructure.jl +++ b/src/systems/systemstructure.jl @@ -474,11 +474,9 @@ function TearingState(sys; quick_cancel = false, check = true, sort_eqs = true) ts = TearingState(sys, fullvars, SystemStructure(complete(var_to_diff), complete(eq_to_diff), - complete(graph), nothing, var_types, sys isa AbstractDiscreteSystem), + complete(graph), nothing, var_types, false), Any[], param_derivative_map) - if sys isa DiscreteSystem - ts = shift_discrete_system(ts) - end + return ts end @@ -681,6 +679,10 @@ function structural_simplify!(state::TearingState; simplify = false, """)) end end + if continuous_id == 1 && any(Base.Fix2(isoperator, Shift), state.fullvars) + state.structure.only_discrete = true + end + sys = _structural_simplify!(state; simplify, check_consistency, inputs, outputs, disturbance_inputs, fully_determined, kwargs...) From aa7961eda277f1491ff9442c1c7111bdc0a56ec8 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Tue, 22 Apr 2025 16:54:06 +0530 Subject: [PATCH 113/235] refactor: do not use `sys.substitutions` --- src/structural_transformation/symbolics_tearing.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/structural_transformation/symbolics_tearing.jl b/src/structural_transformation/symbolics_tearing.jl index 60d24e69ce..04c88d1d52 100644 --- a/src/structural_transformation/symbolics_tearing.jl +++ b/src/structural_transformation/symbolics_tearing.jl @@ -150,7 +150,7 @@ end function tearing_substitution(sys::AbstractSystem; kwargs...) neweqs = full_equations(sys::AbstractSystem; kwargs...) @set! sys.eqs = neweqs - @set! sys.substitutions = nothing + # @set! sys.substitutions = nothing @set! sys.schedule = nothing end @@ -753,7 +753,7 @@ function update_simplified_system!( @set! sys.eqs = neweqs @set! sys.observed = obs - @set! sys.substitutions = Substitutions(subeqs, deps) + # @set! sys.substitutions = Substitutions(subeqs, deps) # Only makes sense for time-dependent if ModelingToolkit.has_schedule(sys) From 83489081101a0263a235888daab887ba9d2a79aa Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Wed, 14 May 2025 15:23:03 +0530 Subject: [PATCH 114/235] test: replace `ODESystem` with `System` --- test/accessor_functions.jl | 8 +- test/analysis_points.jl | 16 +- test/basic_transformations.jl | 32 +- test/bvproblem.jl | 32 +- test/causal_variables_connection.jl | 8 +- test/clock.jl | 18 +- test/code_generation.jl | 6 +- test/components.jl | 36 +-- test/constants.jl | 8 +- test/dae_jacobian.jl | 2 +- test/debugging.jl | 4 +- test/dep_graphs.jl | 2 +- test/distributed.jl | 2 +- test/domain_connectors.jl | 12 +- test/downstream/analysis_points.jl | 52 ++-- test/downstream/inversemodel.jl | 2 +- test/downstream/linearization_dd.jl | 2 +- test/downstream/linearize.jl | 22 +- test/downstream/test_disturbance_model.jl | 4 +- test/dq_units.jl | 38 +-- test/equation_type_accessors.jl | 6 +- test/error_handling.jl | 8 +- test/extensions/ad.jl | 10 +- test/extensions/bifurcationkit.jl | 2 +- test/extensions/dynamic_optimization.jl | 24 +- test/fmi/fmi.jl | 12 +- test/funcaffect.jl | 32 +- test/function_registration.jl | 8 +- test/guess_propagation.jl | 16 +- test/hierarchical_initialization_eqs.jl | 12 +- test/index_cache.jl | 16 +- test/initial_values.jl | 46 +-- test/initializationsystem.jl | 92 +++--- test/input_output_handling.jl | 54 ++-- test/inputoutput.jl | 6 +- test/jacobiansparsity.jl | 4 +- test/labelledarrays.jl | 2 +- test/latexify.jl | 2 +- test/linearity.jl | 6 +- test/lowering_solving.jl | 10 +- test/mass_matrix.jl | 8 +- test/model_parsing.jl | 10 +- test/modelingtoolkitize.jl | 2 +- test/mtkparameters.jl | 22 +- test/namespacing.jl | 6 +- test/nonlinearsystem.jl | 14 +- test/odesystem.jl | 282 +++++++++--------- test/parameter_dependencies.jl | 36 +-- test/precompile_test/ODEPrecompileTest.jl | 2 +- test/problem_validation.jl | 4 +- test/reduction.jl | 36 +-- test/scc_nonlinear_problem.jl | 4 +- test/sciml_problem_inputs.jl | 2 +- test/sdesystem.jl | 22 +- test/serialization.jl | 4 +- test/split_parameters.jl | 20 +- test/state_selection.jl | 22 +- test/static_arrays.jl | 2 +- test/steadystatesystems.jl | 2 +- test/stream_connectors.jl | 50 ++-- .../index_reduction.jl | 14 +- test/structural_transformation/tearing.jl | 8 +- test/structural_transformation/utils.jl | 20 +- test/substitute_component.jl | 6 +- test/symbolic_events.jl | 118 ++++---- test/symbolic_indexing_interface.jl | 18 +- test/symbolic_parameters.jl | 2 +- test/test_variable_metadata.jl | 6 +- test/units.jl | 36 +-- test/variable_scope.jl | 22 +- test/variable_utils.jl | 6 +- 71 files changed, 740 insertions(+), 740 deletions(-) diff --git a/test/accessor_functions.jl b/test/accessor_functions.jl index 4136736a8b..707b928e75 100644 --- a/test/accessor_functions.jl +++ b/test/accessor_functions.jl @@ -56,13 +56,13 @@ let ] cevs = [[t ~ 1.0] => [Y ~ Pre(Y) + 2.0]] devs = [(t == 2.0) => [Y ~ Pre(Y) + 2.0]] - @named sys_bot = ODESystem( + @named sys_bot = System( eqs_bot, t; systems = [], continuous_events = cevs, discrete_events = devs) - @named sys_mid2 = ODESystem( + @named sys_mid2 = System( eqs_mid2, t; systems = [], continuous_events = cevs, discrete_events = devs) - @named sys_mid1 = ODESystem( + @named sys_mid1 = System( eqs_mid1, t; systems = [sys_bot], continuous_events = cevs, discrete_events = devs) - @named sys_top = ODESystem(eqs_top, t; systems = [sys_mid1, sys_mid2], + @named sys_top = System(eqs_top, t; systems = [sys_mid1, sys_mid2], continuous_events = cevs, discrete_events = devs) sys_bot_comp = complete(sys_bot) sys_mid2_comp = complete(sys_mid2) diff --git a/test/analysis_points.jl b/test/analysis_points.jl index e36afc02f7..54cf0004c6 100644 --- a/test/analysis_points.jl +++ b/test/analysis_points.jl @@ -12,14 +12,14 @@ using Symbolics: NAMESPACE_SEPARATOR ap = AnalysisPoint(:plant_input) eqs = [connect(P.output, C.input) connect(C.output, ap, P.input)] - sys_ap = ODESystem(eqs, t, systems = [P, C], name = :hej) + sys_ap = System(eqs, t, systems = [P, C], name = :hej) sys_ap2 = @test_nowarn expand_connections(sys_ap) @test all(eq -> !(eq.lhs isa AnalysisPoint), equations(sys_ap2)) eqs = [connect(P.output, C.input) connect(C.output, P.input)] - sys_normal = ODESystem(eqs, t, systems = [P, C], name = :hej) + sys_normal = System(eqs, t, systems = [P, C], name = :hej) sys_normal2 = @test_nowarn expand_connections(sys_normal) @test isequal(sys_ap2, sys_normal2) @@ -41,10 +41,10 @@ end @named C = Gain(; k = -1) eqs = [connect(P.output, C.input), connect(C.output, :plant_input, P.input)] - sys_ap = ODESystem(eqs, t, systems = [P, C], name = :hej) + sys_ap = System(eqs, t, systems = [P, C], name = :hej) ap2 = @test_nowarn sys_ap.plant_input @test nameof(ap2) == Symbol(join(["hej", "plant_input"], NAMESPACE_SEPARATOR)) - @named sys = ODESystem(Equation[], t; systems = [sys_ap]) + @named sys = System(Equation[], t; systems = [sys_ap]) ap3 = @test_nowarn sys.hej.plant_input @test nameof(ap3) == Symbol(join(["sys", "hej", "plant_input"], NAMESPACE_SEPARATOR)) csys = complete(sys) @@ -62,8 +62,8 @@ end ap = AnalysisPoint(:plant_input) eqs = [connect(P.output, C.input), connect(C.output, ap, P.input)] -sys = ODESystem(eqs, t, systems = [P, C], name = :hej) -@named nested_sys = ODESystem(Equation[], t; systems = [sys]) +sys = System(eqs, t, systems = [P, C], name = :hej) +@named nested_sys = System(Equation[], t; systems = [sys]) nonamespace_sys = toggle_namespacing(nested_sys, false) @testset "simplifies and solves" begin @@ -132,8 +132,8 @@ end eqs = [connect(P.output, :plant_output, C.input) connect(C.output, :plant_input, P.input)] -sys = ODESystem(eqs, t, systems = [P, C], name = :hej) -@named nested_sys = ODESystem(Equation[], t; systems = [sys]) +sys = System(eqs, t, systems = [P, C], name = :hej) +@named nested_sys = System(Equation[], t; systems = [sys]) test_cases = [ ("inner", sys, sys.plant_input), diff --git a/test/basic_transformations.jl b/test/basic_transformations.jl index bb57e27ea5..a414120ed1 100644 --- a/test/basic_transformations.jl +++ b/test/basic_transformations.jl @@ -8,7 +8,7 @@ D = Differential(t) @parameters α β γ δ @variables x(t) y(t) eqs = [D(x) ~ α * x - β * x * y, D(y) ~ -δ * y + γ * x * y] - @named sys = ODESystem(eqs, t) + @named sys = System(eqs, t) sys = complete(sys) u0 = [x => 1.0, y => 1.0] @@ -29,7 +29,7 @@ end @testset "Change independent variable (trivial)" begin @variables x(t) y(t) eqs1 = [D(D(x)) ~ D(x) + x, D(y) ~ 1] - M1 = ODESystem(eqs1, t; name = :M) + M1 = System(eqs1, t; name = :M) M2 = change_independent_variable(M1, y) @variables y x(y) yˍt(y) Dy = Differential(y) @@ -48,7 +48,7 @@ end D(s) ~ 1 / (2 * s) ] initialization_eqs = [x ~ 1.0, y ~ 1.0, D(y) ~ 0.0] - M1 = ODESystem(eqs, t; initialization_eqs, name = :M) + M1 = System(eqs, t; initialization_eqs, name = :M) M2 = change_independent_variable(M1, s) M1 = structural_simplify(M1; allow_symbolic = true) @@ -68,7 +68,7 @@ end D = Differential(t) @variables a(t) ȧ(t) Ω(t) ϕ(t) a, ȧ = GlobalScope.([a, ȧ]) - species(w; kw...) = ODESystem([D(Ω) ~ -3(1 + w) * D(a) / a * Ω], t, [Ω], []; kw...) + species(w; kw...) = System([D(Ω) ~ -3(1 + w) * D(a) / a * Ω], t, [Ω], []; kw...) @named r = species(1 // 3) @named m = species(0) @named Λ = species(-1) @@ -78,7 +78,7 @@ end ȧ ~ √(Ω) * a^2, D(D(ϕ)) ~ -3 * D(a) / a * D(ϕ) ] - M1 = ODESystem(eqs, t, [Ω, a, ȧ, ϕ], []; name = :M) + M1 = System(eqs, t, [Ω, a, ȧ, ϕ], []; name = :M) M1 = compose(M1, r, m, Λ) # Apply in two steps, where derivatives are defined at each step: first t -> a, then a -> b @@ -109,7 +109,7 @@ end @testset "Change independent variable (simple)" begin @variables x(t) y1(t) # y(t)[1:1] # TODO: use array variables y(t)[1:2] when fixed: https://github.com/JuliaSymbolics/Symbolics.jl/issues/1383 - Mt = ODESystem([D(x) ~ 2 * x, D(y1) ~ y1], t; name = :M) + Mt = System([D(x) ~ 2 * x, D(y1) ~ y1], t; name = :M) Mx = change_independent_variable(Mt, x) @variables x xˍt(x) xˍtt(x) y1(x) # y(x)[1:1] # TODO: array variables Dx = Differential(x) @@ -119,7 +119,7 @@ end @testset "Change independent variable (free fall with 1st order horizontal equation)" begin @variables x(t) y(t) @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, ... + Mt = System([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) Dx = Differential(Mx.x) @@ -133,7 +133,7 @@ end @testset "Change independent variable (free fall with 2nd order horizontal equation)" begin @variables x(t) y(t) @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, ... + Mt = System([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) Dx = Differential(Mx.x) @@ -151,7 +151,7 @@ end (D^3)(y) ~ D(x)^2 + (D^2)(y^2) |> expand_derivatives, D(x)^2 + D(y)^2 ~ x^4 + y^5 + t^6 ] - M1 = ODESystem(eqs, t; name = :M) + M1 = System(eqs, t; name = :M) M2 = change_independent_variable(M1, x; add_old_diff = true) @test_nowarn structural_simplify(M2) @@ -185,7 +185,7 @@ end D(x) ~ 2t, D(y) ~ 1fc(t) + 2fc(x) + 3fc(y) + 1callme(f, t) + 2callme(f, x) + 3callme(f, y) ] - M1 = ODESystem(eqs, t; name = :M) + M1 = System(eqs, t; name = :M) # Ensure that interpolations are called with the same variables M2 = change_independent_variable(M1, x, [t ~ √(x)]) @@ -207,13 +207,13 @@ 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) + M = System([D(x) ~ 1, v ~ x], t; name = :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) @variables x(..) # require explicit argument - M = ODESystem([D(x(t)) ~ x(t - 1)], t; name = :M) + M = System([D(x(t)) ~ x(t - 1)], t; name = :M) @test_throws "DDE" change_independent_variable(M, x(t)) end @@ -222,7 +222,7 @@ end D_units = Differential(t_units) @variables x(t_units) [unit = u"m"] y(t_units) [unit = u"m"] @parameters g=9.81 [unit = u"m * s^-2"] # gravitational acceleration - Mt = ODESystem([D_units(D_units(y)) ~ -g, D_units(D_units(x)) ~ 0], t_units; name = :M) # gives (x, y) as function of t, ... + Mt = System([D_units(D_units(y)) ~ -g, D_units(D_units(x)) ~ 0], t_units; 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) Dx = Differential(Mx.x) @@ -243,7 +243,7 @@ end @named input_sys = Input() input_sys = complete(input_sys) # test no failures - @test change_independent_variable(input_sys, input_sys.u) isa ODESystem + @test change_independent_variable(input_sys, input_sys.u) isa System @mtkmodel NestedInput begin @components begin @@ -258,7 +258,7 @@ end end @named nested_input_sys = NestedInput() nested_input_sys = complete(nested_input_sys; flatten = false) - @test change_independent_variable(nested_input_sys, nested_input_sys.x) isa ODESystem + @test change_independent_variable(nested_input_sys, nested_input_sys.x) isa System end @testset "Change of variables, connections" begin @@ -295,7 +295,7 @@ end z ~ ModelingToolkit.scalarize.([sin(y), cos(y)]), D(y) ~ z[1]^2 + z[2]^2 ] - @named sys = ODESystem(eqs, t) + @named sys = System(eqs, t) sys = complete(sys) new_sys = change_independent_variable(sys, sys.x; add_old_diff = true) ss_new_sys = structural_simplify(new_sys; allow_symbolic = true) diff --git a/test/bvproblem.jl b/test/bvproblem.jl index 6472608366..dc5f1ef658 100644 --- a/test/bvproblem.jl +++ b/test/bvproblem.jl @@ -20,7 +20,7 @@ daesolvers = [Ascher2, Ascher4, Ascher6] parammap = [α => 7.5, β => 4, γ => 8.0, δ => 5.0] tspan = (0.0, 10.0) - @mtkbuild lotkavolterra = ODESystem(eqs, t) + @mtkbuild lotkavolterra = System(eqs, t) op = ODEProblem(lotkavolterra, u0map, tspan, parammap) osol = solve(op, Vern9()) @@ -52,7 +52,7 @@ end eqs = [D(θ) ~ θ_t D(θ_t) ~ -(g / L) * sin(θ)] - @mtkbuild pend = ODESystem(eqs, t) + @mtkbuild pend = System(eqs, t) u0map = [θ => π / 2, θ_t => π / 2] parammap = [:L => 1.0, :g => 9.81] @@ -80,7 +80,7 @@ end end ################################################################## -### ODESystem with constraint equations, DAEs with constraints ### +### System with constraint equations, DAEs with constraints ### ################################################################## # Test generation of boundary condition function using `generate_function_bc`. Compare solutions to manually written boundary conditions @@ -91,7 +91,7 @@ end D(y(t)) ~ -γ * y(t) + δ * x(t) * y(t)] tspan = (0.0, 1.0) - @mtkbuild lksys = ODESystem(eqs, t) + @mtkbuild lksys = System(eqs, t) function lotkavolterra!(du, u, p, t) du[1] = p[1] * u[1] - p[2] * u[1] * u[2] @@ -104,7 +104,7 @@ end # Test with a constraint. constr = [y(0.5) ~ 2.0] - @mtkbuild lksys = ODESystem(eqs, t; constraints = constr) + @mtkbuild lksys = System(eqs, t; constraints = constr) function bc!(resid, u, p, t) resid[1] = u(0.0)[1] - 1.0 @@ -165,7 +165,7 @@ function test_solvers( end end -# Simple ODESystem with BVP constraints. +# Simple System with BVP constraints. @testset "ODE with constraints" begin @parameters α=1.5 β=1.0 γ=3.0 δ=1.0 @variables x(..) y(..) @@ -177,7 +177,7 @@ end tspan = (0.0, 1.0) guess = [x(t) => 4.0, y(t) => 2.0] constr = [x(0.6) ~ 3.5, x(0.3) ~ 7.0] - @mtkbuild lksys = ODESystem(eqs, t; constraints = constr) + @mtkbuild lksys = System(eqs, t; constraints = constr) bvp = SciMLBase.BVProblem{true, SciMLBase.AutoSpecialize}( lksys, u0map, tspan; guesses = guess) @@ -185,13 +185,13 @@ end # Testing that more complicated constraints give correct solutions. constr = [y(0.2) + x(0.8) ~ 3.0, y(0.3) ~ 2.0] - @mtkbuild lksys = ODESystem(eqs, t; constraints = constr) + @mtkbuild lksys = System(eqs, t; constraints = constr) bvp = SciMLBase.BVProblem{false, SciMLBase.FullSpecialize}( lksys, u0map, tspan; guesses = guess) test_solvers(solvers, bvp, u0map, constr; dt = 0.05) constr = [α * β - x(0.6) ~ 0.0, y(0.2) ~ 3.0] - @mtkbuild lksys = ODESystem(eqs, t; constraints = constr) + @mtkbuild lksys = System(eqs, t; constraints = constr) bvp = SciMLBase.BVProblem{true, SciMLBase.AutoSpecialize}( lksys, u0map, tspan; guesses = guess) test_solvers(solvers, bvp, u0map, constr) @@ -205,7 +205,7 @@ end # eqs = [D(D(x)) ~ λ * x # D(D(y)) ~ λ * y - g # x^2 + y^2 ~ 1] -# @mtkbuild pend = ODESystem(eqs, t) +# @mtkbuild pend = System(eqs, t) # # tspan = (0.0, 1.5) # u0map = [x => 1, y => 0] @@ -243,7 +243,7 @@ end # D(D(y)) ~ λ * y - g # x(t)^2 + y^2 ~ 1] # constr = [x(0.5) ~ 1] -# @mtkbuild pend = ODESystem(eqs, t; constr) +# @mtkbuild pend = System(eqs, t; constr) # # tspan = (0.0, 1.5) # u0map = [x(t) => 0.6, y => 0.8] @@ -262,13 +262,13 @@ end # # constr = [x(0.5) ~ 1, # x(0.3)^3 + y(0.6)^2 ~ 0.5] -# @mtkbuild pend = ODESystem(eqs, t; constr) +# @mtkbuild pend = System(eqs, t; constr) # bvp = SciMLBase.BVProblem{true, SciMLBase.AutoSpecialize}(pend, u0map, tspan, parammap; guesses, check_length = false) # test_solvers(daesolvers, bvp, u0map, constr, get_alg_eqs(pend)) # # constr = [x(0.4) * g ~ y(0.2), # y(0.7) ~ 0.3] -# @mtkbuild pend = ODESystem(eqs, t; constr) +# @mtkbuild pend = System(eqs, t; constr) # bvp = SciMLBase.BVProblem{true, SciMLBase.AutoSpecialize}(pend, u0map, tspan, parammap; guesses, check_length = false) # test_solvers(daesolvers, bvp, u0map, constr, get_alg_eqs(pend)) # end @@ -286,8 +286,8 @@ end parammap = [α => 7.5, β => 4, γ => 8.0, δ => 5.0] costs = [x(0.6), x(0.3)^2] consolidate(u) = (u[1] + 3)^2 + u[2] - @mtkbuild lksys = ODESystem(eqs, t; costs, consolidate) - @test_throws ErrorException @mtkbuild lksys2 = ODESystem(eqs, t; costs) + @mtkbuild lksys = System(eqs, t; costs, consolidate) + @test_throws ErrorException @mtkbuild lksys2 = System(eqs, t; costs) @test_throws ErrorException ODEProblem(lksys, u0map, tspan, parammap) prob = ODEProblem(lksys, u0map, tspan, parammap; allow_cost = true) @@ -300,7 +300,7 @@ end @parameters t_c costs = [y(t_c) + x(0.0), x(0.4)^2] consolidate(u) = log(u[1]) - u[2] - @mtkbuild lksys = ODESystem(eqs, t; costs, consolidate) + @mtkbuild lksys = System(eqs, t; costs, consolidate) @test t_c ∈ Set(parameters(lksys)) push!(parammap, t_c => 0.56) prob = ODEProblem(lksys, u0map, tspan, parammap; allow_cost = true) diff --git a/test/causal_variables_connection.jl b/test/causal_variables_connection.jl index c22e8319d4..222db540de 100644 --- a/test/causal_variables_connection.jl +++ b/test/causal_variables_connection.jl @@ -40,12 +40,12 @@ end eqs = [connect(P.output.u, C.input.u) connect(C.output.u, P.input.u)] - sys1 = ODESystem(eqs, t, systems = [P, C], name = :hej) + sys1 = System(eqs, t, systems = [P, C], name = :hej) sys = expand_connections(sys1) @test any(isequal(P.output.u ~ C.input.u), equations(sys)) @test any(isequal(C.output.u ~ P.input.u), equations(sys)) - @named sysouter = ODESystem(Equation[], t; systems = [sys1]) + @named sysouter = System(Equation[], t; systems = [sys1]) sys = expand_connections(sysouter) @test any(isequal(sys1.P.output.u ~ sys1.C.input.u), equations(sys)) @test any(isequal(sys1.C.output.u ~ sys1.P.input.u), equations(sys)) @@ -57,8 +57,8 @@ end ap = AnalysisPoint(:plant_input) eqs = [connect(P.output, C.input), connect(C.output.u, ap, P.input.u)] - sys = ODESystem(eqs, t, systems = [P, C], name = :hej) - @named nested_sys = ODESystem(Equation[], t; systems = [sys]) + sys = System(eqs, t, systems = [P, C], name = :hej) + @named nested_sys = System(Equation[], t; systems = [sys]) test_cases = [ ("inner", sys, sys.plant_input), diff --git a/test/clock.jl b/test/clock.jl index c4c64dbf90..3c5a32c1ce 100644 --- a/test/clock.jl +++ b/test/clock.jl @@ -22,7 +22,7 @@ eqs = [yd ~ Sample(dt)(y) u ~ Hold(ud) D(x) ~ -x + u y ~ x] -@named sys = ODESystem(eqs, t) +@named sys = System(eqs, t) # compute equation and variables' time domains #TODO: test linearize @@ -115,7 +115,7 @@ eqs = [yd ~ Sample(dt)(y) u ~ Hold(ud) D(x) ~ -x + u y ~ x] -@named sys = ODESystem(eqs, t) +@named sys = System(eqs, t) @test_throws ModelingToolkit.HybridSystemNotSupportedException ss=structural_simplify(sys); @test_skip begin @@ -190,7 +190,7 @@ eqs = [yd ~ Sample(dt)(y) u ~ Hold(ud1) + Hold(ud2) D(x) ~ -x + u y ~ x] - @named sys = ODESystem(eqs, t) + @named sys = System(eqs, t) ci, varmap = infer_clocks(sys) d = Clock(dt) @@ -218,7 +218,7 @@ eqs = [yd ~ Sample(dt)(y) @variables x(t)=1 u(t)=0 y(t)=0 eqs = [D(x) ~ -x + u y ~ x] - ODESystem(eqs, t; name = name) + System(eqs, t; name = name) end function filt(; name) @@ -226,7 +226,7 @@ eqs = [yd ~ Sample(dt)(y) a = 1 / exp(dt) eqs = [x ~ a * x(k - 1) + (1 - a) * u(k - 1) y ~ x] - ODESystem(eqs, t, name = name) + System(eqs, t, name = name) end function controller(kp; name) @@ -234,7 +234,7 @@ eqs = [yd ~ Sample(dt)(y) @parameters kp = kp eqs = [yd ~ Sample(y) ud ~ kp * (r - yd)] - ODESystem(eqs, t; name = name) + System(eqs, t; name = name) end @named f = filt() @@ -246,7 +246,7 @@ eqs = [yd ~ Sample(dt)(y) Hold(c.ud) ~ p.u # controller output to plant input p.y ~ c.y] - @named cl = ODESystem(connections, t, systems = [f, c, p]) + @named cl = System(connections, t, systems = [f, c, p]) ci, varmap = infer_clocks(cl) @@ -281,7 +281,7 @@ eqs = [yd ~ Sample(dt)(y) D(x) ~ -x + u y ~ x] - @named cl = ODESystem(eqs, t) + @named cl = System(eqs, t) d = Clock(dt) d2 = Clock(dt2) @@ -529,7 +529,7 @@ eqs = [yd ~ Sample(dt)(y) @variables x(t)=1.0 y(t)=1.0 eqs = [D(y) ~ Hold(x) x ~ x(k - 1) + x(k - 2)] - @mtkbuild sys = ODESystem(eqs, t) + @mtkbuild sys = System(eqs, t) prob = ODEProblem(sys, [], (0.0, 10.0)) int = init(prob, Tsit5(); kwargshandle = KeywordArgSilent) @test int.ps[x] == 2.0 diff --git a/test/code_generation.jl b/test/code_generation.jl index 2fbf8f13d7..87a485e198 100644 --- a/test/code_generation.jl +++ b/test/code_generation.jl @@ -5,7 +5,7 @@ using ModelingToolkit: t_nounits as t, D_nounits as D @variables x(t) y(t)[1:3] @parameters p1=1.0 p2[1:3]=[1.0, 2.0, 3.0] p3::Int=1 p4::Bool=false - sys = complete(ODESystem(Equation[], t, [x; y], [p1, p2, p3, p4]; name = :sys)) + sys = complete(System(Equation[], t, [x; y], [p1, p2, p3, p4]; name = :sys)) u0 = [1.0, 2.0, 3.0, 4.0] p = ModelingToolkit.MTKParameters(sys, []) @@ -58,7 +58,7 @@ end @testset "Non-standard array variables" begin @variables x(t) @parameters p[0:2] (f::Function)(..) - @mtkbuild sys = ODESystem(D(x) ~ p[0] * x + p[1] * t + p[2] + f(p), t) + @mtkbuild sys = System(D(x) ~ p[0] * x + p[1] * t + p[2] + f(p), t) prob = ODEProblem(sys, [x => 1.0], (0.0, 1.0), [p => [1.0, 2.0, 3.0], f => sum]) @test prob.ps[p] == [1.0, 2.0, 3.0] @test prob.ps[p[0]] == 1.0 @@ -68,7 +68,7 @@ end @testset "Array split across buffers" begin @variables x(t)[0:2] @parameters p[1:2] (f::Function)(..) - @named sys = ODESystem( + @named sys = System( [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 = []) @test is_parameter(sys, x[2]) diff --git a/test/components.jl b/test/components.jl index 43c0d1acf8..282c4c0aa7 100644 --- a/test/components.jl +++ b/test/components.jl @@ -75,7 +75,7 @@ end eqs = [connect(p, resistor.p); connect(resistor.n, capacitor.p); connect(capacitor.n, n)] - @named sys = ODESystem(eqs, t, [], [R, C]) + @named sys = System(eqs, t, [], [R, C]) compose(sys, [p, n, resistor, capacitor]; name = name) end @@ -87,7 +87,7 @@ end connect(source.p, rc_comp.p) connect(source.n, rc_comp.n) connect(source.n, ground.g)] - @named sys′ = ODESystem(eqs, t) + @named sys′ = System(eqs, t) @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) @@ -131,16 +131,16 @@ end @testset "Compose/extend" begin @variables x1(t) x2(t) x3(t) x4(t) - @named sys1_inner = ODESystem([D(x1) ~ x1], t) - @named sys1_partial = compose(ODESystem([D(x2) ~ x2], t; name = :foo), sys1_inner) - @named sys1 = extend(ODESystem([D(x3) ~ x3], t; name = :foo), sys1_partial) - @named sys2 = compose(ODESystem([D(x4) ~ x4], t; name = :foo), sys1) + @named sys1_inner = System([D(x1) ~ x1], t) + @named sys1_partial = compose(System([D(x2) ~ x2], t; name = :foo), sys1_inner) + @named sys1 = extend(System([D(x3) ~ x3], t; name = :foo), sys1_partial) + @named sys2 = compose(System([D(x4) ~ x4], t; name = :foo), sys1) @test_nowarn sys2.sys1.sys1_inner.x1 # test the correct nesting # compose tests function record_fun(; name) pars = @parameters a=10 b=100 - ODESystem(Equation[], t, [], pars; name) + System(Equation[], t, [], pars; name) end function first_model(; name) @@ -150,7 +150,7 @@ end defs[foo.a] = 3 defs[foo.b] = 300 pars = @parameters x=2 y=20 - compose(ODESystem(Equation[], t, [], pars; name, defaults = defs), foo) + compose(System(Equation[], t, [], pars; name, defaults = defs), foo) end @named goo = first_model() @unpack foo = goo @@ -165,7 +165,7 @@ function Load(; name) @named resistor = Resistor(R = R) eqs = [connect(p, resistor.p); connect(resistor.n, n)] - @named sys = ODESystem(eqs, t) + @named sys = System(eqs, t) compose(sys, [p, n, resistor]; name = name) end @@ -176,7 +176,7 @@ function Circuit(; name) @named resistor = Resistor(R = R) eqs = [connect(load.p, ground.g); connect(resistor.p, ground.g)] - @named sys = ODESystem(eqs, t) + @named sys = System(eqs, t) compose(sys, [ground, resistor, load]; name = name) end @@ -196,7 +196,7 @@ end connect(capacitor.n, source.n, ground.g) connect(resistor.heat_port, heat_capacitor.port)] - compose(ODESystem(rc_eqs, t, name = Symbol(name, i)), + compose(System(rc_eqs, t, name = Symbol(name, i)), [resistor, capacitor, source, ground, heat_capacitor]) end V = 2.0 @@ -214,7 +214,7 @@ end D(E) ~ sum(((i, sys),) -> getproperty(sys, Symbol(:resistor, i)).heat_port.Q_flow, enumerate(rc_systems)) ] - @named _big_rc = ODESystem(eqs, t, [E], []) + @named _big_rc = System(eqs, t, [E], []) @named big_rc = compose(_big_rc, rc_systems) ts = TearingState(expand_connections(big_rc)) # this is block upper triangular, so `istriu` needs a little leeway @@ -230,7 +230,7 @@ end eqs = [ v ~ i * R ] - extend(ODESystem(eqs, t, [], []; name = name), oneport) + extend(System(eqs, t, [], []; name = name), oneport) end capacitor = Capacitor(; name = :c1, C = 1.0) resistor = FixedResistor(; name = :r1) @@ -239,7 +239,7 @@ end connect(resistor.n, capacitor.p) connect(capacitor.n, ground.g)] - @named _rc_model = ODESystem(rc_eqs, t) + @named _rc_model = System(rc_eqs, t) @named rc_model = compose(_rc_model, [resistor, capacitor, ground]) sys = structural_simplify(rc_model) @@ -254,7 +254,7 @@ end @connector function Pin1(; name) @independent_variables t sts = @variables v(t)=1.0 i(t)=1.0 - ODESystem(Equation[], t, sts, []; name = name) + System(Equation[], t, sts, []; name = name) end @test string(Base.doc(Pin1)) == "Hey there, Pin1!\n" @@ -264,7 +264,7 @@ end @component function Pin2(; name) @independent_variables t sts = @variables v(t)=1.0 i(t)=1.0 - ODESystem(Equation[], t, sts, []; name = name) + System(Equation[], t, sts, []; name = name) end @test string(Base.doc(Pin2)) == "Hey there, Pin2!\n" end @@ -328,8 +328,8 @@ end @testset "Issue#3275: Metadata retained on `complete`" begin @variables x(t) y(t) @testset "ODESystem" begin - @named inner = ODESystem(D(x) ~ x, t) - @named outer = ODESystem(D(y) ~ y, t; systems = [inner], metadata = "test") + @named inner = System(D(x) ~ x, t) + @named outer = System(D(y) ~ y, t; systems = [inner], metadata = "test") @test ModelingToolkit.get_metadata(outer) == "test" sys = complete(outer) @test ModelingToolkit.get_metadata(sys) == "test" diff --git a/test/constants.jl b/test/constants.jl index f2c4fdaa86..5e97d52d7f 100644 --- a/test/constants.jl +++ b/test/constants.jl @@ -10,7 +10,7 @@ UMT = ModelingToolkit.UnitfulUnitCheck @variables x(t) w(t) D = Differential(t) eqs = [D(x) ~ a] -@named sys = ODESystem(eqs, t) +@named sys = System(eqs, t) prob = ODEProblem(complete(sys), [0], [0.0, 1.0], []) sol = solve(prob, Tsit5()) @@ -20,7 +20,7 @@ newsys = MT.eliminate_constants(sys) # Test structural_simplify substitutions & observed values eqs = [D(x) ~ 1, w ~ a] -@named sys = ODESystem(eqs, t) +@named sys = System(eqs, t) # Now eliminate the constants first simp = structural_simplify(sys) @test equations(simp) == [D(x) ~ 1.0] @@ -33,7 +33,7 @@ UMT.get_unit(β) @variables x(t) [unit = u"m"] D = Differential(t) eqs = [D(x) ~ β] -@named sys = ODESystem(eqs, t) +@named sys = System(eqs, t) simp = structural_simplify(sys) @test isempty(MT.collect_constants(nothing)) @@ -44,7 +44,7 @@ simp = structural_simplify(sys) @variables x(MT.t_nounits) = h eqs = [MT.D_nounits(x) ~ (h - x) / τ] - @mtkbuild fol_model = ODESystem(eqs, MT.t_nounits) + @mtkbuild fol_model = System(eqs, MT.t_nounits) prob = ODEProblem(fol_model, [], (0.0, 10.0)) @test prob[x] ≈ 1 diff --git a/test/dae_jacobian.jl b/test/dae_jacobian.jl index 8c68df767a..94f15cbb7c 100644 --- a/test/dae_jacobian.jl +++ b/test/dae_jacobian.jl @@ -36,7 +36,7 @@ sol1 = solve(prob1, IDA(linear_solver = :KLU)) eqs = [D(u1) ~ p1 * u1 - u1 * u2, D(u2) ~ u1 * u2 - p2 * u2] -@named sys = ODESystem(eqs, t) +@named sys = System(eqs, t) u0 = [u1 => 1.0, u2 => 1.0] diff --git a/test/debugging.jl b/test/debugging.jl index a55684737c..de4420d08c 100644 --- a/test/debugging.jl +++ b/test/debugging.jl @@ -4,7 +4,7 @@ using ModelingToolkit: t_nounits as t, D_nounits as D, ASSERTION_LOG_VARIABLE @variables x(t) @brownian a -@named inner_ode = ODESystem(D(x) ~ -sqrt(x), t; assertions = [(x > 0) => "ohno"]) +@named inner_ode = System(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) @@ -36,7 +36,7 @@ end @testset "Hierarchical system" begin @testset "$(typeof(inner))" for (ctor, Problem, inner, alg) in [ - (ODESystem, ODEProblem, inner_ode, Tsit5()), + (System, ODEProblem, inner_ode, Tsit5()), (System, SDEProblem, inner_sde, ImplicitEM())] @mtkbuild outer = ctor(Equation[], t; systems = [inner]) dsys = debug_system(outer; functions = []) diff --git a/test/dep_graphs.jl b/test/dep_graphs.jl index 3b02efb2d0..76cc216635 100644 --- a/test/dep_graphs.jl +++ b/test/dep_graphs.jl @@ -155,7 +155,7 @@ eqs = [D(S) ~ k1 - k1 * S - k2 * S * I - k1 * k2 / (1 + t) * S, D(I) ~ k2 * S * I, D(R) ~ -k2 * S^2 * R / 2 + k1 * I + k1 * k2 * S / (1 + t)] noiseeqs = [S, I, R] -@named os = ODESystem(eqs, t, [S, I, R], [k1, k2]) +@named os = System(eqs, t, [S, I, R], [k1, k2]) deps = equation_dependencies(os) S = value(S); I = value(I); diff --git a/test/distributed.jl b/test/distributed.jl index 0b75b8eeb3..e7edc17a66 100644 --- a/test/distributed.jl +++ b/test/distributed.jl @@ -13,7 +13,7 @@ addprocs(2) D(y) ~ x * (ρ - z) - y, D(z) ~ x * y - β * z] -@everywhere @named de = ODESystem(eqs, t) +@everywhere @named de = System(eqs, t) @everywhere de = complete(de) @everywhere u0 = [19.0, 20.0, 50.0] diff --git a/test/domain_connectors.jl b/test/domain_connectors.jl index 9a43d2938f..03d6ff264a 100644 --- a/test/domain_connectors.jl +++ b/test/domain_connectors.jl @@ -14,7 +14,7 @@ using Test dm(t), [connect = Flow] end - ODESystem(Equation[], t, vars, pars; name, defaults = [dm => 0]) + System(Equation[], t, vars, pars; name, defaults = [dm => 0]) end @connector function HydraulicFluid(; @@ -36,7 +36,7 @@ end dm ~ 0 ] - ODESystem(eqs, t, vars, pars; name, defaults = [dm => 0]) + System(eqs, t, vars, pars; name, defaults = [dm => 0]) end function FixedPressure(; p, name) @@ -54,7 +54,7 @@ function FixedPressure(; p, name) port.p ~ p ] - ODESystem(eqs, t, vars, pars; name, systems) + System(eqs, t, vars, pars; name, systems) end function FixedVolume(; vol, p_int, name) @@ -80,7 +80,7 @@ function FixedVolume(; vol, p_int, name) rho ~ port.ρ * (1 + p / port.β) dm ~ drho * vol] - ODESystem(eqs, t, vars, pars; name, systems) + System(eqs, t, vars, pars; name, systems) end function Valve2Port(; p_s_int, p_r_int, p_int, name) @@ -120,7 +120,7 @@ function Valve2Port(; p_s_int, p_r_int, p_int, name) HS.dm ~ ifelse(x >= 0, port.dm, 0) HR.dm ~ ifelse(x < 0, port.dm, 0)] - ODESystem(eqs, t, vars, pars; name, systems) + System(eqs, t, vars, pars; name, systems) end function System(; name) @@ -139,7 +139,7 @@ function System(; name) connect(vol.port, valve.port) valve.x ~ sin(2π * t * 10)] - return ODESystem(eqs, t, vars, pars; systems, name) + return System(eqs, t, vars, pars; systems, name) end @named odesys = System() diff --git a/test/downstream/analysis_points.jl b/test/downstream/analysis_points.jl index 58c4a97a8d..5dc0026138 100644 --- a/test/downstream/analysis_points.jl +++ b/test/downstream/analysis_points.jl @@ -22,7 +22,7 @@ import ControlSystemsBase as CS connect(inertia2.flange_a, spring.flange_b, damper.flange_b)] if u !== nothing push!(eqs, connect(torque.tau, u.output)) - return ODESystem(eqs, t; + return System(eqs, t; systems = [ torque, inertia1, @@ -33,7 +33,7 @@ import ControlSystemsBase as CS ], name) end - ODESystem(eqs, t; systems = [torque, inertia1, inertia2, spring, damper], name) + System(eqs, t; systems = [torque, inertia1, inertia2, spring, damper], name) end @named r = Step(start_time = 0) @@ -50,7 +50,7 @@ import ControlSystemsBase as CS connect(sensor.phi, :y, er.input2) connect(er.output, :e, pid.err_input)] - closed_loop = ODESystem(connections, t, systems = [model, pid, filt, sensor, r, er], + closed_loop = System(connections, t, systems = [model, pid, filt, sensor, r, er], name = :closed_loop, defaults = [ model.inertia1.phi => 0.0, model.inertia2.phi => 0.0, @@ -89,7 +89,7 @@ end connect(add.output, C.input) connect(C.output, P.input)] - sys_inner = ODESystem(eqs, t, systems = [P, C, add], name = :inner) + sys_inner = System(eqs, t, systems = [P, C, add], name = :inner) @named r = Constant(k = 1) @named F = FirstOrder(k = 1, T = 3) @@ -98,7 +98,7 @@ end connect(sys_inner.P.output, sys_inner.add.input2) connect(sys_inner.C.output, :plant_input, sys_inner.P.input) connect(F.output, sys_inner.add.input1)] - sys_outer = ODESystem(eqs, t, systems = [F, sys_inner, r], name = :outer) + sys_outer = System(eqs, t, systems = [F, sys_inner, r], name = :outer) # test first that the structural_simplify works correctly ssys = structural_simplify(sys_outer) @@ -125,7 +125,7 @@ end connect(add.output, C.input) connect(C.output.u, P.input.u)] - sys_inner = ODESystem(eqs, t, systems = [P, C, add], name = :inner) + sys_inner = System(eqs, t, systems = [P, C, add], name = :inner) @named r = Constant(k = 1) @named F = FirstOrder(k = 1, T = 3) @@ -134,7 +134,7 @@ end connect(sys_inner.P.output.u, sys_inner.add.input2.u) connect(sys_inner.C.output.u, :plant_input, sys_inner.P.input.u) connect(F.output, sys_inner.add.input1)] - sys_outer = ODESystem(eqs, t, systems = [F, sys_inner, r], name = :outer) + sys_outer = System(eqs, t, systems = [F, sys_inner, r], name = :outer) # test first that the structural_simplify works correctly ssys = structural_simplify(sys_outer) @@ -161,7 +161,7 @@ end connect(add.output, C.input) connect(C.output, P.input)] - sys_inner = ODESystem(eqs, t, systems = [P, C, add], name = :inner) + sys_inner = System(eqs, t, systems = [P, C, add], name = :inner) @named r = Constant(k = 1) @named F = FirstOrder(k = 1, T = 3) @@ -170,7 +170,7 @@ end connect(sys_inner.P.output, sys_inner.add.input2) connect(sys_inner.C.output.u, :plant_input, sys_inner.P.input.u) connect(F.output, sys_inner.add.input1)] - sys_outer = ODESystem(eqs, t, systems = [F, sys_inner, r], name = :outer) + sys_outer = System(eqs, t, systems = [F, sys_inner, r], name = :outer) # test first that the structural_simplify works correctly ssys = structural_simplify(sys_outer) @@ -192,7 +192,7 @@ end @named P_inner = FirstOrder(k = 1, T = 1) @named feedback = Feedback() @named ref = Step() - @named sys_inner = ODESystem( + @named sys_inner = System( [connect(P_inner.output, :y, feedback.input2) connect(feedback.output, :u, P_inner.input) connect(ref.output, :r, feedback.input1)], @@ -208,7 +208,7 @@ end Sinner = sminreal(ss(get_sensitivity(sys_inner, :u)[1]...)) - @named sys_inner = ODESystem( + @named sys_inner = System( [connect(P_inner.output, :y, feedback.input2) connect(feedback.output, :u, P_inner.input)], t, @@ -216,7 +216,7 @@ end @named P_outer = FirstOrder(k = rand(), T = rand()) - @named sys_outer = ODESystem( + @named sys_outer = System( [connect(sys_inner.P_inner.output, :y2, P_outer.input) connect(P_outer.output, :u2, sys_inner.feedback.input1)], t, @@ -249,7 +249,7 @@ end eqs = [connect(P.output, :plant_output, K.input) connect(K.output, :plant_input, P.input)] - sys = ODESystem(eqs, t, systems = [P, K], name = :hej) + sys = System(eqs, t, systems = [P, K], name = :hej) matrices, _ = get_sensitivity(sys, :plant_input) S = CS.feedback(I(2), Kss * Pss, pos_feedback = true) @@ -281,14 +281,14 @@ end connect(add.output, C.input) connect(C.output, :plant_input, P.input)] - sys_inner = ODESystem(eqs, t, systems = [P, C, add], name = :inner) + sys_inner = System(eqs, t, systems = [P, C, add], name = :inner) @named r = Constant(k = 1) @named F = FirstOrder(k = 1, T = 3) eqs = [connect(r.output, F.input) connect(F.output, sys_inner.add.input1)] - sys_outer = ODESystem(eqs, t, systems = [F, sys_inner, r], name = :outer) + sys_outer = System(eqs, t, systems = [F, sys_inner, r], name = :outer) matrices, _ = get_sensitivity( sys_outer, [sys_outer.inner.plant_input, sys_outer.inner.plant_output]) @@ -352,13 +352,13 @@ function normal_test_system() connect(F1.output, add.input1) connect(F2.output, add.input2) connect(add.output, back.input2)] - @named normal_inner = ODESystem(eqs_normal, t; systems = [F1, F2, add, back]) + @named normal_inner = System(eqs_normal, t; systems = [F1, F2, add, back]) @named step = Step() eqs2_normal = [ connect(step.output, normal_inner.back.input1) ] - @named sys_normal = ODESystem(eqs2_normal, t; systems = [normal_inner, step]) + @named sys_normal = System(eqs2_normal, t; systems = [normal_inner, step]) end sys_normal = normal_test_system() @@ -377,12 +377,12 @@ matrices_normal, _ = get_sensitivity(sys_normal, sys_normal.normal_inner.ap) connect(F1.output, add.input1) connect(F2.output, add.input2) connect(add.output, back.input2)] - @named inner = ODESystem(eqs, t; systems = [F1, F2, add, back]) + @named inner = System(eqs, t; systems = [F1, F2, add, back]) @named step = Step() eqs2 = [connect(step.output, inner.back.input1) connect(inner.back.output, :ap, inner.F1.input)] - @named sys = ODESystem(eqs2, t; systems = [inner, step]) + @named sys = System(eqs2, t; systems = [inner, step]) prob = ODEProblem(structural_simplify(sys), [], (0.0, 10.0)) @test SciMLBase.successful_retcode(solve(prob, Rodas5P())) @@ -401,12 +401,12 @@ end connect(F1.output, add.input1) connect(F2.output, add.input2) connect(add.output, back.input2)] - @named inner = ODESystem(eqs, t; systems = [F1, F2, add, back]) + @named inner = System(eqs, t; systems = [F1, F2, add, back]) @named step = Step() eqs2 = [connect(step.output, inner.back.input1) connect(inner.back.output.u, :ap, inner.F1.input.u)] - @named sys = ODESystem(eqs2, t; systems = [inner, step]) + @named sys = System(eqs2, t; systems = [inner, step]) prob = ODEProblem(structural_simplify(sys), [], (0.0, 10.0)) @test SciMLBase.successful_retcode(solve(prob, Rodas5P())) @@ -425,12 +425,12 @@ end connect(F1.output, add.input1) connect(F2.output, add.input2) connect(add.output, back.input2)] - @named inner = ODESystem(eqs, t; systems = [F1, F2, add, back]) + @named inner = System(eqs, t; systems = [F1, F2, add, back]) @named step = Step() eqs2 = [connect(step.output, inner.back.input1) connect(inner.back.output.u, :ap, inner.F1.input.u)] - @named sys = ODESystem(eqs2, t; systems = [inner, step]) + @named sys = System(eqs2, t; systems = [inner, step]) prob = ODEProblem(structural_simplify(sys), [], (0.0, 10.0)) @test SciMLBase.successful_retcode(solve(prob, Rodas5P())) @@ -459,10 +459,10 @@ end connect(inertia2.flange_a, spring.flange_b, damper.flange_b)] if u !== nothing push!(eqs, connect(torque.tau, u.output)) - return @named model = ODESystem( + return @named model = System( eqs, t; systems = [torque, inertia1, inertia2, spring, damper, u]) end - ODESystem(eqs, t; systems = [torque, inertia1, inertia2, spring, damper], name) + System(eqs, t; systems = [torque, inertia1, inertia2, spring, damper], name) end @named r = Step(start_time = 1) @@ -481,7 +481,7 @@ end connect(add.output, :u, model.torque.tau) # Name connection u to form an analysis point connect(model.inertia1.flange_b, sensor.flange) connect(sensor.phi, :y, pid.measurement)] - closed_loop = ODESystem(connections, t, + closed_loop = System(connections, t, systems = [model, inverse_model, pid, filt, sensor, inverse_sensor, r, add], name = :closed_loop) # just ensure the system simplifies diff --git a/test/downstream/inversemodel.jl b/test/downstream/inversemodel.jl index dc2ee380a2..0e4b1ea5eb 100644 --- a/test/downstream/inversemodel.jl +++ b/test/downstream/inversemodel.jl @@ -65,7 +65,7 @@ begin Fss = ss(Ftf) # Create an MTK-compatible constructor function RefFilter(; name) - sys = ODESystem(Fss; name) + sys = System(Fss; name) "Compute initial state that yields y0 as output" empty!(ModelingToolkit.get_defaults(sys)) return sys diff --git a/test/downstream/linearization_dd.jl b/test/downstream/linearization_dd.jl index c4076b6aad..3e94a02469 100644 --- a/test/downstream/linearization_dd.jl +++ b/test/downstream/linearization_dd.jl @@ -24,7 +24,7 @@ eqs = [connect(link1.TX1, cart.flange) connect(cart.flange, force.flange) connect(link1.TY1, fixed.flange)] -@named model = ODESystem(eqs, t, [], []; systems = [link1, cart, force, fixed]) +@named model = System(eqs, t, [], []; systems = [link1, cart, force, fixed]) lin_outputs = [cart.s, cart.v, link1.A, link1.dA] lin_inputs = [force.f.u] diff --git a/test/downstream/linearize.jl b/test/downstream/linearize.jl index 20a9d317e8..f86d7c937e 100644 --- a/test/downstream/linearize.jl +++ b/test/downstream/linearize.jl @@ -12,7 +12,7 @@ eqs = [u ~ kp * (r - y) D(x) ~ -x + u y ~ x] -@named sys = ODESystem(eqs, t) +@named sys = System(eqs, t) lsys, ssys = linearize(sys, [r], [y]) lprob = LinearizationProblem(sys, [r], [y]) @@ -56,7 +56,7 @@ function plant(; name) D = Differential(t) eqs = [D(x) ~ -x + u y ~ x] - ODESystem(eqs, t; name = name) + System(eqs, t; name = name) end function filt_(; name) @@ -65,7 +65,7 @@ function filt_(; name) D = Differential(t) eqs = [D(x) ~ -2 * x + u y ~ x] - ODESystem(eqs, t, name = name) + System(eqs, t, name = name) end function controller(kp; name) @@ -74,7 +74,7 @@ function controller(kp; name) eqs = [ u ~ kp * (r - y) ] - ODESystem(eqs, t; name = name) + System(eqs, t; name = name) end @named f = filt_() @@ -85,7 +85,7 @@ connections = [f.y ~ c.r # filtered reference to controller reference c.u ~ p.u # controller output to plant input p.y ~ c.y] -@named cl = ODESystem(connections, t, systems = [f, c, p]) +@named cl = System(connections, t, systems = [f, c, p]) lsys0, ssys = linearize(cl) desired_order = [f.x, p.x] @@ -181,7 +181,7 @@ function saturation(; y_max, y_min = y_max > 0 ? -y_max : -Inf, name) # The equation below is equivalent to y ~ clamp(u, y_min, y_max) y ~ ie(u > y_max, y_max, ie((y_min < u) & (u < y_max), u, y_min)) ] - ODESystem(eqs, t, name = name) + System(eqs, t, name = name) end @named sat = saturation(; y_max = 1) # inside the linear region, the function is identity @@ -228,7 +228,7 @@ function SystemModel(u = nothing; name = :model) connect(inertia2.flange_a, spring.flange_b, damper.flange_b)] if u !== nothing push!(eqs, connect(torque.tau, u.output)) - return ODESystem(eqs, t; + return System(eqs, t; systems = [ torque, inertia1, @@ -239,7 +239,7 @@ function SystemModel(u = nothing; name = :model) ], name) end - ODESystem(eqs, t; systems = [torque, inertia1, inertia2, spring, damper], name) + System(eqs, t; systems = [torque, inertia1, inertia2, spring, damper], name) end @named r = Step(start_time = 0) @@ -256,7 +256,7 @@ connections = [connect(r.output, :r, filt.input) connect(sensor.phi, :y, er.input2) connect(er.output, :e, pid.err_input)] -closed_loop = ODESystem(connections, t, systems = [model, pid, filt, sensor, r, er], +closed_loop = System(connections, t, systems = [model, pid, filt, sensor, r, er], name = :closed_loop, defaults = [ model.inertia1.phi => 0.0, model.inertia2.phi => 0.0, @@ -303,7 +303,7 @@ m_ss = 2.4000000003229878 @variables x(t) y(t) u(t)=1.0 @parameters p = 1.0 eqs = [D(x) ~ p * u, x ~ y] -@named sys = ODESystem(eqs, t) +@named sys = System(eqs, t) matrices1, _ = linearize(sys, [u], []; op = Dict(x => 2.0)) matrices2, _ = linearize(sys, [u], []; op = Dict(y => 2.0)) @@ -325,7 +325,7 @@ end @variables x(t) y(t) @parameters p eqs = [0 ~ x * log(y) - p] - @named sys = ODESystem(eqs, t; defaults = [p => 1.0]) + @named sys = System(eqs, t; defaults = [p => 1.0]) sys = complete(sys) @test_throws ModelingToolkit.MissingVariablesError linearize( sys, [x], []; op = Dict(x => 1.0), allow_input_derivatives = true) diff --git a/test/downstream/test_disturbance_model.jl b/test/downstream/test_disturbance_model.jl index 0e04200237..6da6c249a1 100644 --- a/test/downstream/test_disturbance_model.jl +++ b/test/downstream/test_disturbance_model.jl @@ -71,7 +71,7 @@ lsys = named_ss( # If we now want to add a disturbance model, we cannot do that since we have already connected a constant to the disturbance input, we thus create a new wrapper model with inputs s = tf("s") -dist(; name) = ODESystem(1 / s; name) +dist(; name) = System(1 / s; name) @mtkmodel SystemModelWithDisturbanceModel begin @components begin @@ -106,7 +106,7 @@ sol = solve(prob, Tsit5()) ## # Now we only have an integrating disturbance affecting inertia1, what if we want both integrating and direct Gaussian? We'd need a "PI controller" disturbancemodel. If we add the disturbance model (s+1)/s we get the integrating and non-integrating noises being correlated which is fine, it reduces the dimensions of the sigma point by 1. -dist3(; name) = ODESystem(ss(1 + 10 / s, balance = false); name) +dist3(; name) = System(ss(1 + 10 / s, balance = false); name) @mtkmodel SystemModelWithDisturbanceModel begin @components begin diff --git a/test/dq_units.jl b/test/dq_units.jl index 3c59c479c1..267e993971 100644 --- a/test/dq_units.jl +++ b/test/dq_units.jl @@ -17,63 +17,63 @@ using ModelingToolkit: t, D eqs = [D(E) ~ P - E / τ 0 ~ P] @test MT.validate(eqs) -@named sys = ODESystem(eqs, t) +@named sys = System(eqs, t) @test !MT.validate(D(D(E)) ~ P) @test !MT.validate(0 ~ P + E * τ) # Disabling unit validation/checks selectively -@test_throws MT.ArgumentError ODESystem(eqs, t, [E, P, t], [τ], name = :sys) -ODESystem(eqs, t, [E, P, t], [τ], name = :sys, checks = MT.CheckUnits) +@test_throws MT.ArgumentError System(eqs, t, [E, P, t], [τ], name = :sys) +System(eqs, t, [E, P, t], [τ], name = :sys, checks = MT.CheckUnits) eqs = [D(E) ~ P - E / τ 0 ~ P + E * τ] -@test_throws MT.ValidationError ODESystem(eqs, t, name = :sys, checks = MT.CheckAll) -@test_throws MT.ValidationError ODESystem(eqs, t, name = :sys, checks = true) -ODESystem(eqs, t, name = :sys, checks = MT.CheckNone) -ODESystem(eqs, t, name = :sys, checks = false) -@test_throws MT.ValidationError ODESystem(eqs, t, name = :sys, +@test_throws MT.ValidationError System(eqs, t, name = :sys, checks = MT.CheckAll) +@test_throws MT.ValidationError System(eqs, t, name = :sys, checks = true) +System(eqs, t, name = :sys, checks = MT.CheckNone) +System(eqs, t, name = :sys, checks = false) +@test_throws MT.ValidationError System(eqs, t, name = :sys, checks = MT.CheckComponents | MT.CheckUnits) -@named sys = ODESystem(eqs, t, checks = MT.CheckComponents) -@test_throws MT.ValidationError ODESystem(eqs, t, [E, P, t], [τ], name = :sys, +@named sys = System(eqs, t, checks = MT.CheckComponents) +@test_throws MT.ValidationError System(eqs, t, [E, P, t], [τ], name = :sys, checks = MT.CheckUnits) # connection validation @connector function Pin(; name) sts = @variables(v(t)=1.0, [unit = u"V"], i(t)=1.0, [unit = u"A", connect = Flow]) - ODESystem(Equation[], t, sts, []; name = name) + System(Equation[], t, sts, []; name = name) end @connector function OtherPin(; name) sts = @variables(v(t)=1.0, [unit = u"mV"], i(t)=1.0, [unit = u"mA", connect = Flow]) - ODESystem(Equation[], t, sts, []; name = name) + System(Equation[], t, sts, []; name = name) end @connector function LongPin(; name) sts = @variables(v(t)=1.0, [unit = u"V"], i(t)=1.0, [unit = u"A", connect = Flow], x(t)=1.0) - ODESystem(Equation[], t, sts, []; name = name) + System(Equation[], t, sts, []; name = name) end @named p1 = Pin() @named p2 = Pin() @named lp = LongPin() good_eqs = [connect(p1, p2)] @test MT.validate(good_eqs) -@named sys = ODESystem(good_eqs, t, [], []) +@named sys = System(good_eqs, t, [], []) @named op = OtherPin() bad_eqs = [connect(p1, op)] @test !MT.validate(bad_eqs) -@test_throws MT.ValidationError @named sys = ODESystem(bad_eqs, t, [], []) +@test_throws MT.ValidationError @named sys = System(bad_eqs, t, [], []) @named op2 = OtherPin() good_eqs = [connect(op, op2)] @test MT.validate(good_eqs) -@named sys = ODESystem(good_eqs, t, [], []) +@named sys = System(good_eqs, t, [], []) # Array variables @variables x(t)[1:3] [unit = u"m"] @parameters v[1:3]=[1, 2, 3] [unit = u"m/s"] eqs = D.(x) .~ v -ODESystem(eqs, t, name = :sys) +System(eqs, t, name = :sys) # Nonlinear system @parameters a [unit = u"kg"^-1] @@ -112,12 +112,12 @@ noiseeqs = [0.1us"W" 0.1us"W" @parameters v [unit = u"m/s"] r [unit = u"m"^3 / u"s"] eqs = [D(L) ~ v, V ~ L^3] -@named sys = ODESystem(eqs, t) +@named sys = System(eqs, t) sys_simple = structural_simplify(sys) eqs = [D(V) ~ r, V ~ L^3] -@named sys = ODESystem(eqs, t) +@named sys = System(eqs, t) sys_simple = structural_simplify(sys) @variables V [unit = u"m"^3] L [unit = u"m"] diff --git a/test/equation_type_accessors.jl b/test/equation_type_accessors.jl index f118784f44..1bf92743ac 100644 --- a/test/equation_type_accessors.jl +++ b/test/equation_type_accessors.jl @@ -44,9 +44,9 @@ eqs2 = [X + Y + c ~ b * X^(X + Z + a) eqs3 = [D(X) ~ sqrt(X + b) + sqrt(Z + c) 2Z * (Z + Y) ~ D(Y) * log(a) D(Z) + c * X ~ b / (X + Y^d) + D(Z)] -@named osys1 = ODESystem(eqs1, t) -@named osys2 = ODESystem(eqs2, t) -@named osys3 = ODESystem(eqs3, t) +@named osys1 = System(eqs1, t) +@named osys2 = System(eqs2, t) +@named osys3 = System(eqs3, t) # Test `has...` for non-composed systems. @test has_alg_equations(osys1) diff --git a/test/error_handling.jl b/test/error_handling.jl index d5605e3cbc..d6c0fa8caa 100644 --- a/test/error_handling.jl +++ b/test/error_handling.jl @@ -14,7 +14,7 @@ function UnderdefinedConstantVoltage(; name, V = 1.0) V ~ p.v - n.v # Remove equation # 0 ~ p.i + n.i ] - ODESystem(eqs, t, [], [V], systems = [p, n], defaults = Dict(V => val), name = name) + System(eqs, t, [], [V], systems = [p, n], defaults = Dict(V => val), name = name) end function OverdefinedConstantVoltage(; name, V = 1.0, I = 1.0) @@ -27,7 +27,7 @@ function OverdefinedConstantVoltage(; name, V = 1.0, I = 1.0) # Overdefine p.i and n.i n.i ~ I p.i ~ I] - ODESystem(eqs, t, [], [V], systems = [p, n], defaults = Dict(V => val, I => val2), + System(eqs, t, [], [V], systems = [p, n], defaults = Dict(V => val, I => val2), name = name) end @@ -42,7 +42,7 @@ rc_eqs = [connect(source.p, resistor.p) connect(resistor.n, capacitor.p) connect(capacitor.n, source.n)] -@named rc_model = ODESystem(rc_eqs, t, systems = [resistor, capacitor, source]) +@named rc_model = System(rc_eqs, t, systems = [resistor, capacitor, source]) @test_throws ModelingToolkit.ExtraVariablesSystemException structural_simplify(rc_model) @named source2 = OverdefinedConstantVoltage(V = V, I = V / R) @@ -50,5 +50,5 @@ rc_eqs2 = [connect(source2.p, resistor.p) connect(resistor.n, capacitor.p) connect(capacitor.n, source2.n)] -@named rc_model2 = ODESystem(rc_eqs2, t, systems = [resistor, capacitor, source2]) +@named rc_model2 = System(rc_eqs2, t, systems = [resistor, capacitor, source2]) @test_throws ModelingToolkit.ExtraEquationsSystemException structural_simplify(rc_model2) diff --git a/test/extensions/ad.jl b/test/extensions/ad.jl index 73b1710812..8764200fac 100644 --- a/test/extensions/ad.jl +++ b/test/extensions/ad.jl @@ -21,7 +21,7 @@ u0 = [x => zeros(3), ps = [p => zeros(3, 3), q => 1.0] tspan = (0.0, 10.0) -@mtkbuild sys = ODESystem(eqs, t) +@mtkbuild sys = System(eqs, t) prob = ODEProblem(sys, u0, tspan, ps) sol = solve(prob, Tsit5()) @@ -37,7 +37,7 @@ end @testset "Issue#2997" begin pars = @parameters y0 mh Tγ0 Th0 h ργ0 vars = @variables x(t) - @named sys = ODESystem([D(x) ~ y0], + @named sys = System([D(x) ~ y0], t, vars, pars; @@ -58,7 +58,7 @@ end 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( +@named sys = System( Equation[], t, [], [a, b, c, d, e, f, g, h], continuous_events = [ModelingToolkit.SymbolicContinuousCallback( [a ~ 0] => [c ~ 0], discrete_parameters = c)]) @@ -112,7 +112,7 @@ fwd, back = ChainRulesCore.rrule(remake_buffer, sys, ps, idxs, vals) @variables y(t) 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) + @named sys = System(eqs, t; initialization_eqs) sys = structural_simplify(sys) # Find initial throw velocity that reaches exactly 10 m after 1 s @@ -129,7 +129,7 @@ end @testset "`sys.var` is non-differentiable" begin @variables x(t) - @mtkbuild sys = ODESystem(D(x) ~ x, t) + @mtkbuild sys = System(D(x) ~ x, t) prob = ODEProblem(sys, [x => 1.0], (0.0, 1.0)) grad = Zygote.gradient(prob) do prob diff --git a/test/extensions/bifurcationkit.jl b/test/extensions/bifurcationkit.jl index 629edf46a6..227cd175e0 100644 --- a/test/extensions/bifurcationkit.jl +++ b/test/extensions/bifurcationkit.jl @@ -60,7 +60,7 @@ let @variables x(t) y(t) z(t) eqs = [D(x) ~ -x + a * y + x^2 * y, D(y) ~ b - a * y - x^2 * y] - @named sys = ODESystem(eqs, t) + @named sys = System(eqs, t) sys = complete(sys) # Creates BifurcationProblem bprob = BifurcationProblem(sys, diff --git a/test/extensions/dynamic_optimization.jl b/test/extensions/dynamic_optimization.jl index 79810d2476..1074cae620 100644 --- a/test/extensions/dynamic_optimization.jl +++ b/test/extensions/dynamic_optimization.jl @@ -20,7 +20,7 @@ const M = ModelingToolkit eqs = [D(x(t)) ~ α * x(t) - β * x(t) * y(t), D(y(t)) ~ -γ * y(t) + δ * x(t) * y(t)] - @mtkbuild sys = ODESystem(eqs, t) + @mtkbuild sys = System(eqs, t) tspan = (0.0, 1.0) u0map = [x(t) => 4.0, y(t) => 2.0] parammap = [α => 1.5, β => 1.0, γ => 3.0, δ => 1.0] @@ -53,7 +53,7 @@ const M = ModelingToolkit u0map = Pair[] guess = [x(t) => 4.0, y(t) => 2.0] constr = [x(0.6) ~ 3.5, x(0.3) ~ 7.0] - @mtkbuild lksys = ODESystem(eqs, t; constraints = constr) + @mtkbuild lksys = System(eqs, t; constraints = constr) jprob = JuMPDynamicOptProblem(lksys, u0map, tspan, parammap; guesses = guess, dt = 0.01) @test JuMP.num_constraints(jprob.model) == 2 @@ -77,7 +77,7 @@ const M = ModelingToolkit # Test whole-interval constraints constr = [x(t) ≳ 1, y(t) ≳ 1] - @mtkbuild lksys = ODESystem(eqs, t; constraints = constr) + @mtkbuild lksys = System(eqs, t; constraints = constr) iprob = InfiniteOptDynamicOptProblem( lksys, u0map, tspan, parammap; guesses = guess, dt = 0.01) isol = solve(iprob, Ipopt.Optimizer, @@ -116,7 +116,7 @@ end @variables u(..) [bounds = (-1.0, 1.0), input = true] constr = [v(1.0) ~ 0.0] cost = [-x(1.0)] # Maximize the final distance. - @named block = ODESystem( + @named block = System( [D(x(t)) ~ v(t), D(v(t)) ~ u(t)], t; costs = cost, constraints = constr) block = structural_simplify(block; inputs = [u(t)]) @@ -139,7 +139,7 @@ end # Test dynamics @parameters (u_interp::ConstantInterpolation)(..) - @mtkbuild block_ode = ODESystem([D(x(t)) ~ v(t), D(v(t)) ~ u_interp(t)], t) + @mtkbuild block_ode = System([D(x(t)) ~ v(t), D(v(t)) ~ u_interp(t)], t) spline = ctrl_to_spline(jsol.input_sol, ConstantInterpolation) oprob = ODEProblem(block_ode, u0map, tspan, [u_interp => spline]) osol = solve(oprob, Vern8(), dt = 0.01, adaptive = false) @@ -165,7 +165,7 @@ end D(q(t)) ~ -ν * q(t) + c * (1 - α) * s * w(t)] costs = [-q(tspan[2])] - @named beesys = ODESystem(eqs, t; costs) + @named beesys = System(eqs, t; costs) beesys = structural_simplify(beesys; inputs = [α]) u0map = [w(t) => 40, q(t) => 2] pmap = [b => 1, c => 1, μ => 1, s => 1, ν => 1, α => 1] @@ -183,7 +183,7 @@ end @parameters (α_interp::LinearInterpolation)(..) eqs = [D(w(t)) ~ -μ * w(t) + b * s * α_interp(t) * w(t), D(q(t)) ~ -ν * q(t) + c * (1 - α_interp(t)) * s * w(t)] - @mtkbuild beesys_ode = ODESystem(eqs, t) + @mtkbuild beesys_ode = System(eqs, t) oprob = ODEProblem(beesys_ode, u0map, tspan, @@ -212,7 +212,7 @@ end (ts, te) = (0.0, 0.2) costs = [-h(te)] cons = [T(te) ~ 0, m(te) ~ m_c] - @named rocket = ODESystem(eqs, t; costs, constraints = cons) + @named rocket = System(eqs, t; costs, constraints = cons) rocket = structural_simplify(rocket; inputs = [T(t)]) u0map = [h(t) => h₀, m(t) => m₀, v(t) => 0] @@ -237,7 +237,7 @@ end eqs = [D(h(t)) ~ v(t), D(v(t)) ~ (T_interp(t) - drag(h(t), v(t))) / m(t) - gravity(h(t)), D(m(t)) ~ -T_interp(t) / c] - @mtkbuild rocket_ode = ODESystem(eqs, t) + @mtkbuild rocket_ode = System(eqs, t) interpmap = Dict(T_interp => ctrl_to_spline(jsol.input_sol, CubicSpline)) oprob = ODEProblem(rocket_ode, u0map, (ts, te), merge(Dict(pmap), interpmap)) osol = solve(oprob, RadauIIA5(); adaptive = false, dt = 0.001) @@ -260,7 +260,7 @@ end # Integral cost function 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) + @named rocket = System(eqs, t; costs, consolidate) rocket = structural_simplify(rocket; inputs = [u(t)]) u0map = [x(t) => 17.5] @@ -283,7 +283,7 @@ end constr = [v(tf) ~ 0, x(tf) ~ 0] cost = [tf] # Minimize time - @named block = ODESystem( + @named block = System( [D(x(t)) ~ v(t), D(v(t)) ~ u(t)], t; costs = cost, constraints = constr) block = structural_simplify(block, inputs = [u(t)]) @@ -327,7 +327,7 @@ end costs = [Symbolics.Integral(t in (0, tf))(u^2)] tspan = (0, tf) - @named cartpole = ODESystem(eqs, t; costs, constraints = cons) + @named cartpole = System(eqs, t; costs, constraints = cons) cartpole = structural_simplify(cartpole; inputs = [u]) u0map = [D(x(t)) => 0.0, D(θ(t)) => 0.0, θ(t) => 0.0, x(t) => 0.0] diff --git a/test/fmi/fmi.jl b/test/fmi/fmi.jl index edbbb312d6..b2684b4524 100644 --- a/test/fmi/fmi.jl +++ b/test/fmi/fmi.jl @@ -34,7 +34,7 @@ const FMU_DIR = joinpath(@__DIR__, "fmus") @named inner = MTK.FMIComponent( Val(2); fmu, communication_step_size = 1e-5, type = :CS) @variables x(t) = 1.0 - @mtkbuild sys = ODESystem([D(x) ~ x], t; systems = [inner]) + @mtkbuild sys = System([D(x) ~ x], t; systems = [inner]) test_no_inputs_outputs(sys) prob = ODEProblem{true, SciMLBase.FullSpecialize}( @@ -68,7 +68,7 @@ const FMU_DIR = joinpath(@__DIR__, "fmus") @named inner = MTK.FMIComponent( Val(3); fmu, communication_step_size = 1e-5, type = :CS) @variables x(t) = 1.0 - @mtkbuild sys = ODESystem([D(x) ~ x], t; systems = [inner]) + @mtkbuild sys = System([D(x) ~ x], t; systems = [inner]) test_no_inputs_outputs(sys) prob = ODEProblem{true, SciMLBase.FullSpecialize}( @@ -120,7 +120,7 @@ end @testset "IO Model" begin function build_simple_adder(adder) @variables a(t) b(t) c(t) [guess = 1.0] - @mtkbuild sys = ODESystem( + @mtkbuild sys = System( [adder.a ~ a, adder.b ~ b, D(a) ~ t, D(b) ~ adder.out + adder.c, c^2 ~ adder.out + adder.value], t; @@ -176,7 +176,7 @@ end function build_sspace_model(sspace) @variables u(t)=1.0 x(t)=1.0 y(t) [guess = 1.0] - @mtkbuild sys = ODESystem( + @mtkbuild sys = System( [sspace.u ~ u, D(u) ~ t, D(x) ~ sspace.x + sspace.y, y^2 ~ sspace.y + sspace.x], t; systems = [sspace] ) @@ -229,7 +229,7 @@ end @testset "FMUs in a loop" begin function build_looped_adders(adder1, adder2) @variables x(t) = 1 - @mtkbuild sys = ODESystem( + @mtkbuild sys = System( [D(x) ~ x, adder1.a ~ adder2.out2, adder2.a ~ adder1.out2, adder1.b ~ 1.0, adder2.b ~ 2.0], t; @@ -274,7 +274,7 @@ end function build_looped_sspace(sspace1, sspace2) @variables x(t) = 1 - @mtkbuild sys = ODESystem([D(x) ~ x, sspace1.u ~ sspace2.x, sspace2.u ~ sspace1.y], + @mtkbuild sys = System([D(x) ~ x, sspace1.u ~ sspace2.x, sspace2.u ~ sspace1.y], t; systems = [sspace1, sspace2]) prob = ODEProblem(sys, [sspace1.x => 1.0, sspace2.x => 1.0], (0.0, 1.0)) return sys, prob diff --git a/test/funcaffect.jl b/test/funcaffect.jl index 1e7c66f39a..8e73280eb3 100644 --- a/test/funcaffect.jl +++ b/test/funcaffect.jl @@ -10,7 +10,7 @@ eqs = [D(u) ~ -u] affect1!(integ, u, p, ctx) = integ.u[u.u] += 10 -@named sys = ODESystem(eqs, t, [u], [], +@named sys = System(eqs, t, [u], [], discrete_events = [[4.0] => (affect1!, [u], [], [], nothing)]) prob = ODEProblem(complete(sys), [u => 10.0], (0, 10.0)) sol = solve(prob, Tsit5()) @@ -37,7 +37,7 @@ cb1 = ModelingToolkit.SymbolicContinuousCallback([t ~ zr], (affect1!, [], [], [] @test hash(cb) == hash(cb1) # named tuple -sys1 = ODESystem(eqs, t, [u], [], name = :sys, +sys1 = System(eqs, t, [u], [], name = :sys, discrete_events = [ [4.0] => (f = affect1!, sts = [u], pars = [], discretes = [], ctx = nothing) ]) @@ -50,7 +50,7 @@ de = de[1] @test ModelingToolkit.conditions(de) == [4.0] @test ModelingToolkit.has_functional_affect(de) -sys2 = ODESystem(eqs, t, [u], [], name = :sys, +sys2 = System(eqs, t, [u], [], name = :sys, discrete_events = [[4.0] => [u ~ -u * h]]) @test !ModelingToolkit.has_functional_affect(ModelingToolkit.get_discrete_events(sys2)[1]) @@ -60,7 +60,7 @@ function affect2!(integ, u, p, ctx) ctx[1] *= 2 end ctx1 = [10.0] -@named sys = ODESystem(eqs, t, [u], [], +@named sys = System(eqs, t, [u], [], discrete_events = [[4.0, 8.0] => (affect2!, [u], [], [], ctx1)]) prob = ODEProblem(complete(sys), [u => 10.0], (0, 10.0)) sol = solve(prob, Tsit5()) @@ -77,7 +77,7 @@ function affect3!(integ, u, p, ctx) end @parameters a = 10.0 -@named sys = ODESystem(eqs, t, [u], [a], +@named sys = System(eqs, t, [u], [a], discrete_events = [[4.0, 8.0] => (affect3!, [u], [a], [a], nothing)]) prob = ODEProblem(complete(sys), [u => 10.0], (0, 10.0)) @@ -93,7 +93,7 @@ function affect3!(integ, u, p, ctx) integ.ps[p.b] *= 2 end -@named sys = ODESystem(eqs, t, [u], [a], +@named sys = System(eqs, t, [u], [a], discrete_events = [ [4.0, 8.0] => (affect3!, [u], [a => :b], [a], nothing) ]) @@ -107,18 +107,18 @@ i8 = findfirst(==(8.0), sol[:t]) # same name @variables v(t) -@test_throws ErrorException ODESystem(eqs, t, [u], [a], +@test_throws ErrorException System(eqs, t, [u], [a], discrete_events = [ [4.0, 8.0] => (affect3!, [u, v => :u], [a], [a], nothing) ]; name = :sys) -@test_nowarn ODESystem(eqs, t, [u], [a], +@test_nowarn System(eqs, t, [u], [a], discrete_events = [ [4.0, 8.0] => (affect3!, [u], [a => :u], [a], nothing) ]; name = :sys) -@named resistor = ODESystem(D(v) ~ v, t, [v], []) +@named resistor = System(D(v) ~ v, t, [v], []) # nested namespace ctx = [0] @@ -127,7 +127,7 @@ function affect4!(integ, u, p, ctx) @test u.resistor₊v == 1 end s1 = compose( - ODESystem(Equation[], t, [], [], name = :s1, + System(Equation[], t, [], [], name = :s1, discrete_events = 1.0 => (affect4!, [resistor.v], [], [], ctx)), resistor) s2 = structural_simplify(s1) @@ -143,7 +143,7 @@ function affect5!(integ, u, p, ctx) end @unpack capacitor = rc_model -@named event_sys = ODESystem(Equation[], t; +@named event_sys = System(Equation[], t; continuous_events = [ [capacitor.v ~ 0.3] => (affect5!, [capacitor.v], [capacitor.C => :C], [capacitor.C], nothing) @@ -175,7 +175,7 @@ function Capacitor2(; name, C = 1.0) D(v) ~ i / C ] extend( - ODESystem(eqs, t, [], ps; name = name, + System(eqs, t, [], ps; name = name, continuous_events = [[v ~ 0.3] => (affect6!, [v], [C], [C], nothing)]), oneport) end @@ -195,7 +195,7 @@ rc_eqs2 = [connect(shape.output, source.V) connect(capacitor2.n, source.n) connect(capacitor2.n, ground.g)] -@named rc_model2 = ODESystem(rc_eqs2, t) +@named rc_model2 = System(rc_eqs2, t) rc_model2 = compose(rc_model2, [resistor, capacitor2, shape, source, ground]) sys2 = structural_simplify(rc_model2) @@ -223,14 +223,14 @@ function Ball(; name, g = 9.8, anti_gravity_time = 1.0) pars = @parameters g = g sts = @variables x(t), v(t) eqs = [D(x) ~ v, D(v) ~ g] - ODESystem(eqs, t, sts, pars; name = name, + System(eqs, t, sts, pars; name = name, discrete_events = [[anti_gravity_time] => (affect7!, [], [g], [g], a7_ctx)]) end @named ball1 = Ball(anti_gravity_time = 1.0) @named ball2 = Ball(anti_gravity_time = 2.0) -@named balls = ODESystem(Equation[], t) +@named balls = System(Equation[], t) balls = compose(balls, [ball1, ball2]) @test ModelingToolkit.has_discrete_events(balls) @@ -282,7 +282,7 @@ function bb_affect!(integ, u, p, ctx) integ.u[u.v] = -integ.u[u.v] end -@named bb_model = ODESystem(bb_eqs, t, sts, par, +@named bb_model = System(bb_eqs, t, sts, par, continuous_events = [ [y ~ zr] => (bb_affect!, [v], [], [], nothing) ]) diff --git a/test/function_registration.jl b/test/function_registration.jl index a1d9041127..7ab9835433 100644 --- a/test/function_registration.jl +++ b/test/function_registration.jl @@ -17,7 +17,7 @@ end @register_symbolic do_something(a) eq = Dt(u) ~ do_something(x) + MyModule.do_something(x) -@named sys = ODESystem([eq], t, [u], [x]) +@named sys = System([eq], t, [u], [x]) sys = complete(sys) fun = ODEFunction(sys) @@ -40,7 +40,7 @@ end @register_symbolic do_something_2(a) eq = Dt(u) ~ do_something_2(x) + MyNestedModule.do_something_2(x) -@named sys = ODESystem([eq], t, [u], [x]) +@named sys = System([eq], t, [u], [x]) sys = complete(sys) fun = ODEFunction(sys) @@ -62,7 +62,7 @@ end @register_symbolic do_something_3(a) eq = Dt(u) ~ do_something_3(x) + (@__MODULE__).do_something_3(x) -@named sys = ODESystem([eq], t, [u], [x]) +@named sys = System([eq], t, [u], [x]) sys = complete(sys) fun = ODEFunction(sys) @@ -99,7 +99,7 @@ function build_ode() @parameters x @variables u(t) eq = Dt(u) ~ do_something_4(x) + (@__MODULE__).do_something_4(x) - @named sys = ODESystem([eq], t, [u], [x]) + @named sys = System([eq], t, [u], [x]) sys = complete(sys) fun = ODEFunction(sys, eval_expression = false) end diff --git a/test/guess_propagation.jl b/test/guess_propagation.jl index 738e930adc..f2131e28cb 100644 --- a/test/guess_propagation.jl +++ b/test/guess_propagation.jl @@ -10,7 +10,7 @@ eqs = [D(x) ~ 1 x ~ y] initialization_eqs = [1 ~ exp(1 + x)] -@named sys = ODESystem(eqs, t; initialization_eqs) +@named sys = System(eqs, t; initialization_eqs) sys = complete(structural_simplify(sys)) tspan = (0.0, 0.2) prob = ODEProblem(sys, [], tspan, []) @@ -27,7 +27,7 @@ eqs = [D(x) ~ 1 x ~ y] initialization_eqs = [1 ~ exp(1 + x)] -@named sys = ODESystem(eqs, t; initialization_eqs) +@named sys = System(eqs, t; initialization_eqs) sys = complete(structural_simplify(sys)) tspan = (0.0, 0.2) prob = ODEProblem(sys, [], tspan, []) @@ -45,7 +45,7 @@ eqs = [D(x) ~ a] initialization_eqs = [1 ~ exp(1 + x)] -@named sys = ODESystem(eqs, t; initialization_eqs) +@named sys = System(eqs, t; initialization_eqs) sys = complete(structural_simplify(sys)) tspan = (0.0, 0.2) @@ -65,7 +65,7 @@ eqs = [D(x) ~ a, initialization_eqs = [1 ~ exp(1 + x)] -@named sys = ODESystem(eqs, t; initialization_eqs) +@named sys = System(eqs, t; initialization_eqs) sys = complete(structural_simplify(sys)) tspan = (0.0, 0.2) @@ -80,7 +80,7 @@ sol = solve(prob.f.initializeprob; show_trace = Val(true)) @parameters x0 @variables x(t) @variables y(t) = x -@mtkbuild sys = ODESystem([x ~ x0, D(y) ~ x], t) +@mtkbuild sys = System([x ~ x0, D(y) ~ x], t) prob = ODEProblem(sys, [], (0.0, 1.0), [x0 => 1.0]) @test prob[x] == 1.0 @test prob[y] == 1.0 @@ -88,7 +88,7 @@ prob = ODEProblem(sys, [], (0.0, 1.0), [x0 => 1.0]) @parameters x0 @variables x(t) @variables y(t) = x0 -@mtkbuild sys = ODESystem([x ~ x0, D(y) ~ x], t) +@mtkbuild sys = System([x ~ x0, D(y) ~ x], t) prob = ODEProblem(sys, [], (0.0, 1.0), [x0 => 1.0]) @test prob[x] == 1.0 @test prob[y] == 1.0 @@ -96,7 +96,7 @@ prob = ODEProblem(sys, [], (0.0, 1.0), [x0 => 1.0]) @parameters x0 @variables x(t) @variables y(t) = x0 -@mtkbuild sys = ODESystem([x ~ y, D(y) ~ x], t) +@mtkbuild sys = System([x ~ y, D(y) ~ x], t) prob = ODEProblem(sys, [], (0.0, 1.0), [x0 => 1.0]) @test prob[x] == 1.0 @test prob[y] == 1.0 @@ -104,7 +104,7 @@ prob = ODEProblem(sys, [], (0.0, 1.0), [x0 => 1.0]) @parameters x0 @variables x(t) = x0 @variables y(t) = x -@mtkbuild sys = ODESystem([x ~ y, D(y) ~ x], t) +@mtkbuild sys = System([x ~ y, D(y) ~ x], t) prob = ODEProblem(sys, [], (0.0, 1.0), [x0 => 1.0]) @test prob[x] == 1.0 @test prob[y] == 1.0 diff --git a/test/hierarchical_initialization_eqs.jl b/test/hierarchical_initialization_eqs.jl index 1e3109a66e..e9ea36947e 100644 --- a/test/hierarchical_initialization_eqs.jl +++ b/test/hierarchical_initialization_eqs.jl @@ -24,7 +24,7 @@ A simple linear resistor model p.i + n.i ~ 0 # Ohm's Law v ~ i * R] - return ODESystem(eqs, t, vars, params; systems, name) + return System(eqs, t, vars, params; systems, name) end @connector Pin begin v(t) @@ -46,7 +46,7 @@ end i ~ p.i p.i + n.i ~ 0 v ~ V] - return ODESystem(eqs, t, vars, params; systems, name) + return System(eqs, t, vars, params; systems, name) end @component function Capacitor(; name, C = 1.0) @@ -68,7 +68,7 @@ end i ~ p.i p.i + n.i ~ 0 C * D(v) ~ i] - return ODESystem(eqs, t, vars, params; systems, name, initialization_eqs) + return System(eqs, t, vars, params; systems, name, initialization_eqs) end @component function Ground(; name) @@ -78,7 +78,7 @@ end eqs = [ g.v ~ 0 ] - return ODESystem(eqs, t, [], []; systems, name) + return System(eqs, t, [], []; systems, name) end @component function Inductor(; name, L = 1.0) @@ -97,7 +97,7 @@ end i ~ p.i p.i + n.i ~ 0 L * D(i) ~ v] - return ODESystem(eqs, t, vars, params; systems, name) + return System(eqs, t, vars, params; systems, name) end """ @@ -118,7 +118,7 @@ HTML as well. eqs = [connect(source.p, inductor.n) connect(inductor.p, resistor.p, capacitor.p) connect(resistor.n, ground.g, capacitor.n, source.n)] - return ODESystem(eqs, t, [], []; systems, name, initialization_eqs) + return System(eqs, t, [], []; systems, name, initialization_eqs) end """Run model RLCModel from 0 to 10""" function simple() diff --git a/test/index_cache.jl b/test/index_cache.jl index 455203d759..3048084d96 100644 --- a/test/index_cache.jl +++ b/test/index_cache.jl @@ -3,9 +3,9 @@ using ModelingToolkit: t_nounits as t # Ensure indexes of array symbolics are cached appropriately @variables x(t)[1:2] -@named sys = ODESystem(Equation[], t, [x], []) +@named sys = System(Equation[], t, [x], []) sys1 = complete(sys) -@named sys = ODESystem(Equation[], t, [x...], []) +@named sys = System(Equation[], t, [x...], []) sys2 = complete(sys) for sys in [sys1, sys2] for (sym, idx) in [(x, 1:2), (x[1], 1), (x[2], 2)] @@ -15,9 +15,9 @@ for sys in [sys1, sys2] end @variables x(t)[1:2, 1:2] -@named sys = ODESystem(Equation[], t, [x], []) +@named sys = System(Equation[], t, [x], []) sys1 = complete(sys) -@named sys = ODESystem(Equation[], t, [x...], []) +@named sys = System(Equation[], t, [x...], []) sys2 = complete(sys) for sys in [sys1, sys2] @test is_variable(sys, x) @@ -32,7 +32,7 @@ end @parameters p1 p2[1:2] p3::String @variables x(t) y(t)[1:2] z(t) -@named sys = ODESystem(Equation[], t, [x, y, z], [p1, p2, p3]) +@named sys = System(Equation[], t, [x, y, z], [p1, p2, p3]) sys = complete(sys) ic = ModelingToolkit.get_index_cache(sys) @@ -46,7 +46,7 @@ ic = ModelingToolkit.get_index_cache(sys) @testset "tunable_parameters is ordered" begin @parameters p q[1:3] r[1:2, 1:2] s [tunable = false] - @named sys = ODESystem(Equation[], t, [], [p, q, r, s]) + @named sys = System(Equation[], t, [], [p, q, r, s]) sys = complete(sys) @test all(splat(isequal), zip(tunable_parameters(sys), parameters(sys)[1:3])) @@ -65,7 +65,7 @@ end @testset "reorder_dimension_by_tunables" begin @parameters p q[1:3] r[1:2, 1:2] s [tunable = false] - @named sys = ODESystem(Equation[], t, [], [p, q, r, s]) + @named sys = System(Equation[], t, [], [p, q, r, s]) src = ones(8) dst = zeros(8) # system must be complete... @@ -108,7 +108,7 @@ end event1 = [1.0, 2, 3] => (update_affect!, [], [p_1], [p_1], nothing) - @named sys = ODESystem([ + @named sys = System([ ModelingToolkit.D_nounits(x) ~ p_1(x) ], ModelingToolkit.t_nounits; diff --git a/test/initial_values.jl b/test/initial_values.jl index 0ed8f7bffe..c030555b82 100644 --- a/test/initial_values.jl +++ b/test/initial_values.jl @@ -7,13 +7,13 @@ using SymbolicIndexingInterface @variables x(t)[1:3]=[1.0, 2.0, 3.0] y(t) z(t)[1:2] -@mtkbuild sys=ODESystem([D(x) ~ t * x], t) simplify=false +@mtkbuild sys=System([D(x) ~ t * x], t) simplify=false @test get_u0(sys, [])[1] == [1.0, 2.0, 3.0] @test get_u0(sys, [x => [2.0, 3.0, 4.0]])[1] == [2.0, 3.0, 4.0] @test get_u0(sys, [x[1] => 2.0, x[2] => 3.0, x[3] => 4.0])[1] == [2.0, 3.0, 4.0] @test get_u0(sys, [2.0, 3.0, 4.0])[1] == [2.0, 3.0, 4.0] -@mtkbuild sys=ODESystem([ +@mtkbuild sys=System([ D(x) ~ 3x, D(y) ~ t, D(z[1]) ~ z[2] + t, @@ -56,7 +56,7 @@ vals = ModelingToolkit.varmap_to_vars(var_vals, desired_values; defaults = defau @parameters k1 k2 Γ[1:1]=X1 + X2 eq = D(X1) ~ -k1 * X1 + k2 * (-X1 + Γ[1]) obs = X2 ~ Γ[1] - X1 -@mtkbuild osys_m = ODESystem([eq], t, [X1], [k1, k2, Γ[1]]; observed = [X2 ~ Γ[1] - X1]) +@mtkbuild osys_m = System([eq], t, [X1], [k1, k2, Γ[1]]; observed = [X2 ~ Γ[1] - X1]) # Creates ODEProblem. u0 = [X1 => 1.0, X2 => 2.0] @@ -77,14 +77,14 @@ target_varmap = Dict(p => ones(3), q => 2ones(3), q[1] => 2.0, q[2] => 2.0, q[3] # Issue#1283 @variables z(t)[1:2, 1:2] eqs = [D(D(z)) ~ ones(2, 2)] -@mtkbuild sys = ODESystem(eqs, t) +@mtkbuild sys = System(eqs, t) @test_nowarn ODEProblem(sys, [z => zeros(2, 2), D(z) => ones(2, 2)], (0.0, 10.0)) # Initialization with defaults involving parameters that are not part of the system # Issue#2817 @parameters A1 A2 B1 B2 @variables x1(t) x2(t) -@mtkbuild sys = ODESystem( +@mtkbuild sys = System( [ x1 ~ B1, x2 ~ B2 @@ -101,7 +101,7 @@ prob = ODEProblem(sys, [], (0.0, 1.0), [A1 => 0.3]) @parameters p = nothing @variables x(t)=nothing y(t) for sys in [ - ODESystem(Equation[], t, [x, y], [p]; defaults = [y => nothing], name = :osys), + System(Equation[], t, [x, y], [p]; defaults = [y => nothing], name = :osys), SDESystem(Equation[], [], t, [x, y], [p]; defaults = [y => nothing], name = :ssys), JumpSystem(Equation[], t, [x, y], [p]; defaults = [y => nothing], name = :jsys), NonlinearSystem(Equation[], [x, y], [p]; defaults = [y => nothing], name = :nsys), @@ -118,14 +118,14 @@ end # Issue#2799 @variables x(t) @parameters p -@mtkbuild sys = ODESystem([D(x) ~ p], t; defaults = [x => t, p => 2t]) +@mtkbuild sys = System([D(x) ~ p], t; defaults = [x => t, p => 2t]) prob = ODEProblem(sys, [], (1.0, 2.0), []) @test prob[x] == 1.0 @test prob.ps[p] == 2.0 @testset "Array of symbolics is unwrapped" begin @variables x(t)[1:2] y(t) - @mtkbuild sys = ODESystem([D(x) ~ x, D(y) ~ t], t; defaults = [x => [y, 3.0]]) + @mtkbuild sys = System([D(x) ~ x, D(y) ~ t], t; defaults = [x => [y, 3.0]]) prob = ODEProblem(sys, [y => 1.0], (0.0, 1.0)) @test eltype(prob.u0) <: Float64 prob = ODEProblem(sys, [x => [y, 4.0], y => 2.0], (0.0, 1.0)) @@ -135,7 +135,7 @@ end @testset "split=false systems with all parameter defaults" begin @variables x(t) = 1.0 @parameters p=1.0 q=2.0 r=3.0 - @mtkbuild sys=ODESystem(D(x) ~ p * x + q * t + r, t) split=false + @mtkbuild sys=System(D(x) ~ p * x + q * t + r, t) split=false prob = @test_nowarn ODEProblem(sys, [], (0.0, 1.0)) @test prob.p isa Vector{Float64} end @@ -147,7 +147,7 @@ end y ~ ifelse(t < c1, 0.0, (-c1 + t)^(c3))] sps = [x, y] ps = [c1, c2, c3] - @mtkbuild osys = ODESystem(eqs, t, sps, ps) + @mtkbuild osys = System(eqs, t, sps, ps) u0map = [x => 1.0] pmap = [c1 => 5.0, c2 => 1.0, c3 => 1.2] oprob = ODEProblem(osys, u0map, (0.0, 10.0), pmap) @@ -155,7 +155,7 @@ end @testset "Cyclic dependency checking and substitution limits" begin @variables x(t) y(t) - @mtkbuild sys = ODESystem( + @mtkbuild sys = System( [D(x) ~ x, D(y) ~ y], t; initialization_eqs = [x ~ 2y + 3, y ~ 2x], guesses = [x => 2y, y => 2x]) @test_warn ["Cycle", "unknowns", "x", "y"] try @@ -166,7 +166,7 @@ end sys, [x => 2y + 1, y => 2x], (0.0, 1.0); build_initializeprob = false) @parameters p q - @mtkbuild sys = ODESystem( + @mtkbuild sys = System( [D(x) ~ x * p, D(y) ~ y * q], t; guesses = [p => 1.0, q => 2.0]) # "unknowns" because they are initialization unknowns @test_warn ["Cycle", "unknowns", "p", "q"] try @@ -181,7 +181,7 @@ end @testset "`add_fallbacks!` checks scalarized array parameters correctly" begin @variables x(t)[1:2] @parameters p[1:2, 1:2] - @mtkbuild sys = ODESystem(D(x) ~ p * x, t) + @mtkbuild sys = System(D(x) ~ p * x, t) # used to throw a `MethodError` complaining about `getindex(::Nothing, ::CartesianIndex{2})` @test_throws ModelingToolkit.MissingParametersError ODEProblem( sys, [x => ones(2)], (0.0, 1.0)) @@ -195,7 +195,7 @@ end y[1] ~ x[3], y[2] ~ x[4] ] - @mtkbuild sys = ODESystem(eqs, t; defaults = [x => vcat(ones(2), y), y => x[1:2] ./ 2]) + @mtkbuild sys = System(eqs, t; defaults = [x => vcat(ones(2), y), y => x[1:2] ./ 2]) prob = ODEProblem(sys, [], (0.0, 1.0)) sol = solve(prob) @test SciMLBase.successful_retcode(sol) @@ -208,7 +208,7 @@ end eqs = [D(D(x)) ~ λ * x D(D(y)) ~ λ * y - g x^2 + y^2 ~ 1] - @mtkbuild pend = ODESystem(eqs, t) + @mtkbuild pend = System(eqs, t) @test_throws ModelingToolkit.MissingGuessError ODEProblem( pend, [x => 1], (0, 1), [g => 1], guesses = [y => λ, λ => y + 1]) @@ -217,7 +217,7 @@ end # Throw multiple if multiple are missing @variables a(t) b(t) c(t) d(t) e(t) eqs = [D(a) ~ b, D(b) ~ c, D(c) ~ d, D(d) ~ e, D(e) ~ 1] - @mtkbuild sys = ODESystem(eqs, t) + @mtkbuild sys = System(eqs, t) @test_throws ["a(t)", "c(t)"] ODEProblem( sys, [e => 2, a => b, b => a + 1, c => d, d => c + 1], (0, 1)) end @@ -229,7 +229,7 @@ end @variables x(t) @parameters (interp::Tspline)(..) - @mtkbuild sys = ODESystem(D(x) ~ interp(t), t) + @mtkbuild sys = System(D(x) ~ interp(t), t) prob = ODEProblem(sys, [x => 0.0], (0.0, 1.0), [interp => spline]) spline2 = LinearInterpolation(ts .^ 2, ts .^ 2) @@ -258,7 +258,7 @@ end @parameters p d @variables X(t) eqs = [D(X) ~ p - d * X] - @mtkbuild osys = ODESystem(eqs, t) + @mtkbuild osys = System(eqs, t) u0 = [X => 1.0f0] ps = [p => 1.0f0, d => 2.0f0] oprob = ODEProblem(osys, u0, (0.0f0, 1.0f0), ps) @@ -270,7 +270,7 @@ end @testset "Array initials and scalar parameters with `split = false`" begin @variables x(t)[1:2] @parameters p - @mtkbuild sys=ODESystem([D(x[1]) ~ x[1], D(x[2]) ~ x[2] + p], t) split=false + @mtkbuild sys=System([D(x[1]) ~ x[1], D(x[2]) ~ x[2] + p], t) split=false ps = Set(parameters(sys; initial_parameters = true)) @test length(ps) == 5 for i in 1:2 @@ -294,7 +294,7 @@ end 0 ~ x^2 + y^2 - w2^2 ] - @mtkbuild sys = ODESystem(eqs, t) + @mtkbuild sys = System(eqs, t) u0 = [D(x) => 2.0, x => 1.0, @@ -319,7 +319,7 @@ end D(y) ~ x * (ρ - z) - y, D(z) ~ x * y - β * z] - @mtkbuild sys = ODESystem(eqs, t) + @mtkbuild sys = System(eqs, t) u0 = SA[D(x) => 2.0f0, x => 1.0f0, @@ -342,7 +342,7 @@ end eqs = [D(D(x)) ~ λ * x D(D(y)) ~ λ * y - g x^2 + y^2 ~ 1] - @mtkbuild pend = ODESystem(eqs, t) + @mtkbuild pend = System(eqs, t) u0 = [x => 1.0, D(x) => 0.0] u0_constructor = p_constructor = vals -> SVector{length(vals)}(vals...) @@ -355,7 +355,7 @@ end @test state_values(initdata.initializeprob) isa SVector @test parameter_values(initdata.initializeprob).tunable isa SVector - @mtkbuild pend=ODESystem(eqs, t) split=false + @mtkbuild pend=System(eqs, t) split=false prob = ODEProblem(pend, u0, tspan; u0_constructor, p_constructor) @test prob.p isa SVector initdata = prob.f.initialization_data diff --git a/test/initializationsystem.jl b/test/initializationsystem.jl index 2cd8499e69..22fd6faf88 100644 --- a/test/initializationsystem.jl +++ b/test/initializationsystem.jl @@ -11,7 +11,7 @@ using DynamicQuantities eqs = [D(D(x)) ~ λ * x D(D(y)) ~ λ * y - g x^2 + y^2 ~ 1] -@mtkbuild pend = ODESystem(eqs, t) +@mtkbuild pend = System(eqs, t) initprob = ModelingToolkit.InitializationProblem(pend, 0.0, [], [g => 1]; guesses = [ModelingToolkit.missing_variable_defaults(pend); x => 1; y => 0.2]) @@ -348,7 +348,7 @@ eqs = [D(D(x)) ~ σ * (y - x), D(y) ~ x * (ρ - z) - y, D(z) ~ x * y - β * z] -@mtkbuild sys = ODESystem(eqs, t) +@mtkbuild sys = System(eqs, t) u0 = [D(x) => 2.0, y => 0.0, @@ -375,7 +375,7 @@ function System2(; name) end eqs = [D(dx) ~ ddx 0 ~ ddx + dx + 1] - return ODESystem(eqs, t, vars, []; name) + return System(eqs, t, vars, []; name) end @mtkbuild sys = System2() @@ -397,7 +397,7 @@ function System3(; name) initialization_eqs = [ ddx ~ -2 ] - return ODESystem(eqs, t, vars, []; name, initialization_eqs) + return System(eqs, t, vars, []; name, initialization_eqs) end @mtkbuild sys = System3() @@ -415,7 +415,7 @@ sol = solve(prob, Tsit5()) D(y) ~ x * (ρ - z) - y, D(z) ~ x * y - β * z] - @named sys = ODESystem(eqs, t) + @named sys = System(eqs, t) sys = structural_simplify(sys) u0 = [D(x) => 2.0, @@ -444,7 +444,7 @@ eqs = [D(x) ~ α * x - β * x * y D(y) ~ -γ * y + δ * x * y z ~ x + y] -@named sys = ODESystem(eqs, t) +@named sys = System(eqs, t) simpsys = structural_simplify(sys) tspan = (0.0, 10.0) @@ -472,7 +472,7 @@ sol = solve(prob, Tsit5()) eqs = [D(D(x)) ~ λ * x D(D(y)) ~ λ * y - g x^2 + y^2 ~ 1] -@mtkbuild pend = ODESystem(eqs, t) +@mtkbuild pend = System(eqs, t) prob = ODEProblem(pend, [x => 1], (0.0, 1.5), [g => 1], guesses = [λ => 0, y => 1], initialization_eqs = [y ~ 1]) @@ -484,8 +484,8 @@ sys = structural_simplify(unsimp; fully_determined = false) # Extend two systems with initialization equations and guesses # https://github.com/SciML/ModelingToolkit.jl/issues/2845 @variables x(t) y(t) -@named sysx = ODESystem([D(x) ~ 0], t; initialization_eqs = [x ~ 1]) -@named sysy = ODESystem([D(y) ~ 0], t; initialization_eqs = [y^2 ~ 2], guesses = [y => 1]) +@named sysx = System([D(x) ~ 0], t; initialization_eqs = [x ~ 1]) +@named sysy = System([D(y) ~ 0], t; initialization_eqs = [y^2 ~ 2], guesses = [y => 1]) sys = extend(sysx, sysy) @test length(equations(generate_initializesystem(sys))) == 2 @test length(ModelingToolkit.guesses(sys)) == 1 @@ -493,7 +493,7 @@ sys = extend(sysx, sysy) # https://github.com/SciML/ModelingToolkit.jl/issues/2873 @testset "Error on missing defaults" begin @variables x(t) y(t) - @named sys = ODESystem([x^2 + y^2 ~ 25, D(x) ~ 1], t) + @named sys = System([x^2 + y^2 ~ 25, D(x) ~ 1], t) ssys = structural_simplify(sys) @test_throws ModelingToolkit.MissingVariablesError ODEProblem( ssys, [x => 3], (0, 1), []) # y should have a guess @@ -505,7 +505,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 = System([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 # system 2 should solve to x = y = 2 sys2 = extend( sys1, - ODESystem([D(y) ~ 0], t; initialization_eqs = [y ~ 2], name = :sys2) + System([D(y) ~ 0], t; initialization_eqs = [y ~ 2], name = :sys2) ) |> structural_simplify ics2 = unknowns(sys1) .=> 2 # should be equivalent to "ics2 = [x => 2]" prob2 = ODEProblem(sys2, ics2, (0.0, 1.0), []; fully_determined = true) @@ -524,12 +524,12 @@ end # https://github.com/SciML/ModelingToolkit.jl/issues/3029 @testset "Derivatives in initialization equations" begin @variables x(t) - sys = ODESystem( + sys = System( [D(D(x)) ~ 0], t; initialization_eqs = [x ~ 0, D(x) ~ 1], name = :sys) |> structural_simplify @test_nowarn ODEProblem(sys, [], (0.0, 1.0), []) - sys = ODESystem( + sys = System( [D(D(x)) ~ 0], t; initialization_eqs = [x ~ 0, D(D(x)) ~ 0], name = :sys) |> structural_simplify @test_nowarn ODEProblem(sys, [D(x) => 1.0], (0.0, 1.0), []) @@ -539,7 +539,7 @@ end @testset "Derivatives in initialization guesses" begin for sign in [-1.0, +1.0] @variables x(t) - sys = ODESystem( + sys = System( [D(D(x)) ~ 0], t; initialization_eqs = [D(x)^2 ~ 1, x ~ 0], guesses = [D(x) => sign], name = :sys ) |> structural_simplify @@ -556,8 +556,8 @@ eqs_1st_order = [D(Y) + Y - ω ~ 0, X + k1 ~ Y + k2] eqs_2nd_order = [D(D(Y)) + 2ω * D(Y) + (ω^2) * Y ~ 0, X + k1 ~ Y + k2] -@mtkbuild sys_1st_order = ODESystem(eqs_1st_order, t) -@mtkbuild sys_2nd_order = ODESystem(eqs_2nd_order, t) +@mtkbuild sys_1st_order = System(eqs_1st_order, t) +@mtkbuild sys_2nd_order = System(eqs_2nd_order, t) u0_1st_order_1 = [X => 1.0, Y => 2.0] u0_1st_order_2 = [Y => 2.0] @@ -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]) + @named sys = System([D(x) ~ x, D(y) ~ y], t; initialization_eqs = [y ~ -x]) sys = structural_simplify(sys) prob = ODEProblem(sys, [sys.x => ones(5)], (0.0, 1.0), []) sol = solve(prob, Tsit5(), reltol = 1e-4) @@ -738,7 +738,7 @@ end @testset "Null system" begin @variables x(t) y(t) s(t) @parameters x0 y0 - @mtkbuild sys = ODESystem([x ~ x0, y ~ y0, s ~ x + y], t; guesses = [y0 => 0.0]) + @mtkbuild sys = System([x ~ x0, y ~ y0, s ~ x + y], t; guesses = [y0 => 0.0]) prob = ODEProblem(sys, [s => 1.0], (0.0, 1.0), [x0 => 0.3, y0 => missing]) # trivial initialization run immediately @test prob.ps[y0] ≈ 0.7 @@ -758,7 +758,7 @@ end @named gravity = Force() @named constant = Constant(; k = 9.81) @named damper = TM.Damper(; d = 0.1) - @mtkbuild sys = ODESystem( + @mtkbuild sys = System( [connect(fixed.flange, spring.flange_a), connect(spring.flange_b, mass.flange_a), connect(mass.flange_a, gravity.flange), connect(constant.output, gravity.f), connect(fixed.flange, damper.flange_a), connect(damper.flange_b, mass.flange_a)], @@ -1062,7 +1062,7 @@ end @testset "Nonnumeric parameter dependencies are retained" begin @variables x(t) y(t) @parameters foo(::Real, ::Real) p - @mtkbuild sys = ODESystem([D(x) ~ t, 0 ~ foo(x, y)], t; + @mtkbuild sys = System([D(x) ~ t, 0 ~ foo(x, y)], t; parameter_dependencies = [foo ~ Multiplier(p, 2p)], guesses = [y => -1.0]) prob = ODEProblem(sys, [x => 1.0], (0.0, 1.0), [p => 1.0]) integ = init(prob, Rosenbrock23()) @@ -1071,7 +1071,7 @@ end @testset "Use observed equations for guesses of observed variables" begin @variables x(t) y(t) [state_priority = 100] - @mtkbuild sys = ODESystem( + @mtkbuild sys = System( [D(x) ~ x + t, y ~ 2x + 1], t; initialization_eqs = [x^3 + y^3 ~ 1]) isys = ModelingToolkit.generate_initializesystem(sys) @test isequal(defaults(isys)[y], 2x + 1) @@ -1079,14 +1079,14 @@ end @testset "Create initializeprob when unknown has dependent value" begin @variables x(t) y(t) - @mtkbuild sys = ODESystem([D(x) ~ x, D(y) ~ t * y], t; defaults = [x => 2y]) + @mtkbuild sys = System([D(x) ~ x, D(y) ~ t * y], t; defaults = [x => 2y]) prob = ODEProblem(sys, [y => 1.0], (0.0, 1.0)) @test prob.f.initializeprob !== nothing integ = init(prob) @test integ[x] ≈ 2.0 @variables x(t)[1:2] y(t) - @mtkbuild sys = ODESystem([D(x) ~ x, D(y) ~ t], t; defaults = [x => [y, 3.0]]) + @mtkbuild sys = System([D(x) ~ x, D(y) ~ t], t; defaults = [x => [y, 3.0]]) prob = ODEProblem(sys, [y => 1.0], (0.0, 1.0)) @test prob.f.initializeprob !== nothing integ = init(prob) @@ -1101,7 +1101,7 @@ end eqs = [D(D(x)) ~ λ * x D(D(y)) ~ λ * y - g x^2 + y^2 ~ L] - @mtkbuild pend = ODESystem(eqs, t) + @mtkbuild pend = System(eqs, t) prob = ODEProblem(pend, [x => 1, y => 0], (0.0, 1.5), [g => 1], guesses = ModelingToolkit.missing_variable_defaults(pend)) @@ -1152,7 +1152,7 @@ end connect(L1.n, emf.p) connect(emf.n, source.n, ground.g)] - @named model = ODESystem(connections, t, + @named model = System(connections, t, systems = [ ground, ref, @@ -1185,7 +1185,7 @@ end eqs = [D(D(x)) ~ λ * x D(D(y)) ~ λ * y - g x^2 + y^2 ~ 1] - @mtkbuild pend = ODESystem(eqs, t) + @mtkbuild pend = System(eqs, t) @test_warn ["structurally singular", "initialization", "Guess", "heuristic"] ODEProblem( pend, [x => 1, y => 0], (0.0, 1.5), [g => 1], guesses = [λ => 1]) end @@ -1193,7 +1193,7 @@ end @testset "DAEProblem initialization" begin @variables x(t) [guess = 1.0] y(t) [guess = 1.0] @parameters p=missing [guess = 1.0] q=missing [guess = 1.0] - @mtkbuild sys = ODESystem( + @mtkbuild sys = System( [D(x) ~ p * y + q * t, x^3 + y^3 ~ 5], t; initialization_eqs = [p^2 + q^3 ~ 3]) # FIXME: solve for du0 @@ -1210,7 +1210,7 @@ end @testset "Guesses provided to `ODEProblem` are used in `remake`" begin @variables x(t) y(t)=2x @parameters p q=3x - @mtkbuild sys = ODESystem([D(x) ~ x * p + q, x^3 + y^3 ~ 3], t) + @mtkbuild sys = System([D(x) ~ x * p + q, x^3 + y^3 ~ 3], t) prob = ODEProblem( sys, [], (0.0, 1.0), [p => 1.0]; guesses = [x => 1.0, y => 1.0, q => 1.0]) @test prob[x] == 1.0 @@ -1240,7 +1240,7 @@ end @testset "Remake problem with no initializeprob" begin @variables x(t) [guess = 1.0] y(t) [guess = 1.0] @parameters p [guess = 1.0] q [guess = 1.0] - @mtkbuild sys = ODESystem( + @mtkbuild sys = System( [D(x) ~ p * x + q * y, y ~ 2x], t; parameter_dependencies = [q ~ 2p]) prob = ODEProblem(sys, [x => 1.0], (0.0, 1.0), [p => 1.0]) test_dummy_initialization_equation(prob, x) @@ -1261,7 +1261,7 @@ end @testset "Variables provided as symbols" begin @variables x(t) [guess = 1.0] y(t) [guess = 1.0] @parameters p [guess = 1.0] q [guess = 1.0] - @mtkbuild sys = ODESystem( + @mtkbuild sys = System( [D(x) ~ p * x + q * y, y ~ 2x], t; parameter_dependencies = [q ~ 2p]) prob = ODEProblem(sys, [:x => 1.0], (0.0, 1.0), [p => 1.0]) test_dummy_initialization_equation(prob, x) @@ -1275,7 +1275,7 @@ end @testset "Issue#3246: type promotion with parameter dependent initialization_eqs" begin @variables x(t)=1 y(t)=1 @parameters a = 1 - @named sys = ODESystem([D(x) ~ 0, D(y) ~ x + a], t; initialization_eqs = [y ~ a]) + @named sys = System([D(x) ~ 0, D(y) ~ x + a], t; initialization_eqs = [y ~ a]) ssys = structural_simplify(sys) prob = ODEProblem(ssys, [], (0, 1), []) @@ -1296,7 +1296,7 @@ end D(X) ~ p - d * X, D(Y) ~ p - d * Y ] - @mtkbuild osys = ODESystem(eqs, t) + @mtkbuild osys = System(eqs, t) # Make problem. u0_vals = [X => 4, Y => 5.0] @@ -1342,7 +1342,7 @@ end @testset "Solvable array parameters with scalarized guesses" begin @variables x(t) @parameters p[1:2] q - @mtkbuild sys = ODESystem( + @mtkbuild sys = System( D(x) ~ p[1] + p[2] + q, t; defaults = [p[1] => q, p[2] => 2q], guesses = [p[1] => q, p[2] => 2q]) @test ModelingToolkit.is_parameter_solvable(p, Dict(), defaults(sys), guesses(sys)) @@ -1356,7 +1356,7 @@ end @testset "Issue#3318: Mutating `Initial` parameters works" begin @variables x(t) y(t)[1:2] [guess = ones(2)] @parameters p[1:2, 1:2] - @mtkbuild sys = ODESystem( + @mtkbuild sys = System( [D(x) ~ x, D(y) ~ p * y], t; initialization_eqs = [x^2 + y[1]^2 + y[2]^2 ~ 4]) prob = ODEProblem(sys, [x => 1.0, y[1] => 1], (0.0, 1.0), [p => 2ones(2, 2)]) integ = init(prob, Tsit5()) @@ -1375,8 +1375,8 @@ end @testset "Issue#3342" begin @variables x(t) y(t) stop!(integrator, _, _, _) = terminate!(integrator) - @named sys = ODESystem([D(x) ~ 1.0 - D(y) ~ 1.0], t; initialization_eqs = [ + @named sys = System([D(x) ~ 1.0 + D(y) ~ 1.0], t; initialization_eqs = [ y ~ 0.0 ], continuous_events = [ @@ -1397,7 +1397,7 @@ end @testset "Issue#3330: Initialization for unsimplified systems" begin @variables x(t) [guess = 1.0] - @mtkbuild sys = ODESystem(D(x) ~ x, t; initialization_eqs = [x^2 ~ 4]) + @mtkbuild sys = System(D(x) ~ x, t; initialization_eqs = [x^2 ~ 4]) prob = ODEProblem(sys, [], (0.0, 1.0)) @test prob.f.initialization_data !== nothing end @@ -1405,7 +1405,7 @@ end @testset "`ReconstructInitializeprob` with `nothing` state" begin @parameters p @variables x(t) - @mtkbuild sys = ODESystem(x ~ p * t, t) + @mtkbuild sys = System(x ~ p * t, t) prob = @test_nowarn ODEProblem(sys, [], (0.0, 1.0), [p => 1.0]) @test_nowarn remake(prob, p = [p => 1.0]) @test_nowarn remake(prob, p = [p => ForwardDiff.Dual(1.0)]) @@ -1419,7 +1419,7 @@ end D(X1) ~ k1 * (Γ[1] - X1) - k2 * X1 ] obs = [X2 ~ Γ[1] - X1] - @mtkbuild osys = ODESystem(eqs, t, [X1, X2], [k1, k2, Γ]; observed = obs) + @mtkbuild osys = System(eqs, t, [X1, X2], [k1, k2, Γ]; observed = obs) u0 = [X1 => 1.0, X2 => 2.0] ps = [k1 => 0.1, k2 => 0.2] @@ -1517,7 +1517,7 @@ end @testset "Issue#3504: Update initials when `remake` called with non-symbolic `u0`" begin @variables x(t) y(t) @parameters c1 c2 - @mtkbuild sys = ODESystem([D(x) ~ -c1 * x + c2 * y, D(y) ~ c1 * x - c2 * y], t) + @mtkbuild sys = System([D(x) ~ -c1 * x + c2 * y, D(y) ~ c1 * x - c2 * y], t) prob1 = ODEProblem(sys, [1.0, 2.0], (0.0, 1.0), [c1 => 1.0, c2 => 2.0]) prob2 = remake(prob1, u0 = [2.0, 3.0]) prob3 = remake(prob1, u0 = [2.0, 3.0], p = [c1 => 2.0]) @@ -1534,7 +1534,7 @@ end @parameters α=1 β=1 γ=1 δ=1 @variables x(t)=1 y(t)=1 eqs = [D(x) ~ α * x - β * x * y, D(y) ~ -δ * y + γ * x * y] - @named sys = ODESystem(eqs, t) + @named sys = System(eqs, t) prob = ODEProblem(complete(sys), [], (0.0, 1)) @inferred remake(prob; u0 = 2 .* prob.u0, p = prob.p) @inferred solve(prob) @@ -1546,7 +1546,7 @@ end eqs = [D(D(x)) ~ λ * x D(D(y)) ~ λ * y - g x^2 + y^2 ~ 1] - @mtkbuild pend = ODESystem(eqs, t) + @mtkbuild pend = System(eqs, t) prob = ODEProblem( pend, [x => (√2 / 2), D(x) => 0.0], (0.0, 1.5), @@ -1601,7 +1601,7 @@ end 0 ~ x^2 + y^2 - w2^2 ] - @mtkbuild sys = ODESystem(eqs, t) + @mtkbuild sys = System(eqs, t) u0 = [D(x) => 2.0, x => 1.0, @@ -1632,7 +1632,7 @@ end eqs = [D(D(x)) ~ λ * x D(D(y)) ~ λ * y - g x^2 + y^2 ~ 1] - @mtkbuild pend=ODESystem(eqs, t) split=false + @mtkbuild pend=System(eqs, t) split=false prob = ODEProblem(pend, [x => 1.0, D(x) => 0.0], (0.0, 1.0), [g => 1.0]; guesses = [y => 1.0, λ => 1.0]) @test !ModelingToolkit.is_split(prob.f.initialization_data.initializeprob.f.sys) @@ -1644,7 +1644,7 @@ end eqs = [D(D(x)) ~ λ * x D(D(y)) ~ λ * y - g x^2 + y^2 ~ 1] - @mtkbuild pend = ODESystem(eqs, t) + @mtkbuild pend = System(eqs, t) prob = ODEProblem(pend, SA[x => 1.0, D(x) => 0.0], (0.0, 1.0), SA[g => 1.0]; guesses = [y => 1.0, λ => 1.0]) @test !SciMLBase.isinplace(prob) diff --git a/test/input_output_handling.jl b/test/input_output_handling.jl index 68936b52bd..1fd7732c50 100644 --- a/test/input_output_handling.jl +++ b/test/input_output_handling.jl @@ -6,7 +6,7 @@ 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) +@named model = System(eqs, t) @test_throws ExtraVariablesSystemException structural_simplify(model) if VERSION >= v"1.8" err = "In particular, the unset input(s) are:\n some_input(t)" @@ -17,14 +17,14 @@ end @variables x(t) u(t) [input = true] v(t)[1:2] [input = true] @test isinput(u) -@named sys = ODESystem([D(x) ~ -x + u], t) # both u and x are unbound -@named sys1 = ODESystem([D(x) ~ -x + v[1] + v[2]], t) # both v and x are unbound -@named sys2 = ODESystem([D(x) ~ -sys.x], t, systems = [sys]) # this binds sys.x in the context of sys2, sys2.x is still unbound -@named sys21 = ODESystem([D(x) ~ -sys1.x], t, systems = [sys1]) # this binds sys.x in the context of sys2, sys2.x is still unbound -@named sys3 = ODESystem([D(x) ~ -sys.x + sys.u], t, systems = [sys]) # This binds both sys.x and sys.u -@named sys31 = ODESystem([D(x) ~ -sys1.x + sys1.v[1]], t, systems = [sys1]) # This binds both sys.x and sys1.v[1] +@named sys = System([D(x) ~ -x + u], t) # both u and x are unbound +@named sys1 = System([D(x) ~ -x + v[1] + v[2]], t) # both v and x are unbound +@named sys2 = System([D(x) ~ -sys.x], t, systems = [sys]) # this binds sys.x in the context of sys2, sys2.x is still unbound +@named sys21 = System([D(x) ~ -sys1.x], t, systems = [sys1]) # this binds sys.x in the context of sys2, sys2.x is still unbound +@named sys3 = System([D(x) ~ -sys.x + sys.u], t, systems = [sys]) # This binds both sys.x and sys.u +@named sys31 = System([D(x) ~ -sys1.x + sys1.v[1]], t, systems = [sys1]) # This binds both sys.x and sys1.v[1] -@named sys4 = ODESystem([D(x) ~ -sys.x, u ~ sys.u], t, systems = [sys]) # This binds both sys.x and sys3.u, this system is one layer deeper than the previous. u is directly forwarded to sys.u, and in this case sys.u is bound while u is not +@named sys4 = System([D(x) ~ -sys.x, u ~ sys.u], t, systems = [sys]) # This binds both sys.x and sys3.u, this system is one layer deeper than the previous. u is directly forwarded to sys.u, and in this case sys.u is bound while u is not @test has_var(x ~ 1, x) @test has_var(1 ~ x, x) @@ -87,10 +87,10 @@ fsys4 = flatten(sys4) # Test output handling @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 +@named sys = System([D(x) ~ -x, y ~ x], t) # both y and x are unbound 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]) +@named sys2 = System([D(x) ~ -sys.x, y ~ sys.y], t, systems = [sys]) @test !is_bound(sys, y) @test !is_bound(sys, x) @@ -127,7 +127,7 @@ eqs = [connect(torque.flange, inertia1.flange_a) connect(inertia1.flange_b, spring.flange_a, damper.flange_a) connect(inertia2.flange_a, spring.flange_b, damper.flange_b) y ~ inertia2.w + torque.tau.u] -model = ODESystem(eqs, t; systems = [torque, inertia1, inertia2, spring, damper], +model = System(eqs, t; systems = [torque, inertia1, inertia2, spring, damper], name = :name, guesses = [spring.flange_a.phi => 0.0]) model_outputs = [inertia1.w, inertia2.w, inertia1.phi, inertia2.phi] model_inputs = [torque.tau.u] @@ -164,7 +164,7 @@ end D(x) ~ -x + u ] - @named sys = ODESystem(eqs, t) + @named sys = System(eqs, t) sys = structural_simplify(sys, inputs = [u]) f, dvs, ps, io_sys = ModelingToolkit.generate_control_function(sys; simplify, split) @@ -182,7 +182,7 @@ end D(x) ~ -x + u + d^2 ] - @named sys = ODESystem(eqs, t) + @named sys = System(eqs, t) sys = structural_simplify(sys, inputs = [u], disturbance_inputs = [d]) f, dvs, ps, io_sys = ModelingToolkit.generate_control_function(sys; simplify, split) @@ -200,7 +200,7 @@ end D(x) ~ -x + u + d^2 ] - @named sys = ODESystem(eqs, t) + @named sys = System(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) @@ -226,25 +226,25 @@ function Mass(; name, m = 1.0, p = 0, v = 0) sts = @variables pos(t)=p vel(t)=v eqs = [D(pos) ~ vel y ~ pos] - ODESystem(eqs, t, [pos, vel, y], ps; name) + System(eqs, t, [pos, vel, y], ps; name) end function MySpring(; name, k = 1e4) ps = @parameters k = k @variables x(t) = 0 # Spring deflection - ODESystem(Equation[], t, [x], ps; name) + System(Equation[], t, [x], ps; name) end function MyDamper(; name, c = 10) ps = @parameters c = c @variables vel(t) = 0 - ODESystem(Equation[], t, [vel], ps; name) + System(Equation[], t, [vel], ps; name) end function SpringDamper(; name, k = false, c = false) spring = MySpring(; name = :spring, k) damper = MyDamper(; name = :damper, c) - compose(ODESystem(Equation[], t; name), + compose(System(Equation[], t; name), spring, damper) end @@ -264,7 +264,7 @@ c = 10 eqs = [connect_sd(sd, mass1, mass2) D(mass1.vel) ~ (sd_force(sd) + u) / mass1.m D(mass2.vel) ~ (-sd_force(sd)) / mass2.m] -@named _model = ODESystem(eqs, t) +@named _model = System(eqs, t) @named model = compose(_model, mass1, mass2, sd); model = structural_simplify(model, inputs = [u]) @@ -282,7 +282,7 @@ i = findfirst(isequal(u[1]), out) @variables x(t) u(t) [input = true] eqs = [D(x) ~ u] -@named sys = ODESystem(eqs, t) +@named sys = System(eqs, t) @test_nowarn structural_simplify(sys, inputs = [u], outputs = []) #= @@ -314,7 +314,7 @@ function SystemModel(u = nothing; name = :model) connect(inertia2.flange_a, spring.flange_b, damper.flange_b)] if u !== nothing push!(eqs, connect(torque.tau, u.output)) - return @named model = ODESystem(eqs, t; + return @named model = System(eqs, t; systems = [ torque, inertia1, @@ -324,7 +324,7 @@ function SystemModel(u = nothing; name = :model) u ]) end - ODESystem(eqs, t; systems = [torque, inertia1, inertia2, spring, damper], + System(eqs, t; systems = [torque, inertia1, inertia2, spring, damper], name, guesses = [spring.flange_a.phi => 0.0]) end @@ -365,7 +365,7 @@ eqs = [D(y₁) ~ -k₁ * y₁ + k₃ * y₂ * y₃ + u1 D(y₂) ~ k₁ * y₁ - k₃ * y₂ * y₃ - k₂ * y₂^2 + u2 y₁ + y₂ + y₃ ~ 1] -@named sys = ODESystem(eqs, t) +@named sys = System(eqs, t) m_inputs = [u[1], u[2]] m_outputs = [y₂] sys_simp = structural_simplify(sys, inputs = m_inputs, outputs = m_outputs) @@ -377,7 +377,7 @@ sys_simp = structural_simplify(sys, inputs = m_inputs, outputs = m_outputs) @named gain = Gain(1;) @named int = Integrator(; k = 1) @named fb = Feedback(;) -@named model = ODESystem( +@named model = System( [ connect(c.output, fb.input1), connect(fb.input2, int.output), @@ -434,7 +434,7 @@ matrices = ModelingToolkit.reorder_unknowns( D(x) ~ -x + u ] - @named sys = ODESystem(eqs, t) + @named sys = System(eqs, t) sys = structural_simplify(sys, inputs = [u]) (; io_sys,) = ModelingToolkit.generate_control_function(sys, simplify = true) obsfn = ModelingToolkit.build_explicit_observed_function( @@ -447,7 +447,7 @@ end @constants c = 2.0 @variables x(t) eqs = [D(x) ~ c * x] - @mtkbuild sys = ODESystem(eqs, t, [x], []) + @mtkbuild sys = System(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] @@ -457,7 +457,7 @@ end @variables x(t)=0 u(t)=0 [input = true] @parameters p(::Real) = (x -> 2x) eqs = [D(x) ~ -x + p(u)] - @named sys = ODESystem(eqs, t) + @named sys = System(eqs, t) sys = structural_simplify(sys, inputs = [u]) f, dvs, ps, io_sys = ModelingToolkit.generate_control_function(sys) p = MTKParameters(io_sys, []) diff --git a/test/inputoutput.jl b/test/inputoutput.jl index 2a81a0e315..28f112425f 100644 --- a/test/inputoutput.jl +++ b/test/inputoutput.jl @@ -10,12 +10,12 @@ eqs = [D(x) ~ σ * (y - x) + F, D(z) ~ x * y - β * z] aliases = [u ~ x + y - z] -lorenz1 = ODESystem(eqs, pins = [F], observed = aliases, name = :lorenz1) -lorenz2 = ODESystem(eqs, pins = [F], observed = aliases, name = :lorenz2) +lorenz1 = System(eqs, pins = [F], observed = aliases, name = :lorenz1) +lorenz2 = System(eqs, pins = [F], observed = aliases, name = :lorenz2) connections = [lorenz1.F ~ lorenz2.u, lorenz2.F ~ lorenz1.u] -connected = ODESystem(Equation[], t, [], [], observed = connections, +connected = System(Equation[], t, [], [], observed = connections, systems = [lorenz1, lorenz2]) sys = connected diff --git a/test/jacobiansparsity.jl b/test/jacobiansparsity.jl index 1f936bc878..d10ec66c44 100644 --- a/test/jacobiansparsity.jl +++ b/test/jacobiansparsity.jl @@ -91,7 +91,7 @@ prob = ODEProblem(sys, u0, (0, 11.5), sparse = true, jac = true) eqs = [D(D(x)) ~ λ * x D(D(y)) ~ λ * y - g x^2 + y^2 ~ 1] - @mtkbuild pend = ODESystem(eqs, t) + @mtkbuild pend = System(eqs, t) u0 = [x => 1, y => 0] prob = ODEProblem( @@ -125,7 +125,7 @@ end eqs = [D(D(x)) ~ λ * x D(D(y)) ~ λ * y - g x^2 + y^2 ~ 1] - @mtkbuild pend = ODESystem(eqs, t) + @mtkbuild pend = System(eqs, t) prob = ODEProblem(pend, [x => 0.0, D(x) => 1.0], (0.0, 1.0), [g => 1.0]; guesses = [y => 1.0, λ => 1.0], jac = true, sparse = true) J = deepcopy(prob.f.jac_prototype) diff --git a/test/labelledarrays.jl b/test/labelledarrays.jl index c9ee7ee50b..59c269dff7 100644 --- a/test/labelledarrays.jl +++ b/test/labelledarrays.jl @@ -12,7 +12,7 @@ eqs = [D(x) ~ σ * (y - x), D(y) ~ t * x * (ρ - z) - y, D(z) ~ x * y - β * z] -@named de = ODESystem(eqs, t) +@named de = System(eqs, t) de = complete(de) ff = ODEFunction(de, [x, y, z], [σ, ρ, β], jac = true) diff --git a/test/latexify.jl b/test/latexify.jl index 105a17ca6e..de5c195610 100644 --- a/test/latexify.jl +++ b/test/latexify.jl @@ -55,6 +55,6 @@ eqs = [D(x) ~ (1 + cos(t)) / (1 + 2 * x)] ap = AnalysisPoint(:plant_input) eqs = [connect(P.output, C.input) connect(C.output, ap, P.input)] -sys_ap = ODESystem(eqs, t, systems = [P, C], name = :hej) +sys_ap = System(eqs, t, systems = [P, C], name = :hej) @test_reference "latexify/50.tex" latexify(sys_ap) diff --git a/test/linearity.jl b/test/linearity.jl index aed9a256d2..d472cdb087 100644 --- a/test/linearity.jl +++ b/test/linearity.jl @@ -12,16 +12,16 @@ eqs = [D(x) ~ σ * (y - x), D(y) ~ -z - y, D(z) ~ y - β * z] -@test ModelingToolkit.islinear(@named sys = ODESystem(eqs, t)) +@test ModelingToolkit.islinear(@named sys = System(eqs, t)) eqs2 = [D(x) ~ σ * (y - x), D(y) ~ -z - 1 / y, D(z) ~ y - β * z] -@test !ModelingToolkit.islinear(@named sys = ODESystem(eqs2, t)) +@test !ModelingToolkit.islinear(@named sys = System(eqs2, t)) eqs3 = [D(x) ~ σ * (y - x), D(y) ~ -z - y, D(z) ~ y - β * z + 1] -@test ModelingToolkit.isaffine(@named sys = ODESystem(eqs, t)) +@test ModelingToolkit.isaffine(@named sys = System(eqs, t)) diff --git a/test/lowering_solving.jl b/test/lowering_solving.jl index 300d505ab0..8c4db26287 100644 --- a/test/lowering_solving.jl +++ b/test/lowering_solving.jl @@ -8,14 +8,14 @@ eqs = [D(D(x)) ~ σ * (y - x), D(y) ~ x * (ρ - z) - y, D(z) ~ x * y - β * z] -@named sys′ = ODESystem(eqs, t) +@named sys′ = System(eqs, t) sys = ode_order_lowering(sys′) eqs2 = [0 ~ x * y - k, D(D(x)) ~ σ * (y - x), D(y) ~ x * (ρ - z) - y, D(z) ~ x * y - β * z] -@named sys2 = ODESystem(eqs2, t, [x, y, z, k], parameters(sys′)) +@named sys2 = System(eqs2, t, [x, y, z, k], parameters(sys′)) sys2 = ode_order_lowering(sys2) # test equation/variable ordering ModelingToolkit.calculate_massmatrix(sys2) == Diagonal([1, 1, 1, 1, 0]) @@ -46,13 +46,13 @@ eqs = [D(x) ~ σ * (y - x), D(y) ~ x * (ρ - z) - y, D(z) ~ x * y - β * z] -lorenz1 = ODESystem(eqs, t, name = :lorenz1) -lorenz2 = ODESystem(eqs, t, name = :lorenz2) +lorenz1 = System(eqs, t, name = :lorenz1) +lorenz2 = System(eqs, t, name = :lorenz2) @variables α(t) @parameters γ connections = [0 ~ lorenz1.x + lorenz2.y + α * γ] -@named connected = ODESystem(connections, t, [α], [γ], systems = [lorenz1, lorenz2]) +@named connected = System(connections, t, [α], [γ], systems = [lorenz1, lorenz2]) connected = complete(connected) u0 = [lorenz1.x => 1.0, lorenz1.y => 0.0, diff --git a/test/mass_matrix.jl b/test/mass_matrix.jl index 8b31123834..f181bdfbde 100644 --- a/test/mass_matrix.jl +++ b/test/mass_matrix.jl @@ -8,9 +8,9 @@ eqs = [D(y[1]) ~ -k[1] * y[1] + k[3] * y[2] * y[3], D(y[2]) ~ k[1] * y[1] - k[3] * y[2] * y[3] - k[2] * y[2]^2, 0 ~ y[1] + y[2] + y[3] - 1] -@named sys = ODESystem(eqs, t, collect(y), [k]) +@named sys = System(eqs, t, collect(y), [k]) sys = complete(sys) -@test_throws ArgumentError ODESystem(eqs, y[1]) +@test_throws ArgumentError System(eqs, y[1]) M = calculate_massmatrix(sys) @test M isa Diagonal @test M == [1 0 0 @@ -45,7 +45,7 @@ sol2 = solve(prob_mm2, Rodas5(), reltol = 1e-8, abstol = 1e-8, tstops = sol.t, # Test mass matrix in the identity case eqs = [D(y[1]) ~ y[1], D(y[2]) ~ y[2], D(y[3]) ~ y[3]] -@named sys = ODESystem(eqs, t, collect(y), [k]) +@named sys = System(eqs, t, collect(y), [k]) @test calculate_massmatrix(sys) === I @@ -54,7 +54,7 @@ eqs = [D(y[1]) ~ y[1], D(y[2]) ~ y[2], D(y[3]) ~ y[3]] D(y[2]) ~ k[1] * y[1] - k[3] * y[2] * y[3] - k[2] * y[2]^2, 0 ~ y[1] + y[2] + y[3] - 1] - @named sys = ODESystem(eqs, t, collect(y), [k]) + @named sys = System(eqs, t, collect(y), [k]) @named sys = SDESystem(sys, [1, 1, 0]) sys = complete(sys) prob = SDEProblem(sys, [y => [1.0, 0.0, 0.0]], (0.0, 1e5), [k => [0.04, 3e7, 1e4]]) diff --git a/test/model_parsing.jl b/test/model_parsing.jl index ad457a0ba3..7271d9bd1d 100644 --- a/test/model_parsing.jl +++ b/test/model_parsing.jl @@ -1044,17 +1044,17 @@ end x + y EvalAt(1)(y)^2 end - @consolidate f(u) = u[1]^2 + log(u[2]) + @consolidate f(u, sub) = u[1]^2 + log(u[2]) + sum(sub; init = 0) end @named ex = Example() ex = complete(ex) costs = ModelingToolkit.get_costs(ex) - constrs = ModelingToolkit.get_constraints(ModelingToolkit.get_constraintsystem(ex)) + constrs = ModelingToolkit.get_constraints(ex) @test isequal(costs[1], ex.x + ex.y) @test isequal(costs[2], EvalAt(1)(ex.y)^2) - @test isequal(constrs[1], -3 + EvalAt(0.3)(ex.x) ~ 0) - @test isequal(constrs[2], -4 + ex.y ≲ 0) - @test ModelingToolkit.get_consolidate(ex)([1, 2]) ≈ 1 + log(2) + @test isequal(constrs[1], EvalAt(0.3)(ex.x) ~ 3) + @test isequal(constrs[2], ex.y ≲ 4) + @test ModelingToolkit.get_consolidate(ex)([1, 2], [3, 4]) ≈ 8 + log(2) end diff --git a/test/modelingtoolkitize.jl b/test/modelingtoolkitize.jl index db99cc91a4..d146fa95c9 100644 --- a/test/modelingtoolkitize.jl +++ b/test/modelingtoolkitize.jl @@ -375,7 +375,7 @@ sys = modelingtoolkitize(prob) @testset "ODE" begin @variables x(t)=1.0 y(t)=2.0 @parameters p=3.0 q=4.0 - @mtkbuild sys = ODESystem([D(x) ~ p * y, D(y) ~ q * x], t) + @mtkbuild sys = System([D(x) ~ p * y, D(y) ~ q * x], t) prob1 = ODEProblem(sys, [], (0.0, 5.0)) newsys = complete(modelingtoolkitize(prob1)) @test is_variable(newsys, newsys.x) diff --git a/test/mtkparameters.jl b/test/mtkparameters.jl index 809da4df94..122b7acdd1 100644 --- a/test/mtkparameters.jl +++ b/test/mtkparameters.jl @@ -8,7 +8,7 @@ using ForwardDiff 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( +@named sys = System( Equation[], t, [], [a, c, d, e, f, g, h], parameter_dependencies = [b ~ 2a], continuous_events = [ModelingToolkit.SymbolicContinuousCallback( [a ~ 0] => [c ~ 0], discrete_parameters = c)], defaults = Dict(a => 0.0)) @@ -140,7 +140,7 @@ end @parameters p::Vector{Float64} @variables X(t) eq = D(X) ~ p[1] - p[2] * X -@mtkbuild osys = ODESystem([eq], t) +@mtkbuild osys = System([eq], t) u0 = [X => 1.0] ps = [p => [2.0, 0.1]] @@ -149,7 +149,7 @@ p = MTKParameters(osys, ps, u0) # Ensure partial update promotes the buffer @parameters p q r -@named sys = ODESystem(Equation[], t, [], [p, q, r]) +@named sys = System(Equation[], t, [], [p, q, r]) sys = complete(sys) ps = MTKParameters(sys, [p => 1.0, q => 2.0, r => 3.0]) newps = remake_buffer(sys, ps, (p,), (1.0f0,)) @@ -160,7 +160,7 @@ newps = remake_buffer(sys, ps, (p,), (1.0f0,)) @parameters p d @variables X(t) eqs = [D(X) ~ p - d * X] -@mtkbuild sys = ODESystem(eqs, t) +@mtkbuild sys = System(eqs, t) u0 = [X => 1.0] tspan = (0.0, 100.0) @@ -180,7 +180,7 @@ function level1() eqs = [D(x) ~ p1 * x - p2 * x * y D(y) ~ -p3 * y + p4 * x * y] - sys = structural_simplify(complete(ODESystem( + sys = structural_simplify(complete(System( eqs, t, tspan = (0, 3.0), name = :sys, parameter_dependencies = [y0 => 2p4]))) prob = ODEProblem{true, SciMLBase.FullSpecialize}(sys) end @@ -194,7 +194,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 = structural_simplify(complete(System( eqs, t, tspan = (0, 3.0), name = :sys, parameter_dependencies = [y0 => 2p4]))) prob = ODEProblem{true, SciMLBase.FullSpecialize}(sys) end @@ -208,7 +208,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 = structural_simplify(complete(System( eqs, t, tspan = (0, 3.0), name = :sys, parameter_dependencies = [y0 => 2p4]))) prob = ODEProblem{true, SciMLBase.FullSpecialize}(sys) end @@ -248,7 +248,7 @@ end @variables x(t) y(t) eqs = [D(x) ~ (α - β * y) * x D(y) ~ (δ * x - γ) * y] -@mtkbuild odesys = ODESystem(eqs, t) +@mtkbuild odesys = System(eqs, t) odeprob = ODEProblem( odesys, [x => 1.0, y => 1.0], (0.0, 10.0), [α => 1.5, β => 1.0, γ => 3.0, δ => 1.0]) tunables, _... = canonicalize(Tunable(), odeprob.p) @@ -273,7 +273,7 @@ VVDual = Vector{<:Vector{<:ForwardDiff.Dual}} end @parameters a b::Int c::Vector{Float64} d[1:2, 1:2]::Int e::Foo{Int} f::Foo - @named sys = ODESystem(Equation[], t, [], [a, b, c, d, e, f]) + @named sys = System(Equation[], t, [], [a, b, c, d, e, f]) sys = complete(sys) ps = MTKParameters(sys, Dict(a => 1.0, b => 2, c => 3ones(2), @@ -311,7 +311,7 @@ end @testset "Error on missing parameter defaults" begin @parameters a b c - @named sys = ODESystem(Equation[], t, [], [a, b]; defaults = Dict(b => 2c)) + @named sys = System(Equation[], t, [], [a, b]; defaults = Dict(b => 2c)) sys = complete(sys) @test_throws ["Could not evaluate", "b", "Missing", "2c"] MTKParameters(sys, [a => 1.0]) end @@ -323,7 +323,7 @@ end D(V[1]) ~ k[1] - k[2] * V[1], D(V[2]) ~ k[3] - k[4] * V[2] ] - @mtkbuild osys_scal = ODESystem(eqs, t, [V[1], V[2]], [k[1], k[2], k[3], k[4]]) + @mtkbuild osys_scal = System(eqs, t, [V[1], V[2]], [k[1], k[2], k[3], k[4]]) u0 = [V => [10.0, 20.0]] ps_vec = [k => [2.0, 3.0, 4.0, 5.0]] diff --git a/test/namespacing.jl b/test/namespacing.jl index b6305a1776..3871398144 100644 --- a/test/namespacing.jl +++ b/test/namespacing.jl @@ -1,10 +1,10 @@ using ModelingToolkit using ModelingToolkit: t_nounits as t, D_nounits as D, iscomplete, does_namespacing -@testset "ODESystem" begin +@testset "System" begin @variables x(t) @parameters p - sys = ODESystem(D(x) ~ p * x, t; name = :inner) + sys = System(D(x) ~ p * x, t; name = :inner) @test !iscomplete(sys) @test does_namespacing(sys) @@ -23,7 +23,7 @@ using ModelingToolkit: t_nounits as t, D_nounits as D, iscomplete, does_namespac @test isequal(p, nsys.p) @test !isequal(p, sys.p) - @test_throws ["namespacing", "inner"] ODESystem( + @test_throws ["namespacing", "inner"] System( Equation[], t; systems = [nsys], name = :a) end diff --git a/test/nonlinearsystem.jl b/test/nonlinearsystem.jl index 158475f7c9..3a22cba409 100644 --- a/test/nonlinearsystem.jl +++ b/test/nonlinearsystem.jl @@ -119,14 +119,14 @@ lorenz2 = lorenz(:lorenz2) using OrdinaryDiffEq @independent_variables t D = Differential(t) -@named subsys = convert_system(ODESystem, lorenz1, t) -@named sys = ODESystem([D(subsys.x) ~ subsys.x + subsys.x], t, systems = [subsys]) +@named subsys = convert_system(System, lorenz1, t) +@named sys = System([D(subsys.x) ~ subsys.x + subsys.x], t, systems = [subsys]) 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) @test sol[subsys.x] + sol[subsys.y] - sol[subsys.z]≈sol[subsys.u] atol=1e-7 -@test_throws ArgumentError convert_system(ODESystem, sys, t) +@test_throws ArgumentError convert_system(System, sys, t) @parameters σ ρ β @variables x y z @@ -197,7 +197,7 @@ eq = [v1 ~ sin(2pi * t * h) v1 - v2 ~ i1 v2 ~ i2 i1 ~ i2] -@named sys = ODESystem(eq, t) +@named sys = System(eq, t) @test length(equations(structural_simplify(sys))) == 0 @testset "Issue: 1504" begin @@ -393,10 +393,10 @@ end @test is_parameter(sys, p) end -@testset "Can convert from `ODESystem`" begin +@testset "Can convert from `System`" begin @variables x(t) y(t) @parameters p q r - @named sys = ODESystem([D(x) ~ p * x^3 + q, 0 ~ -y + q * x - r], t; + @named sys = System([D(x) ~ p * x^3 + q, 0 ~ -y + q * x - r], t; defaults = [x => 1.0, p => missing], guesses = [p => 1.0], initialization_eqs = [p^3 + q^3 ~ 4r], parameter_dependencies = [r ~ 3p]) nlsys = NonlinearSystem(sys) @@ -435,7 +435,7 @@ end @test sol.ps[p^3 + q^3]≈sol.ps[4r] atol=1e-10 @testset "Differential inside expression also substituted" begin - @named sys = ODESystem([0 ~ y * D(x) + x^2 - p, 0 ~ x * D(y) + y * p], t) + @named sys = System([0 ~ y * D(x) + x^2 - p, 0 ~ x * D(y) + y * p], t) nlsys = NonlinearSystem(sys) vs = ModelingToolkit.vars(equations(nlsys)) @test !in(D(x), vs) diff --git a/test/odesystem.jl b/test/odesystem.jl index b707d119fc..75914e40a7 100644 --- a/test/odesystem.jl +++ b/test/odesystem.jl @@ -24,7 +24,7 @@ eqs = [D(x) ~ σ * (y - x), D(z) ~ x * y - β * z * κ] ModelingToolkit.toexpr.(eqs)[1] -@named de = ODESystem(eqs, t; defaults = Dict(x => 1)) +@named de = System(eqs, t; defaults = Dict(x => 1)) subed = substitute(de, [σ => k]) ssort(eqs) = sort(eqs, by = string) @test isequal(ssort(parameters(subed)), [k, β, ρ]) @@ -32,7 +32,7 @@ ssort(eqs) = sort(eqs, by = string) [D(x) ~ k * (y - x) D(y) ~ (ρ - z) * x - y D(z) ~ x * y - β * κ * z]) -@named des[1:3] = ODESystem(eqs, t) +@named des[1:3] = System(eqs, t) @test length(unique(x -> ModelingToolkit.get_tag(x), des)) == 1 @test eval(toexpr(de)) == de @@ -41,7 +41,7 @@ ssort(eqs) = sort(eqs, by = string) generate_function(de) function test_diffeq_inference(name, sys, iv, dvs, ps) - @testset "ODESystem construction: $name" begin + @testset "System construction: $name" begin @test isequal(independent_variables(sys)[1], value(iv)) @test length(independent_variables(sys)) == 1 @test isempty(setdiff(Set(unknowns(sys)), Set(value.(dvs)))) @@ -124,7 +124,7 @@ f = eval(ODEFunctionExpr(de, [x, y, z], [σ, ρ, β], sparsity = false)) eqs = [D(x) ~ σ * (y - x), D(y) ~ x * (ρ - z) - y * t, D(z) ~ x * y - β * z * κ] -@named de = ODESystem(eqs, t) +@named de = System(eqs, t) de = complete(de) ModelingToolkit.calculate_tgrad(de) @@ -141,7 +141,7 @@ tgrad_iip(du, u, p, t) eqs = [D(x) ~ σ(t - 1) * (y - x), D(y) ~ x * (ρ - z) - y, D(z) ~ x * y - β * z * κ] -@named de = ODESystem(eqs, t) +@named de = System(eqs, t) test_diffeq_inference("single internal 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] @@ -149,7 +149,7 @@ f(du, [1.0, 2.0, 3.0], [x -> x + 7, 2, 3], 5.0) @test du ≈ [11, -3, -7] eqs = [D(x) ~ x + 10σ(t - 1) + 100σ(t - 2) + 1000σ(t^2)] -@named de = ODESystem(eqs, t) +@named de = System(eqs, t) test_diffeq_inference("many internal iv-varying", de, t, (x,), (σ,)) f = generate_function(de, [x], [σ], expression = Val{false})[2] du = [0.0] @@ -162,7 +162,7 @@ D2 = D^2 @variables u(t) uˍtt(t) uˍt(t) xˍt(t) eqs = [D3(u) ~ 2(D2(u)) + D(u) + D(x) + 1 D2(x) ~ D(x) + 2] -@named de = ODESystem(eqs, t) +@named de = System(eqs, t) de1 = ode_order_lowering(de) lowered_eqs = [D(uˍtt) ~ 2uˍtt + uˍt + xˍt + 1 D(xˍt) ~ xˍt + 2 @@ -170,13 +170,13 @@ lowered_eqs = [D(uˍtt) ~ 2uˍtt + uˍt + xˍt + 1 D(u) ~ uˍt D(x) ~ xˍt] -#@test de1 == ODESystem(lowered_eqs) +#@test de1 == System(lowered_eqs) # issue #219 @test all(isequal.( [ModelingToolkit.var_from_nested_derivative(eq.lhs)[1] for eq in equations(de1)], - unknowns(@named lowered = ODESystem(lowered_eqs, t)))) + unknowns(@named lowered = System(lowered_eqs, t)))) test_diffeq_inference("first-order transform", de1, t, [uˍtt, xˍt, uˍt, u, x], []) du = zeros(5) @@ -189,7 +189,7 @@ a = y - x eqs = [D(x) ~ σ * a, D(y) ~ x * (ρ - z) - y, D(z) ~ x * y - β * z * κ] -@named de = ODESystem(eqs, t) +@named de = System(eqs, t) generate_function(de, [x, y, z], [σ, ρ, β]) jac = calculate_jacobian(de) @test ModelingToolkit.jacobian_sparsity(de).colptr == sparse(jac).colptr @@ -201,7 +201,7 @@ f = ODEFunction(complete(de), [x, y, z], [σ, ρ, β]) _x = y / C eqs = [D(x) ~ -A * x, D(y) ~ A * x - B * _x] -@named de = ODESystem(eqs, t) +@named de = System(eqs, t) @test begin local f, du f = generate_function(de, [x, y], [A, B, C], expression = Val{false})[2] @@ -242,7 +242,7 @@ ODEFunction(de)(similar(prob.u0), prob.u0, prob.p, 0.1) eqs = [D(y₁) ~ -k₁ * y₁ + k₃ * y₂ * y₃, 0 ~ y₁ + y₂ + y₃ - 1, D(y₂) ~ k₁ * y₁ - k₂ * y₂^2 - k₃ * y₂ * y₃ * κ] -@named sys = ODESystem(eqs, t, defaults = [k₁ => 100, k₂ => 3e7, y₁ => 1.0]) +@named sys = System(eqs, t, defaults = [k₁ => 100, k₂ => 3e7, y₁ => 1.0]) sys = complete(sys) u0 = Pair[] push!(u0, y₂ => 0.0) @@ -289,14 +289,14 @@ sol_dpmap = solve(prob_dpmap, Rodas5()) function makesys(name) @parameters a = 1.0 @variables x(t) = 0.0 - ODESystem([D(x) ~ -a * x], t; name) + System([D(x) ~ -a * x], t; name) end function makecombinedsys() sys1 = makesys(:sys1) sys2 = makesys(:sys2) @parameters b = 1.0 - complete(ODESystem(Equation[], t, [], [b]; systems = [sys1, sys2], name = :foo)) + complete(System(Equation[], t, [], [b]; systems = [sys1, sys2], name = :foo)) end sys = makecombinedsys() @@ -341,7 +341,7 @@ end eqs = [D(x) ~ σ * (y - x), D(y) ~ x - β * y, x + z ~ y] -@named sys = ODESystem(eqs, t) +@named sys = System(eqs, t) @test all(isequal.(unknowns(sys), [x, y, z])) @test all(isequal.(parameters(sys), [σ, β])) @test equations(sys) == eqs @@ -351,13 +351,13 @@ eqs = [D(x) ~ σ * (y - x), using ModelingToolkit @parameters a @variables x(t) -@named sys = ODESystem([D(x) ~ a], t) +@named sys = System([D(x) ~ a], t) @test issym(equations(sys)[1].rhs) # issue 708 @parameters a @variables x(t) y(t) z(t) -@named sys = ODESystem([D(x) ~ y, 0 ~ x + z, 0 ~ x - y], t, [z, y, x], []) +@named sys = System([D(x) ~ y, 0 ~ x + z, 0 ~ x - y], t, [z, y, x], []) asys = add_accumulations(sys) @variables accumulation_x(t) accumulation_y(t) accumulation_z(t) eqs = [0 ~ x + z @@ -386,16 +386,16 @@ eqs = [ D(x1) ~ -x1, 0 ~ x1 - x2 ] -@named sys = ODESystem(eqs, t) +@named sys = System(eqs, t) @test isequal(ModelingToolkit.get_iv(sys), t) @test isequal(unknowns(sys), [x1, x2]) @test isempty(parameters(sys)) -# one equation ODESystem test +# one equation System test @parameters r @variables x(t) eq = D(x) ~ r * x -@named ode = ODESystem(eq, t) +@named ode = System(eq, t) @test equations(ode) == [eq] # issue #808 @testset "Combined system name collisions" begin @@ -403,14 +403,14 @@ eq = D(x) ~ r * x @parameters a @variables x(t) f(t) - ODESystem([D(x) ~ -a * x + f], t; name) + System([D(x) ~ -a * x + f], t; name) end function issue808() sys1 = makesys(:sys1) sys2 = makesys(:sys1) - @test_throws ArgumentError ODESystem([sys2.f ~ sys1.x, D(sys1.f) ~ 0], t, + @test_throws ArgumentError System([sys2.f ~ sys1.x, D(sys1.f) ~ 0], t, systems = [sys1, sys2], name = :foo) end issue808() @@ -422,19 +422,19 @@ vars = @variables((u1,)) eqs = [ D(u1) ~ 1 ] -@test_throws ArgumentError ODESystem(eqs, t, vars, pars, name = :foo) +@test_throws ArgumentError System(eqs, t, vars, pars, name = :foo) #Issue 1063/998 pars = [t] vars = @variables((u1(t),)) -@test_throws ArgumentError ODESystem(eqs, t, vars, pars, name = :foo) +@test_throws ArgumentError System(eqs, t, vars, pars, name = :foo) @parameters w der = Differential(w) eqs = [ der(u1) ~ t ] -@test_throws ArgumentError ModelingToolkit.ODESystem(eqs, t, vars, pars, name = :foo) +@test_throws ArgumentError ModelingToolkit.System(eqs, t, vars, pars, name = :foo) @variables x(t) @parameters M b k @@ -442,7 +442,7 @@ eqs = [D(D(x)) ~ -b / M * D(x) - k / M * x] ps = [M, b, k] default_u0 = [D(x) => 0.0, x => 10.0] default_p = [M => 1.0, b => 1.0, k => 1.0] -@named sys = ODESystem(eqs, t, [x], ps; defaults = [default_u0; default_p], tspan) +@named sys = System(eqs, t, [x], ps; defaults = [default_u0; default_p], tspan) sys = ode_order_lowering(sys) sys = complete(sys) prob = ODEProblem(sys) @@ -455,7 +455,7 @@ prob = ODEProblem{false}(sys; u0_constructor = x -> SVector(x...)) # check_eqs_u0 kwarg test @variables x1(t) x2(t) eqs = [D(x1) ~ -x1] -@named sys = ODESystem(eqs, t, [x1, x2], []) +@named sys = System(eqs, t, [x1, x2], []) sys = complete(sys) @test_throws ArgumentError ODEProblem(sys, [1.0, 1.0], (0.0, 1.0)) @test_nowarn ODEProblem(sys, [1.0, 1.0], (0.0, 1.0), check_length = false) @@ -466,7 +466,7 @@ let @variables x(t) ẋ(t) f(t) [input = true] eqs = [D(x) ~ ẋ, D(ẋ) ~ f - k * x - d * ẋ] - @named sys = ODESystem(eqs, t, [x, ẋ], [d, k]) + @named sys = System(eqs, t, [x, ẋ], [f, d, k]) sys = structural_simplify(sys; inputs = [f]) @test isequal(calculate_control_jacobian(sys), @@ -476,7 +476,7 @@ end # issue 1109 let @variables x(t)[1:3, 1:3] - @named sys = ODESystem(D.(x) .~ x, t) + @named sys = System(D.(x) .~ x, t) @test_nowarn structural_simplify(sys) end @@ -487,7 +487,7 @@ sts = @variables x(t)[1:3]=[1, 2, 3.0] y(t)=1.0 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) +@named sys = System(eqs, t, sts, ps) sys = structural_simplify(sys) @test isequal(@nonamespace(sys.x), x) @test isequal(@nonamespace(sys.y), y) @@ -511,14 +511,14 @@ function submodel(; name) @variables y(t) @parameters A[1:5] A = collect(A) - ODESystem(D(y) ~ sum(A) * y, t; name = name) + System(D(y) ~ sum(A) * y, t; name = name) end # Build system @named sys1 = submodel() @named sys2 = submodel() -@named sys = ODESystem([0 ~ sys1.y + sys2.y], t; systems = [sys1, sys2]) +@named sys = System([0 ~ sys1.y + sys2.y], t; systems = [sys1, sys2]) # DelayDiffEq using ModelingToolkit: hist @@ -526,7 +526,7 @@ using ModelingToolkit: hist xₜ₋₁ = hist(x, t - 1) eqs = [D(x) ~ x * y D(y) ~ y * x - xₜ₋₁] -@named sys = ODESystem(eqs, t) +@named sys = System(eqs, t) # register using StaticArrays @@ -537,8 +537,8 @@ foo(a, ms::AbstractVector) = a + sum(ms) @register_symbolic foo(a, ms::AbstractVector) @variables x(t) ms(t)[1:3] eqs = [D(x) ~ foo(x, ms); D(ms) ~ ones(3)] -@named sys = ODESystem(eqs, t, [x; ms], []) -@named emptysys = ODESystem(Equation[], t) +@named sys = System(eqs, t, [x; ms], []) +@named emptysys = System(Equation[], t) @mtkbuild outersys = compose(emptysys, sys) prob = ODEProblem( outersys, [outersys.sys.x => 1.0; collect(outersys.sys.ms .=> 1:3)], (0, 1.0)) @@ -552,8 +552,8 @@ bar(x, p) = p * x end @parameters p[1:3, 1:3] eqs = [D(x) ~ foo(x, ms); D(ms) ~ bar(ms, p)] -@named sys = ODESystem(eqs, t) -@named emptysys = ODESystem(Equation[], t) +@named sys = System(eqs, t) +@named emptysys = System(Equation[], t) @mtkbuild outersys = compose(emptysys, sys) prob = ODEProblem( outersys, [sys.x => 1.0, sys.ms => 1:3], (0.0, 1.0), [sys.p => ones(3, 3)]) @@ -564,13 +564,13 @@ obsfn = ModelingToolkit.build_explicit_observed_function( # x/x @variables x(t) -@named sys = ODESystem([D(x) ~ x / x], t) +@named sys = System([D(x) ~ x / x], t) @test equations(alias_elimination(sys)) == [D(x) ~ 1] # observed variable handling @variables x(t) RHS(t) @parameters τ -@named fol = ODESystem([D(x) ~ (1 - x) / τ], t; observed = [RHS ~ (1 - x) / τ]) +@named fol = System([D(x) ~ (1 - x) / τ], t; observed = [RHS ~ (1 - x) / τ]) @test isequal(RHS, @nonamespace fol.RHS) RHS2 = RHS @unpack RHS = fol @@ -586,13 +586,13 @@ eqs = [ z ~ α * x - β * y ] -@named sys = ODESystem(eqs, t, [x, y, z], [α, β]) +@named sys = System(eqs, t, [x, y, z], [α, β]) sys = complete(sys) @test_throws Any ODEFunction(sys) eqs = copy(eqs) eqs[end] = D(D(z)) ~ α * x - β * y -@named sys = ODESystem(eqs, t, [x, y, z], [α, β]) +@named sys = System(eqs, t, [x, y, z], [α, β]) sys = complete(sys) @test_throws Any ODEFunction(sys) @@ -644,7 +644,7 @@ sys = complete(sys) D(us[i]) ~ dummy_identity(buffer[i], us[i]) end - @named sys = ODESystem(eqs, t, us, ps; defaults = defs, preface = preface) + @named sys = System(eqs, t, us, ps; defaults = defs, preface = preface) sys = complete(sys) prob = ODEProblem(sys, [], (0.0, 1.0)) sol = solve(prob, Euler(); dt = 0.1) @@ -659,7 +659,7 @@ let eqs = [D(x[1]) ~ x[2] 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)) + @named sys = System(eqs, t, vcat(x, [y]), [k], defaults = Dict(x .=> 0)) sys = structural_simplify(sys) u0 = [0.5, 0] @@ -705,7 +705,7 @@ let @parameters k1 k2::Int @variables A(t) eqs = [D(A) ~ -k1 * k2 * A] - @named sys = ODESystem(eqs, t) + @named sys = System(eqs, t) sys = complete(sys) u0map = [A => 1.0] pmap = (k1 => 1.0, k2 => 1) @@ -741,7 +741,7 @@ let D(p) ~ q / C 0 ~ q / C - R * F] - @named sys = ODESystem(eqs, t) + @named sys = System(eqs, t) @test length(equations(structural_simplify(sys))) == 2 end @@ -774,11 +774,11 @@ let D(z2) ~ y2 - beta * z2 # missing x2 term ] - @named sys1 = ODESystem(eqs, t) - @named sys2 = ODESystem(eqs2, t) - @named sys3 = ODESystem(eqs3, t) + @named sys1 = System(eqs, t) + @named sys2 = System(eqs2, t) + @named sys3 = System(eqs3, t) ssys3 = structural_simplify(sys3) - @named sys4 = ODESystem(eqs4, t) + @named sys4 = System(eqs4, t) @test ModelingToolkit.isisomorphic(sys1, sys2) @test !ModelingToolkit.isisomorphic(sys1, sys3) @@ -787,7 +787,7 @@ let # 1281 iv2 = only(independent_variables(sys2)) - @test isequal(only(independent_variables(convert_system(ODESystem, sys1, iv2))), iv2) + @test isequal(only(independent_variables(convert_system(System, sys1, iv2))), iv2) end let @@ -799,7 +799,7 @@ let sph ~ b spm ~ 0 sph ~ a] - @named sys = ODESystem(eqs, t, vars, pars) + @named sys = System(eqs, t, vars, pars) @test_throws ModelingToolkit.ExtraEquationsSystemException structural_simplify(sys) end @@ -823,7 +823,7 @@ let ps = [] - @named sys = ODESystem(eqs, t, u, ps) + @named sys = System(eqs, t, u, ps) @test_nowarn simpsys = structural_simplify(sys) sys = structural_simplify(sys) @@ -844,7 +844,7 @@ let @parameters k @variables A(t) eqs = [D(A) ~ -k * A] - @named osys = ODESystem(eqs, t) + @named osys = System(eqs, t) osys = complete(osys) oprob = ODEProblem(osys, [A => 1.0], (0.0, 10.0), [k => 1.0]; check_length = false) @test_nowarn sol = solve(oprob, Tsit5()) @@ -854,13 +854,13 @@ let function sys1(; name) vars = @variables x(t)=0.0 dx(t)=0.0 - ODESystem([D(x) ~ dx], t, vars, []; name, defaults = [D(x) => x]) + System([D(x) ~ dx], t, vars, []; name, defaults = [D(x) => x]) end function sys2(; name) @named s1 = sys1() - ODESystem(Equation[], t, [], []; systems = [s1], name) + System(Equation[], t, [], []; systems = [s1], name) end s1′ = sys1(; name = :s1) @@ -889,7 +889,7 @@ let @variables u(t) x(t) v(t) eqs = [u ~ kx * x + kv * v] - ODESystem(eqs, t; name) + System(eqs, t; name) end @named ctrl = pd_ctrl() @@ -900,7 +900,7 @@ let @variables u(t) x(t) v(t) eqs = [D(x) ~ v, D(v) ~ u] - ODESystem(eqs, t; name) + System(eqs, t; name) end @named sys = double_int() @@ -909,7 +909,7 @@ let connections = [sys.u ~ ctrl.u, ctrl.x ~ sys.x, ctrl.v ~ sys.v] - @named connected = ODESystem(connections, t) + @named connected = System(connections, t) @named sys_con = compose(connected, sys, ctrl) sys_simp = structural_simplify(sys_con) @@ -922,7 +922,7 @@ let @variables x(t) = 1 @variables y(t) = 1 @parameters pp = -1 - @named sys4 = ODESystem([D(x) ~ -y; D(y) ~ 1 + pp * y + x], t) + @named sys4 = System([D(x) ~ -y; D(y) ~ 1 + pp * y + x], t) 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)"] @@ -935,7 +935,7 @@ let ∂t = D eqs = [∂t(Q) ~ 0.2P ∂t(P) ~ -80.0sin(Q)] - @test_throws ArgumentError @named sys = ODESystem(eqs, t) + @test_throws ArgumentError @named sys = System(eqs, t) end @parameters C L R @@ -945,12 +945,12 @@ eqs = [D(q) ~ -p / L - F D(p) ~ q / C 0 ~ q / C - R * F] testdict = Dict([:name => "test"]) -@named sys = ODESystem(eqs, t, metadata = testdict) +@named sys = System(eqs, t, metadata = testdict) @test get_metadata(sys) == testdict @variables P(t)=NaN Q(t)=NaN eqs = [D(Q) ~ 1 / sin(P), D(P) ~ log(-cos(Q))] -@named sys = ODESystem(eqs, t, [P, Q], []) +@named sys = System(eqs, t, [P, Q], []) sys = complete(debug_system(sys)) prob = ODEProblem(sys, [], (0.0, 1.0)) @test_throws "log(-cos(Q(t))) errors" prob.f([1, 0], prob.p, 0.0) @@ -961,7 +961,7 @@ let @variables y(t) = 1 @parameters pp = -1 der = Differential(t) - @named sys4 = ODESystem([der(x) ~ -y; der(y) ~ 1 + pp * y + x], t) + @named sys4 = System([der(x) ~ -y; der(y) ~ 1 + pp * y + x], t) sys4s = structural_simplify(sys4) prob = ODEProblem(sys4s, [x => 1.0, D(x) => 1.0], (0, 1.0)) @test !isnothing(prob.f.sys) @@ -969,15 +969,15 @@ end # SYS 1: vars_sub1 = @variables s1(t) -@named sub = ODESystem(Equation[], t, vars_sub1, []) +@named sub = System(Equation[], t, vars_sub1, []) vars1 = @variables x1(t) -@named sys1 = ODESystem(Equation[], t, vars1, [], systems = [sub]) -@named sys2 = ODESystem(Equation[], t, vars1, [], systems = [sys1, sub]) +@named sys1 = System(Equation[], t, vars1, [], systems = [sub]) +@named sys2 = System(Equation[], t, vars1, [], systems = [sys1, sub]) # SYS 2: Extension to SYS 1 vars_sub2 = @variables s2(t) -@named partial_sub = ODESystem(Equation[], t, vars_sub2, []) +@named partial_sub = System(Equation[], t, vars_sub2, []) @named sub = extend(partial_sub, sub) # no warnings for systems without events @@ -996,7 +996,7 @@ let # Issue https://github.com/SciML/ModelingToolkit.jl/issues/2322 eqs = [Dt(x) ~ -b * (x - z), 0 ~ z - c * x] - sys = ODESystem(eqs, t; name = :kjshdf) + sys = System(eqs, t; name = :kjshdf) sys_simp = structural_simplify(sys) @@ -1011,7 +1011,7 @@ end # Issue#2599 @variables x(t) y(t) eqs = [D(x) ~ x * t, y ~ 2x] -@mtkbuild sys = ODESystem(eqs, t; continuous_events = [[y ~ 3] => [x ~ 2]]) +@mtkbuild sys = System(eqs, t; continuous_events = [[y ~ 3] => [x ~ 2]]) prob = ODEProblem(sys, [x => 1.0], (0.0, 10.0)) @test_nowarn solve(prob, Tsit5()) @@ -1022,14 +1022,14 @@ prob = ODEProblem(sys, [x => 1.0], (0.0, 10.0)) eqs = [ D(x) ~ p * x ] - @mtkbuild sys = ODESystem( + @mtkbuild sys = System( 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( + @mtkbuild sys = System( 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)]) @@ -1042,7 +1042,7 @@ end @test_skip begin @variables x(t)[1:3] y(t) @parameters p[1:3, 1:3] - @test_nowarn @mtkbuild sys = ODESystem([D(x) ~ p * x, D(y) ~ x' * p * x], t) + @test_nowarn @mtkbuild sys = System([D(x) ~ p * x, D(y) ~ x' * p * x], t) @test_nowarn ODEProblem(sys, [x => ones(3), y => 2], (0.0, 10.0), [p => ones(3, 3)]) end @@ -1054,7 +1054,7 @@ eqs = [D(D(q₁)) ~ -λ * q₁, q₁ ~ L * sin(θ), q₂ ~ L * cos(θ)] -@named pend = ODESystem(eqs, t) +@named pend = System(eqs, t) @test_nowarn generate_initializesystem( pend, u0map = [q₁ => 1.0, q₂ => 0.0], guesses = [λ => 1]) @@ -1066,7 +1066,7 @@ eqs = [D(D(x)) ~ σ * (y - x), D(y) ~ x * (ρ - z) - y, D(z) ~ x * y - β * z] -@mtkbuild sys = ODESystem(eqs, t) +@mtkbuild sys = System(eqs, t) u0 = [D(x) => 2.0, x => 1.0, @@ -1098,7 +1098,7 @@ function FML2(; name) eqs = [ D(x) ~ constant.output.u + k2[1] ] - ODESystem(eqs, t; systems, name) + System(eqs, t; systems, name) end @mtkbuild model = FML2() @@ -1114,7 +1114,7 @@ function RealExpression(; name, y) eqns = [ u ~ y ] - sys = ODESystem(eqns, t, vars, []; name) + sys = System(eqns, t, vars, []; name) end function RealExpressionSystem(; name) @@ -1125,7 +1125,7 @@ function RealExpressionSystem(; name) @named e1 = RealExpression(y = x) # This works perfectly. @named e2 = RealExpression(y = z[1]) # This bugs. However, `full_equations(e2)` works as expected. systems = [e1, e2] - ODESystem(Equation[], t, Iterators.flatten(vars), []; systems, name) + System(Equation[], t, Iterators.flatten(vars), []; systems, name) end @named sys = RealExpressionSystem() @@ -1138,8 +1138,8 @@ orig_vars = unknowns(sys) # Guesses in hierarchical systems @variables x(t) y(t) -@named sys = ODESystem(Equation[], t, [x], []; guesses = [x => 1.0]) -@named outer = ODESystem( +@named sys = System(Equation[], t, [x], []; guesses = [x => 1.0]) +@named outer = System( [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) @@ -1150,9 +1150,9 @@ int = init(prob, Rodas4()) # Ensure indexes of array symbolics are cached appropriately @variables x(t)[1:2] -@named sys = ODESystem(Equation[], t, [x], []) +@named sys = System(Equation[], t, [x], []) sys1 = complete(sys) -@named sys = ODESystem(Equation[], t, [x...], []) +@named sys = System(Equation[], t, [x...], []) sys2 = complete(sys) for sys in [sys1, sys2] for (sym, idx) in [(x, 1:2), (x[1], 1), (x[2], 2)] @@ -1162,9 +1162,9 @@ for sys in [sys1, sys2] end @variables x(t)[1:2, 1:2] -@named sys = ODESystem(Equation[], t, [x], []) +@named sys = System(Equation[], t, [x], []) sys1 = complete(sys) -@named sys = ODESystem(Equation[], t, [x...], []) +@named sys = System(Equation[], t, [x...], []) sys2 = complete(sys) for sys in [sys1, sys2] @test is_variable(sys, x) @@ -1177,7 +1177,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], []) + @named sys = System([x[0] ~ 0.0, D(x[1]) ~ x[0]], t, [x], []) @test_nowarn sys = structural_simplify(sys) @test equations(sys) == [D(x[1]) ~ 0.0] end @@ -1185,7 +1185,7 @@ end # Namespacing of array variables @testset "Namespacing of array variables" begin @variables x(t)[1:2] - @named sys = ODESystem(Equation[], t) + @named sys = System(Equation[], t) @test getname(unknowns(sys, x)) == :sys₊x @test size(unknowns(sys, x)) == size(x) end @@ -1194,7 +1194,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 = structural_simplify(System([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]) @@ -1207,7 +1207,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 = structural_simplify(System([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]) @@ -1220,7 +1220,7 @@ end @testset "Custom independent variable" begin @independent_variables x @variables y(x) - @test_nowarn @named sys = ODESystem([y ~ 0], x) + @test_nowarn @named sys = System([y ~ 0], x) # the same, but with @mtkmodel @independent_variables x @@ -1235,7 +1235,7 @@ end @test_nowarn @mtkbuild sys = MyModel() @variables x y(x) - @test_logs (:warn,) @named sys = ODESystem([y ~ 0], x) + @test_logs (:warn,) @named sys = System([y ~ 0], x) @parameters T D = Differential(T) @@ -1243,7 +1243,7 @@ end eqs = [D(x) ~ 0.0] initialization_eqs = [x ~ T] guesses = [x => 0.0] - @named sys2 = ODESystem(eqs, T; initialization_eqs, guesses) + @named sys2 = System(eqs, T; initialization_eqs, guesses) prob2 = ODEProblem(structural_simplify(sys2), [], (1.0, 2.0), []) sol2 = solve(prob2) @test all(sol2[x] .== 1.0) @@ -1253,10 +1253,10 @@ end @testset "Extend systems with a field that can be nothing" begin A = Dict(:a => 1) B = Dict(:b => 2) - @named A1 = ODESystem(Equation[], t, [], []) - @named B1 = ODESystem(Equation[], t, [], []) - @named A2 = ODESystem(Equation[], t, [], []; metadata = A) - @named B2 = ODESystem(Equation[], t, [], []; metadata = B) + @named A1 = System(Equation[], t, [], []) + @named B1 = System(Equation[], t, [], []) + @named A2 = System(Equation[], t, [], []; metadata = A) + @named B2 = System(Equation[], t, [], []; metadata = B) @test ModelingToolkit.get_metadata(extend(A1, B1)) == nothing @test ModelingToolkit.get_metadata(extend(A1, B2)) == B @test ModelingToolkit.get_metadata(extend(A2, B1)) == A @@ -1268,7 +1268,7 @@ end @variables x(t) y(t) z(t) eqs = [D(x) ~ 0, y ~ x, D(z) ~ 0] defaults = [x => 1, z => y] - @named sys = ODESystem(eqs, t; defaults) + @named sys = System(eqs, t; defaults) ssys = structural_simplify(sys) prob = ODEProblem(ssys, [], (0.0, 1.0), []) @test prob[x] == prob[y] == prob[z] == 1.0 @@ -1277,7 +1277,7 @@ end @variables x(t) y(t) z(t) eqs = [D(x) ~ 0, y ~ y0 / x, D(z) ~ y] defaults = [y0 => 1, x => 1, z => y] - @named sys = ODESystem(eqs, t; defaults) + @named sys = System(eqs, t; defaults) ssys = structural_simplify(sys) prob = ODEProblem(ssys, [], (0.0, 1.0), []) @test prob[x] == prob[y] == prob[z] == 1.0 @@ -1286,7 +1286,7 @@ end @testset "Scalarized parameters in array functions" begin @variables u(t)[1:2] x(t)[1:2] o(t)[1:2] @parameters p[1:2, 1:2] [tunable = false] - @named sys = ODESystem( + @named sys = System( [D(u) ~ (sum(u) + sum(x) + sum(p) + sum(o)) * x, o ~ prod(u) * x], t, [u..., x..., o...], [p...]) sys1 = structural_simplify(sys, inputs = [x...], outputs = []) @@ -1347,7 +1347,7 @@ end @testset "Independent variable as system property" begin @variables x(t) - @named sys = ODESystem([x ~ t], t) + @named sys = System([x ~ t], t) @named sys = compose(sys, sys) # nest into a hierarchical system @test t === sys.t === sys.sys.t end @@ -1355,7 +1355,7 @@ end @testset "Substituting preserves parameter dependencies, defaults, guesses" begin @parameters p1 p2 @variables x(t) y(t) - @named sys = ODESystem([D(x) ~ y + p2], t; parameter_dependencies = [p2 ~ 2p1], + @named sys = System([D(x) ~ y + p2], t; parameter_dependencies = [p2 ~ 2p1], defaults = [p1 => 1.0, p2 => 2.0], guesses = [p1 => 2.0, p2 => 3.0]) @parameters p3 sys2 = substitute(sys, [p1 => p3]) @@ -1371,10 +1371,10 @@ end @testset "Substituting with nested systems" begin @parameters p1 p2 @variables x(t) y(t) - @named innersys = ODESystem([D(x) ~ y + p2], t; parameter_dependencies = [p2 ~ 2p1], + @named innersys = System([D(x) ~ y + p2], t; parameter_dependencies = [p2 ~ 2p1], defaults = [p1 => 1.0, p2 => 2.0], guesses = [p1 => 2.0, p2 => 3.0]) @parameters p3 p4 - @named outersys = ODESystem( + @named outersys = System( [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) @@ -1401,7 +1401,7 @@ end o[1] ~ sum(p) * sum(u) o[2] ~ sum(p) * sum(x)] - @named sys = ODESystem(eqs, t, [u..., x..., o], [p...]) + @named sys = System(eqs, t, [u..., x..., o], [p...]) sys1 = structural_simplify(sys, inputs = [x...], outputs = [o...], split = false) @test_nowarn ModelingToolkit.build_explicit_observed_function(sys1, u; inputs = [x...]) @@ -1414,17 +1414,17 @@ end @testset "Passing `nothing` to `u0`" begin @variables x(t) = 1 - @mtkbuild sys = ODESystem(D(x) ~ t, t) + @mtkbuild sys = System(D(x) ~ t, t) prob = @test_nowarn ODEProblem(sys, nothing, (0.0, 1.0)) @test_nowarn solve(prob) end @testset "ODEs are not DDEs" begin @variables x(t) - @named sys = ODESystem(D(x) ~ x, t) + @named sys = System(D(x) ~ x, t) @test !ModelingToolkit.is_dde(sys) @test is_markovian(sys) - @named sys2 = ODESystem(Equation[], t; systems = [sys]) + @named sys2 = System(Equation[], t; systems = [sys]) @test !ModelingToolkit.is_dde(sys) @test is_markovian(sys) end @@ -1434,7 +1434,7 @@ end for eqs in [D(x) ~ x, collect(D(x) .~ x)] for dvs in [[x], collect(x)] - @named sys = ODESystem(eqs, t, dvs, []) + @named sys = System(eqs, t, dvs, []) sys = complete(sys) if eqs isa Vector && length(eqs) == 2 && length(dvs) == 2 @test_nowarn ODEProblem(sys, [], (0.0, 1.0)) @@ -1447,7 +1447,7 @@ end end for eqs in [[D(x) ~ x, D(y) ~ y], [collect(D(x) .~ x); D(y) ~ y]] for dvs in [[x, y], [x..., y]] - @named sys = ODESystem(eqs, t, dvs, []) + @named sys = System(eqs, t, dvs, []) sys = complete(sys) if eqs isa Vector && length(eqs) == 3 && length(dvs) == 3 @test_nowarn ODEProblem(sys, [], (0.0, 1.0)) @@ -1462,13 +1462,13 @@ end @testset "Parameter dependencies with constant RHS" begin @parameters p - @test_nowarn ODESystem(Equation[], t; parameter_dependencies = [p ~ 1.0], name = :a) + @test_nowarn System(Equation[], t; parameter_dependencies = [p ~ 1.0], name = :a) end @testset "Variable discovery in arrays of `Num` inside callable symbolic" begin @variables x(t) y(t) @parameters foo(::AbstractVector) - sys = @test_nowarn ODESystem(D(x) ~ foo([x, 2y]), t; name = :sys) + sys = @test_nowarn System(D(x) ~ foo([x, 2y]), t; name = :sys) @test length(unknowns(sys)) == 2 @test any(isequal(y), unknowns(sys)) end @@ -1476,7 +1476,7 @@ end @testset "Inplace observed" begin @variables x(t) @parameters p[1:2] q - @mtkbuild sys = ODESystem(D(x) ~ sum(p) * x + q * t, t) + @mtkbuild sys = System(D(x) ~ sum(p) * x + q * t, t) prob = ODEProblem(sys, [x => 1.0], (0.0, 1.0), [p => ones(2), q => 2]) obsfn = ModelingToolkit.build_explicit_observed_function( sys, [p..., q], return_inplace = true)[2] @@ -1516,7 +1516,7 @@ end @testset "`complete` with `split = false` removes the index cache" begin @variables x(t) @parameters p - @mtkbuild sys = ODESystem(D(x) ~ p * t, t) + @mtkbuild sys = System(D(x) ~ p * t, t) @test ModelingToolkit.get_index_cache(sys) !== nothing sys2 = complete(sys; split = false) @test ModelingToolkit.get_index_cache(sys2) === nothing @@ -1526,7 +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], + @mtkbuild sys = System([D(x) ~ c * cos(x), obs ~ c], t, [x], [c]; @@ -1540,7 +1540,7 @@ end @testset "DAEProblem with array parameters" begin @variables x(t)=1.0 y(t) [guess = 1.0] @parameters p[1:2] = [1.0, 2.0] - @mtkbuild sys = ODESystem([D(x) ~ x, y^2 ~ x + sum(p)], t) + @mtkbuild sys = System([D(x) ~ x, y^2 ~ x + sum(p)], t) prob = DAEProblem(sys, [D(x) => x, D(y) => D(x) / 2y], [], (0.0, 1.0)) sol = solve(prob, DFBDF(), abstol = 1e-8, reltol = 1e-8) @test sol[x]≈sol[y^2 - sum(p)] atol=1e-5 @@ -1549,7 +1549,7 @@ end @testset "Symbolic tstops" begin @variables x(t) = 1.0 @parameters p=0.15 q=0.25 r[1:2]=[0.35, 0.45] - @mtkbuild sys = ODESystem( + @mtkbuild sys = System( [D(x) ~ p * x + q * t + sum(r)], t; tstops = [0.5p, [0.1, 0.2], [p + 2q], r]) prob = ODEProblem(sys, [], (0.0, 5.0)) sol = solve(prob) @@ -1561,7 +1561,7 @@ end @test all(x -> any(isapprox(x, atol = 1e-6), sol2.t), expected_tstops) @variables y(t) [guess = 1.0] - @mtkbuild sys = ODESystem([D(x) ~ p * x + q * t + sum(r), y^3 ~ 2x + 1], + @mtkbuild sys = System([D(x) ~ p * x + q * t + sum(r), y^3 ~ 2x + 1], t; tstops = [0.5p, [0.1, 0.2], [p + 2q], r]) prob = DAEProblem( sys, [D(y) => 2D(x) / 3y^2, D(x) => p * x + q * t + sum(r)], [], (0.0, 5.0)) @@ -1578,14 +1578,14 @@ end @parameters p d @variables X(t)::Int64 eq = D(X) ~ p - d * X - @test_throws ArgumentError @mtkbuild osys = ODESystem([eq], t) + @test_throws ArgumentError @mtkbuild osys = System([eq], t) @variables Y(t)[1:3]::String eq = D(Y) ~ [p, p, p] - @test_throws ArgumentError @mtkbuild osys = ODESystem([eq], t) + @test_throws ArgumentError @mtkbuild osys = System([eq], t) @variables X(t)::Complex eq = D(X) ~ p - d * X - @test_nowarn @named osys = ODESystem([eq], t) + @test_nowarn @named osys = System([eq], t) end # Test `isequal` @@ -1594,31 +1594,31 @@ end @parameters p d(t) eq = D(X) ~ p - d * X - osys1 = complete(ODESystem([eq], t; name = :osys)) - osys2 = complete(ODESystem([eq], t; name = :osys)) + osys1 = complete(System([eq], t; name = :osys)) + osys2 = complete(System([eq], t; name = :osys)) @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])] - osys1 = complete(ODESystem([eq], t; name = :osys, continuous_events)) - osys2 = complete(ODESystem([eq], t; name = :osys)) + osys1 = complete(System([eq], t; name = :osys, continuous_events)) + osys2 = complete(System([eq], t; name = :osys)) @test osys1 !== osys2 - osys1 = complete(ODESystem([eq], t; name = :osys, discrete_events)) - osys2 = complete(ODESystem([eq], t; name = :osys)) + osys1 = complete(System([eq], t; name = :osys, discrete_events)) + osys2 = complete(System([eq], t; name = :osys)) @test osys1 !== osys2 - osys1 = complete(ODESystem([eq], t; name = :osys, continuous_events)) - osys2 = complete(ODESystem([eq], t; name = :osys, discrete_events)) + osys1 = complete(System([eq], t; name = :osys, continuous_events)) + osys2 = complete(System([eq], t; name = :osys, discrete_events)) @test osys1 !== osys2 end @testset "dae_order_lowering basic test" begin @parameters a @variables x(t) y(t) z(t) - @named dae_sys = ODESystem([ + @named dae_sys = System([ D(x) ~ y, 0 ~ x + z, 0 ~ x - y + z @@ -1654,7 +1654,7 @@ end D(x) => 0.0, x => 10.0, y => 0.0, z => 10.0 ] 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]) + @named dae_sys = System(eqs, t, [x, y, z], ps; defaults = [default_u0; default_p]) simplified_dae_sys = structural_simplify(dae_sys) @@ -1679,32 +1679,32 @@ end cons = [x(0.3) ~ c * d, y(0.7) ~ 3] # Test variables + parameters infer correctly. - @mtkbuild sys = ODESystem(eqs, t; constraints = cons) + @mtkbuild sys = System(eqs, t; constraints = cons) @test issetequal(parameters(sys), [a, c, d, e]) @test issetequal(unknowns(sys), [x(t), y(t), z(t)]) @parameters t_c cons = [x(t_c) ~ 3] - @mtkbuild sys = ODESystem(eqs, t; constraints = cons) + @mtkbuild sys = System(eqs, t; constraints = cons) @test issetequal(parameters(sys), [a, e, t_c]) @parameters g(..) h i cons = [g(h, i) * x(3) ~ c] - @mtkbuild sys = ODESystem(eqs, t; constraints = cons) + @mtkbuild sys = System(eqs, t; constraints = cons) @test issetequal(parameters(sys), [g, h, i, a, e, c]) # Test that bad constraints throw errors. cons = [x(3, 4) ~ 3] # unknowns cannot have multiple args. - @test_throws ArgumentError @mtkbuild sys = ODESystem(eqs, t; constraints = cons) + @test_throws ArgumentError @mtkbuild sys = System(eqs, t; constraints = cons) cons = [x(y(t)) ~ 2] # unknown arg must be parameter, value, or t - @test_throws ArgumentError @mtkbuild sys = ODESystem(eqs, t; constraints = cons) + @test_throws ArgumentError @mtkbuild sys = System(eqs, t; constraints = cons) @variables u(t) v cons = [x(t) * u ~ 3] - @test_throws ArgumentError @mtkbuild sys = ODESystem(eqs, t; constraints = cons) + @test_throws ArgumentError @mtkbuild sys = System(eqs, t; constraints = cons) cons = [x(t) * v ~ 3] - @test_throws ArgumentError @mtkbuild sys = ODESystem(eqs, t; constraints = cons) # Need time argument. + @test_throws ArgumentError @mtkbuild sys = System(eqs, t; constraints = cons) # Need time argument. # Test array variables @variables x(..)[1:5] @@ -1715,13 +1715,13 @@ end 0 0 2 0 5] eqs = D(x(t)) ~ mat * x(t) cons = [x(3) ~ [2, 3, 3, 5, 4]] - @mtkbuild ode = ODESystem(D(x(t)) ~ mat * x(t), t; constraints = cons) + @mtkbuild ode = System(D(x(t)) ~ mat * x(t), t; constraints = cons) @test length(constraints(ModelingToolkit.get_constraintsystem(ode))) == 5 end @testset "`build_explicit_observed_function` with `expression = true` returns `Expr`" begin @variables x(t) - @mtkbuild sys = ODESystem(D(x) ~ 2x, t) + @mtkbuild sys = System(D(x) ~ 2x, t) obsfn_expr = ModelingToolkit.build_explicit_observed_function( sys, 2x + 1, expression = true) @test obsfn_expr isa Expr @@ -1739,7 +1739,7 @@ end D(y) ~ x * (ρ - z) - y, D(z) ~ x * y - β * z] - @mtkbuild sys=ODESystem(eqs, t) split=false + @mtkbuild sys=System(eqs, t) split=false u0 = SA[D(x) => 2.0f0, x => 1.0f0, @@ -1763,17 +1763,17 @@ end @test scope isa ParentScope @test scope.parent isa ParentScope @test scope.parent.parent isa LocalScope - return ODESystem(D(x) ~ var1, t; name) + return System(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) + return System(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) + return System(D(x) ~ x, t; systems = [subsys], name) end @mtkbuild sys = SysC() @test length(unknowns(sys)) == 3 diff --git a/test/parameter_dependencies.jl b/test/parameter_dependencies.jl index bdaa2a391b..6f4f56c140 100644 --- a/test/parameter_dependencies.jl +++ b/test/parameter_dependencies.jl @@ -20,7 +20,7 @@ using NonlinearSolve cb2 = [x ~ 4.0] => (affect1!, [], [p1, p2], [p1]) # triggers at t=-2+√7 cb3 = SymbolicDiscreteCallback([1.0] => [p1 ~ 5.0], discrete_parameters = [p1]) - @mtkbuild sys = ODESystem( + @mtkbuild sys = System( [D(x) ~ p1 * t + p2], t; parameter_dependencies = [p2 => 2p1], @@ -54,7 +54,7 @@ end @parameters p1[1:2]=[1.0, 2.0] p2[1:2]=[0.0, 0.0] @variables x(t) = 0 - @named sys = ODESystem( + @named sys = System( [D(x) ~ sum(p1) * t + sum(p2)], t; parameter_dependencies = [p2 => 2p1] @@ -73,11 +73,11 @@ end @parameters p1=1.0 p2=1.0 @variables x(t) = 0 - @mtkbuild sys1 = ODESystem( + @mtkbuild sys1 = System( [D(x) ~ p1 * t + p2], t ) - @named sys2 = ODESystem( + @named sys2 = System( [], t; parameter_dependencies = [p2 => 2p1] @@ -94,7 +94,7 @@ end @parameters p1=1.0 p2=1.0 @variables x(t) = 0 - @named sys = ODESystem( + @named sys = System( [D(x) ~ p1 * t + p2], t; parameter_dependencies = [p2 => 2p1] @@ -108,7 +108,7 @@ end @parameters p1[1:2]=[1.0, 2.0] p2[1:2]=[0.0, 0.0] @variables x(t) = 0 - @named sys = ODESystem( + @named sys = System( [D(x) ~ sum(p1) * t + sum(p2)], t; parameter_dependencies = [p2 => 2p1] @@ -122,16 +122,16 @@ end @parameters p1=1.0 p2=2.0 @variables x(t) = 0 - @named sys1 = ODESystem( + @named sys1 = System( [D(x) ~ p1 * t + p2], t ) - @named sys2 = ODESystem( + @named sys2 = System( [D(x) ~ p1 * t - p2], t; parameter_dependencies = [p2 => 2p1] ) - sys = complete(ODESystem([], t, systems = [sys1, sys2], name = :sys)) + sys = complete(System([], t, systems = [sys1, sys2], name = :sys)) prob = ODEProblem(sys) v1 = sys.sys2.p2 @@ -159,12 +159,12 @@ end @parameters p2 @variables x(t) = 1.0 eqs = [D(x) ~ p2] - ODESystem(eqs, t, [x], [p2]; name) + System(eqs, t, [x], [p2]; name) end @parameters p1 = 1.0 parameter_dependencies = [sys2.p2 ~ p1 * 2.0] - sys1 = ODESystem( + sys1 = System( Equation[], t, [], [p1]; parameter_dependencies, name = :sys1, systems = [sys2]) # ensure that parameter_dependencies is type stable @@ -191,7 +191,7 @@ end @parameters p=2 (i::CallableFoo)(..) eqs = [D(y) ~ i(t) + p] - @named model = ODESystem(eqs, t, [y], [p, i]; + @named model = System(eqs, t, [y], [p, i]; parameter_dependencies = [i ~ CallableFoo(p)]) sys = structural_simplify(model) @@ -215,7 +215,7 @@ end D(x) ~ -x + u y ~ x z(k) ~ z(k - 2) + yd(k - 2)] - @test_throws ModelingToolkit.HybridSystemNotSupportedException @mtkbuild sys = ODESystem( + @test_throws ModelingToolkit.HybridSystemNotSupportedException @mtkbuild sys = System( eqs, t; parameter_dependencies = [kq => 2kp]) @test_skip begin @@ -225,7 +225,7 @@ end yd(k - 2) => 2.0]) @test_nowarn solve(prob, Tsit5()) - @mtkbuild sys = ODESystem(eqs, t; parameter_dependencies = [kq => 2kp], + @mtkbuild sys = System(eqs, t; parameter_dependencies = [kq => 2kp], discrete_events = [SymbolicDiscreteCallback( [0.5] => [kp ~ 2.0], discrete_parameters = [kp])]) prob = ODEProblem(sys, [x => 0.0, y => 0.0], (0.0, Tf), @@ -258,7 +258,7 @@ end 0.1 * y, 0.1 * z] - @named sys = ODESystem(eqs, t) + @named sys = System(eqs, t) @named sdesys = SDESystem(sys, noiseeqs; parameter_dependencies = [ρ => 2σ]) sdesys = complete(sdesys) @test !(ρ in Set(parameters(sdesys))) @@ -269,7 +269,7 @@ end @test prob.ps[ρ] == 2prob.ps[σ] @test_nowarn solve(prob, SRIW1()) - @named sys = ODESystem(eqs, t) + @named sys = System(eqs, t) @named sdesys = SDESystem(sys, noiseeqs; parameter_dependencies = [ρ => 2σ], discrete_events = [SymbolicDiscreteCallback( [10.0] => [σ ~ 15.0], discrete_parameters = [σ])]) @@ -350,7 +350,7 @@ end cb2 = [x ~ 4.0] => (affect1!, [], [p1, p2], [p1]) # triggers at t=-2+√7 cb3 = [1.0] => [p1 ~ 5.0] - @mtkbuild sys = ODESystem( + @mtkbuild sys = System( [D(x) ~ p1 * t + p2], t; parameter_dependencies = [p2 => 2p1] @@ -381,7 +381,7 @@ end @testset "Discovery of parameters from dependencies" begin @parameters p1 p2 @variables x(t) y(t) - @named sys = ODESystem([D(x) ~ y + p2], t; parameter_dependencies = [p2 ~ 2p1]) + @named sys = System([D(x) ~ y + p2], t; parameter_dependencies = [p2 ~ 2p1]) @test is_parameter(sys, p1) @named sys = NonlinearSystem([x * y^2 ~ y + p2]; parameter_dependencies = [p2 ~ 2p1]) @test is_parameter(sys, p1) diff --git a/test/precompile_test/ODEPrecompileTest.jl b/test/precompile_test/ODEPrecompileTest.jl index 81187c4075..911f45152e 100644 --- a/test/precompile_test/ODEPrecompileTest.jl +++ b/test/precompile_test/ODEPrecompileTest.jl @@ -13,7 +13,7 @@ function system(; kwargs...) D(y) ~ x * (ρ - z) - y, D(z) ~ x * y - β * z] - @named de = ODESystem(eqs, t) + @named de = System(eqs, t) de = complete(de) return ODEFunction(de, [x, y, z], [σ, ρ, β]; kwargs...) end diff --git a/test/problem_validation.jl b/test/problem_validation.jl index a0a7afaf3c..3db151bda5 100644 --- a/test/problem_validation.jl +++ b/test/problem_validation.jl @@ -6,7 +6,7 @@ using ModelingToolkit: t_nounits as t, D_nounits as D @variables X(t) @parameters p d eqs = [D(X) ~ p - d * X] - @mtkbuild osys = ODESystem(eqs, t) + @mtkbuild osys = System(eqs, t) p = "I accidentally renamed p" u0 = [X => 1.0] @@ -23,7 +23,7 @@ using ModelingToolkit: t_nounits as t, D_nounits as D @variables x(t) y(t) z(t) @parameters a b c d eqs = [D(x) ~ x * a, D(y) ~ y * c, D(z) ~ b + d] - @mtkbuild sys = ODESystem(eqs, t) + @mtkbuild sys = System(eqs, t) pmap = [a => 1, b => 2, c => 3, d => 4, "b" => 2] u0map = [x => 1, y => 2, z => 3] @test_throws InvalidKeyError ODEProblem(sys, u0map, (0.0, 1.0), pmap) diff --git a/test/reduction.jl b/test/reduction.jl index a692dbff75..7e988a2d55 100644 --- a/test/reduction.jl +++ b/test/reduction.jl @@ -28,7 +28,7 @@ eqs = [D(x) ~ σ * (y - x) 0 ~ a + z u ~ z + a] -lorenz1 = ODESystem(eqs, t, name = :lorenz1) +lorenz1 = System(eqs, t, name = :lorenz1) lorenz1_aliased = structural_simplify(lorenz1) io = IOBuffer(); @@ -53,13 +53,13 @@ eqs1 = [ u ~ x + y - z ] -lorenz = name -> ODESystem(eqs1, t, name = name) +lorenz = name -> System(eqs1, t, name = name) lorenz1 = lorenz(:lorenz1) state = TearingState(lorenz1) @test isempty(setdiff(state.fullvars, [D(x), F, y, x, D(y), u, z, D(z)])) lorenz2 = lorenz(:lorenz2) -@named connected = ODESystem( +@named connected = System( [s ~ a + lorenz1.x lorenz2.y ~ s lorenz1.u ~ lorenz2.F @@ -125,13 +125,13 @@ prob2 = SteadyStateProblem(reduced_system, u0, pp) let @variables x(t) u(t) y(t) @parameters a b c d - ol = ODESystem([D(x) ~ a * x + b * u; y ~ c * x + d * u], t, name = :ol) + ol = System([D(x) ~ a * x + b * u; y ~ c * x + d * u], t, name = :ol) @variables u_c(t) y_c(t) @parameters k_P - pc = ODESystem(Equation[u_c ~ k_P * y_c], t, name = :pc) + pc = System(Equation[u_c ~ k_P * y_c], t, name = :pc) connections = [pc.u_c ~ ol.u pc.y_c ~ ol.y] - @named connected = ODESystem(connections, t, systems = [ol, pc]) + @named connected = System(connections, t, systems = [ol, pc]) @test equations(connected) isa Vector{Equation} reduced_sys = structural_simplify(connected) ref_eqs = [D(ol.x) ~ ol.a * ol.x + ol.b * ol.u @@ -142,7 +142,7 @@ end # issue #889 let @variables x(t) - @named sys = ODESystem([0 ~ D(x) + x], t, [x], []) + @named sys = System([0 ~ D(x) + x], t, [x], []) #@test_throws ModelingToolkit.InvalidSystemException ODEProblem(sys, [1.0], (0, 10.0)) sys = structural_simplify(sys) #@test_nowarn ODEProblem(sys, [1.0], (0, 10.0)) @@ -188,7 +188,7 @@ eqs = [D(E) ~ k₋₁ * C - k₁ * E * S D(P) ~ k₂ * C E₀ ~ E + C] -@named sys = ODESystem(eqs, t, [E, C, S, P], [k₁, k₂, k₋₁, E₀]) +@named sys = System(eqs, t, [E, C, S, P], [k₁, k₂, k₋₁, E₀]) @test_throws ModelingToolkit.ExtraEquationsSystemException structural_simplify(sys) # Example 5 from Pantelides' original paper @@ -197,7 +197,7 @@ sts = collect(@variables x(t) u1(t) u2(t)) eqs = [0 ~ x + sin(u1 + u2) D(x) ~ x + y1 cos(x) ~ sin(y2)] -@named sys = ODESystem(eqs, t, sts, params) +@named sys = System(eqs, t, sts, params) @test_throws ModelingToolkit.InvalidSystemException structural_simplify(sys) # issue #963 @@ -214,7 +214,7 @@ eq = [v47 ~ v1 0 ~ i74 + i75 - i64 0 ~ i64 + i71] -@named sys0 = ODESystem(eq, t) +@named sys0 = System(eq, t) sys = structural_simplify(sys0) @test length(equations(sys)) == 1 eq = equations(tearing_substitution(sys))[1] @@ -232,7 +232,7 @@ eqs = [D(x) ~ σ * (y - x) 0 ~ a + z u ~ z + a] -lorenz1 = ODESystem(eqs, t, name = :lorenz1) +lorenz1 = System(eqs, t, name = :lorenz1) lorenz1_reduced = structural_simplify(lorenz1, inputs = [z], outputs = []) @test z in Set(parameters(lorenz1_reduced)) @@ -241,7 +241,7 @@ vars = @variables x(t) y(t) z(t) eqs = [D(x) ~ x D(y) ~ y D(z) ~ t] -@named model = ODESystem(eqs, t) +@named model = System(eqs, t) sys = structural_simplify(model) Js = ModelingToolkit.jacobian_sparsity(sys) @test size(Js) == (3, 3) @@ -252,29 +252,29 @@ vars = @variables a(t) w(t) phi(t) eqs = [a ~ D(w) w ~ D(phi) w ~ sin(t)] -@named sys = ODESystem(eqs, t, vars, []) +@named sys = System(eqs, t, vars, []) ss = alias_elimination(sys) @test isempty(observed(ss)) @variables x(t) y(t) -@named sys = ODESystem([D(x) ~ 1 - x, +@named sys = System([D(x) ~ 1 - x, D(y) + D(x) ~ 0], t) new_sys = alias_elimination(sys) @test isempty(observed(new_sys)) -@named sys = ODESystem([D(x) ~ x, +@named sys = System([D(x) ~ x, D(y) + D(x) ~ 0], t) new_sys = alias_elimination(sys) @test isempty(observed(new_sys)) -@named sys = ODESystem([D(x) ~ 1 - x, +@named sys = System([D(x) ~ 1 - x, y + D(x) ~ 0], t) new_sys = alias_elimination(sys) @test isempty(observed(new_sys)) eqs = [x ~ 0 D(x) ~ x + y] -@named sys = ODESystem(eqs, t, [x, y], []) +@named sys = System(eqs, t, [x, y], []) ss = structural_simplify(sys) @test isempty(equations(ss)) @test sort(string.(observed(ss))) == ["x(t) ~ 0.0" @@ -282,7 +282,7 @@ ss = structural_simplify(sys) "y(t) ~ xˍt(t) - x(t)"] eqs = [D(D(x)) ~ -x] -@named sys = ODESystem(eqs, t, [x], []) +@named sys = System(eqs, t, [x], []) ss = alias_elimination(sys) @test length(equations(ss)) == length(unknowns(ss)) == 1 ss = structural_simplify(sys) diff --git a/test/scc_nonlinear_problem.jl b/test/scc_nonlinear_problem.jl index ac7978d269..f98f588032 100644 --- a/test/scc_nonlinear_problem.jl +++ b/test/scc_nonlinear_problem.jl @@ -226,7 +226,7 @@ import ModelingToolkitStandardLibrary.Hydraulic.IsothermalCompressible as IC f ~ p * area m ~ rho * x * area] - return ODESystem(eqs, t, vars, pars; name, systems) + return System(eqs, t, vars, pars; name, systems) end systems = @named begin @@ -255,7 +255,7 @@ import ModelingToolkitStandardLibrary.Hydraulic.IsothermalCompressible as IC initialization_eqs = [mass.s ~ 0.0 mass.v ~ 0.0] - @mtkbuild sys = ODESystem(eqs, t, [], []; systems, initialization_eqs) + @mtkbuild sys = System(eqs, t, [], []; systems, initialization_eqs) prob = ODEProblem(sys, [], (0, 5)) sol = solve(prob) @test SciMLBase.successful_retcode(sol) diff --git a/test/sciml_problem_inputs.jl b/test/sciml_problem_inputs.jl index bfa560cda3..deae4b4772 100644 --- a/test/sciml_problem_inputs.jl +++ b/test/sciml_problem_inputs.jl @@ -38,7 +38,7 @@ begin ] # Create systems (without structural_simplify, since that might modify systems to affect intended tests). - osys = complete(ODESystem(diff_eqs, t; name = :osys)) + osys = complete(System(diff_eqs, t; name = :osys)) ssys = complete(SDESystem( diff_eqs, noise_eqs, t, [X, Y, Z], [kp, kd, k1, k2]; name = :ssys)) jsys = complete(JumpSystem(jumps, t, [X, Y, Z], [kp, kd, k1, k2]; name = :jsys)) diff --git a/test/sdesystem.jl b/test/sdesystem.jl index b031a2f5ab..96251ceb00 100644 --- a/test/sdesystem.jl +++ b/test/sdesystem.jl @@ -17,8 +17,8 @@ noiseeqs = [0.1 * x, 0.1 * y, 0.1 * z] -# ODESystem -> SDESystem shorthand constructor -@named sys = ODESystem(eqs, tt, [x, y, z], [σ, ρ, β]) +# System -> SDESystem shorthand constructor +@named sys = System(eqs, tt, [x, y, z], [σ, ρ, β]) @test SDESystem(sys, noiseeqs, name = :foo) isa SDESystem @named de = SDESystem(eqs, noiseeqs, tt, [x, y, z], [σ, ρ, β], tspan = (0.0, 10.0)) @@ -510,7 +510,7 @@ noiseeqs = [0.1 * x] @test observed(de) == [weight ~ x * 10] @test sol[weight] == 10 * sol[x] - @named ode = ODESystem(eqs, tt, [x], [α, β], observed = [weight ~ x * 10]) + @named ode = System(eqs, tt, [x], [α, β], observed = [weight ~ x * 10]) ode = complete(ode) odeprob = ODEProblem(ode, u0map, (0.0, 1.0), parammap) solode = solve(odeprob, Tsit5()) @@ -810,13 +810,13 @@ end @test prob[z] ≈ 2.0 end -@testset "SDESystem to ODESystem" begin +@testset "SDESystem to System" begin @variables x(t) y(t) z(t) @testset "Scalar noise" begin @named sys = SDESystem([D(x) ~ x, D(y) ~ y, z ~ x + y], [x, y, 3], t, [x, y, z], [], is_scalar_noise = true) - odesys = ODESystem(sys) - @test odesys isa ODESystem + odesys = System(sys) + @test odesys isa System vs = ModelingToolkit.vars(equations(odesys)) nbrownian = count( v -> ModelingToolkit.getvariabletype(v) == ModelingToolkit.BROWNIAN, vs) @@ -830,8 +830,8 @@ end @testset "Non-scalar vector noise" begin @named sys = SDESystem([D(x) ~ x, D(y) ~ y, z ~ x + y], [x, y, 0], t, [x, y, z], [], is_scalar_noise = false) - odesys = ODESystem(sys) - @test odesys isa ODESystem + odesys = System(sys) + @test odesys isa System vs = ModelingToolkit.vars(equations(odesys)) nbrownian = count( v -> ModelingToolkit.getvariabletype(v) == ModelingToolkit.BROWNIAN, vs) @@ -847,8 +847,8 @@ end 2y 2z 2x z+1 x+1 y+1] @named sys = SDESystem([D(x) ~ x, D(y) ~ y, D(z) ~ z], noiseeqs, t, [x, y, z], []) - odesys = ODESystem(sys) - @test odesys isa ODESystem + odesys = System(sys) + @test odesys isa System vs = ModelingToolkit.vars(equations(odesys)) nbrownian = count( v -> ModelingToolkit.getvariabletype(v) == ModelingToolkit.BROWNIAN, vs) @@ -892,7 +892,7 @@ end 0.1 * y, 0.1 * z] - @named sys = ODESystem(eqs, tt, [x, y, z], [σ, ρ, β]) + @named sys = System(eqs, tt, [x, y, z], [σ, ρ, β]) @named de = SDESystem(eqs, noiseeqs, tt, [x, y, z], [σ, ρ, β], tspan = (0.0, 10.0)) de = complete(de) diff --git a/test/serialization.jl b/test/serialization.jl index ee380b6616..cce015c776 100644 --- a/test/serialization.jl +++ b/test/serialization.jl @@ -3,7 +3,7 @@ using ModelingToolkit: t_nounits as t, D_nounits as D @variables x(t) -@named sys = ODESystem([D(x) ~ -0.5 * x], t, defaults = Dict(x => 1.0)) +@named sys = System([D(x) ~ -0.5 * x], t, defaults = Dict(x => 1.0)) sys = complete(sys) for prob in [ eval(ModelingToolkit.ODEProblem{false}(sys, nothing, nothing, @@ -37,7 +37,7 @@ all_obs = observables(ss) prob = ODEProblem(ss, [capacitor.v => 0.0], (0, 0.1)) sol = solve(prob, ImplicitEuler()) -## Check ODESystem with Observables ---------- +## Check System with Observables ---------- ss_exp = ModelingToolkit.toexpr(ss) ss_ = complete(eval(ss_exp)) prob_ = ODEProblem(ss_, [capacitor.v => 0.0], (0, 0.1)) diff --git a/test/split_parameters.jl b/test/split_parameters.jl index 18fdb49a48..39b242db08 100644 --- a/test/split_parameters.jl +++ b/test/split_parameters.jl @@ -68,7 +68,7 @@ function Sampled(; name, interp = Interpolator(Float64[], 0.0)) output.u ~ get_value(interpolator, t) ] - return ODESystem(eqs, t, vars, [interpolator]; name, systems) + return System(eqs, t, vars, [interpolator]; name, systems) end vars = @variables y(t) dy(t) ddy(t) @@ -80,7 +80,7 @@ eqs = [y ~ src.output.u D(dy) ~ ddy connect(src.output, int.input)] -@named sys = ODESystem(eqs, t, vars, []; systems = [int, src]) +@named sys = System(eqs, t, vars, []; systems = [int, src]) s = complete(sys) sys = structural_simplify(sys) prob = ODEProblem( @@ -107,7 +107,7 @@ eqs = [D(y) ~ dy * a D(dy) ~ ddy * b ddy ~ sin(t) * c] -@named model = ODESystem(eqs, t, vars, pars) +@named model = System(eqs, t, vars, pars) sys = structural_simplify(model; split = false) tspan = (0.0, t_end) @@ -134,7 +134,7 @@ using ModelingToolkit: connect "A wrapper function to make symbolic indexing easier" function wr(sys) - ODESystem(Equation[], ModelingToolkit.get_iv(sys), systems = [sys], name = :a_wrapper) + System(Equation[], ModelingToolkit.get_iv(sys), systems = [sys], name = :a_wrapper) end indexof(sym, syms) = findfirst(isequal(sym), syms) @@ -156,11 +156,11 @@ function SystemModel(u = nothing; name = :model) connect(inertia2.flange_a, spring.flange_b, damper.flange_b)] if u !== nothing push!(eqs, connect(torque.tau, u.output)) - return @named model = ODESystem(eqs, + return @named model = System(eqs, t; systems = [torque, inertia1, inertia2, spring, damper, u]) end - ODESystem(eqs, t; systems = [torque, inertia1, inertia2, spring, damper], + System(eqs, t; systems = [torque, inertia1, inertia2, spring, damper], name, guesses = [spring.flange_a.phi => 0.0]) end @@ -186,7 +186,7 @@ L = randn(1, 4) # Post-multiply by `C` to get the correct input to the controlle # @named input = RealInput(; nin = nin) # @named output = RealOutput(; nout = nout) # eqs = [output.u[i] ~ sum(K[i, j] * input.u[j] for j in 1:nin) for i in 1:nout] -# compose(ODESystem(eqs, t, [], []; name = name), [input, output]) +# compose(System(eqs, t, [], []; name = name), [input, output]) # end @named state_feedback = MatrixGain(K = -L) # Build negative feedback into the feedback matrix @@ -196,7 +196,7 @@ connections = [[state_feedback.input.u[i] ~ model_outputs[i] for i in 1:4] connect(d.output, :d, add.input1) connect(add.input2, state_feedback.output) connect(add.output, :u, model.torque.tau)] -@named closed_loop = ODESystem(connections, t, systems = [model, state_feedback, add, d]) +@named closed_loop = System(connections, t, systems = [model, state_feedback, add, d]) S = get_sensitivity(closed_loop, :u) @testset "Indexing MTKParameters with ParameterIndex" begin @@ -236,7 +236,7 @@ end (::Foo)(x) = 3x @variables x(t) @parameters fn(::Real) = _f1 - @mtkbuild sys = ODESystem(D(x) ~ fn(t), t) + @mtkbuild sys = System(D(x) ~ fn(t), t) @test is_parameter(sys, fn) @test ModelingToolkit.defaults(sys)[fn] == _f1 @@ -260,7 +260,7 @@ end interp = LinearInterpolation(ts .^ 2, ts; extrapolate = true) @variables x(t) @parameters (fn::typeof(interp))(..) - @mtkbuild sys = ODESystem(D(x) ~ fn(x), t) + @mtkbuild sys = System(D(x) ~ fn(x), t) @test is_parameter(sys, fn) getter = getp(sys, fn) prob = ODEProblem(sys, [x => 1.0], (0.0, 1.0), [fn => interp]) diff --git a/test/state_selection.jl b/test/state_selection.jl index a8d3e57773..b8bec5d7b7 100644 --- a/test/state_selection.jl +++ b/test/state_selection.jl @@ -7,7 +7,7 @@ eqs = [x1 + x2 + u1 ~ 0 x1 + x2 + x3 + u2 ~ 0 x1 + D(x3) + x4 + u3 ~ 0 2 * D(D(x1)) + D(D(x2)) + D(D(x3)) + D(x4) + u4 ~ 0] -@named sys = ODESystem(eqs, t) +@named sys = System(eqs, t) let dd = dummy_derivative(sys) has_dx1 = has_dx2 = false @@ -35,7 +35,7 @@ eqs = [D(x) ~ σ * (y - x) 0 ~ a + z u ~ z + a] -lorenz1 = ODESystem(eqs, t, name = :lorenz1) +lorenz1 = System(eqs, t, name = :lorenz1) let al1 = alias_elimination(lorenz1) let lss = partial_state_selection(al1) @test length(equations(lss)) == 2 @@ -47,13 +47,13 @@ let @connector function Fluid_port(; name, p = 101325.0, m = 0.0, T = 293.15) sts = @variables p(t) [guess = p] m(t) [guess = m, connect = Flow] T(t) [ guess = T, connect = Stream] - ODESystem(Equation[], t, sts, []; name = name) + System(Equation[], t, sts, []; name = name) end #this one is for latter @connector function Heat_port(; name, Q = 0.0, T = 293.15) sts = @variables T(t) [guess = T] Q(t) [guess = Q, connect = Flow] - ODESystem(Equation[], t, sts, []; name = name) + System(Equation[], t, sts, []; name = name) end # like ground but for fluid systems (fluid_port.m is expected to be zero in closed loop) @@ -62,7 +62,7 @@ let ps = @parameters p=p T_back=T_back eqs = [fluid_port.p ~ p fluid_port.T ~ T_back] - compose(ODESystem(eqs, t, [], ps; name = name), fluid_port) + compose(System(eqs, t, [], ps; name = name), fluid_port) end function Source(; name, delta_p = 100, T_feed = 293.15) @@ -73,7 +73,7 @@ let supply_port.p ~ return_port.p + delta_p supply_port.T ~ instream(supply_port.T) return_port.T ~ T_feed] - compose(ODESystem(eqs, t, [], ps; name = name), [supply_port, return_port]) + compose(System(eqs, t, [], ps; name = name), [supply_port, return_port]) end function Substation(; name, T_return = 343.15) @@ -84,7 +84,7 @@ let supply_port.p ~ return_port.p # zero pressure loss for now supply_port.T ~ instream(supply_port.T) return_port.T ~ T_return] - compose(ODESystem(eqs, t, [], ps; name = name), [supply_port, return_port]) + compose(System(eqs, t, [], ps; name = name), [supply_port, return_port]) end function Pipe(; name, L = 1000, d = 0.1, N = 100, rho = 1000, f = 1) @@ -98,7 +98,7 @@ let v * pi * d^2 / 4 * rho ~ fluid_port_a.m dp_z ~ abs(v) * v * 0.5 * rho * L / d * f # pressure loss D(v) * rho * L ~ (fluid_port_a.p - fluid_port_b.p - dp_z)] - compose(ODESystem(eqs, t, sts, ps; name = name), [fluid_port_a, fluid_port_b]) + compose(System(eqs, t, sts, ps; name = name), [fluid_port_a, fluid_port_b]) end function System(; name, L = 10.0) @named compensator = Compensator() @@ -113,7 +113,7 @@ let connect(supply_pipe.fluid_port_b, substation.supply_port) connect(substation.return_port, return_pipe.fluid_port_b) connect(return_pipe.fluid_port_a, source.return_port)] - compose(ODESystem(eqs, t, [], ps; name = name), subs) + compose(System(eqs, t, [], ps; name = name), subs) end @named system = System(L = 10) @@ -167,7 +167,7 @@ let D(rho_2) ~ (mo_1 - mo_3) / dx D(mo_2) ~ (Ek_1 - Ek_3 + p_1 - p_2) / dx - f / 2 / pipe_D * u_2 * u_2] - @named trans = ODESystem(eqs, t) + @named trans = System(eqs, t) sys = structural_simplify(trans) @@ -273,7 +273,7 @@ let # ---------------------------------------------------------------------------- # solution ------------------------------------------------------------------- - @named catapult = ODESystem(eqs, t, vars, params, defaults = defs) + @named catapult = System(eqs, t, vars, params, defaults = defs) 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 diff --git a/test/static_arrays.jl b/test/static_arrays.jl index 61177e5ab2..61b9bb6b25 100644 --- a/test/static_arrays.jl +++ b/test/static_arrays.jl @@ -8,7 +8,7 @@ eqs = [D(D(x)) ~ σ * (y - x), D(y) ~ x * (ρ - z) - y, D(z) ~ x * y - β * z] -@named sys = ODESystem(eqs, t) +@named sys = System(eqs, t) sys = structural_simplify(sys) u0 = @SVector [D(x) => 2.0, diff --git a/test/steadystatesystems.jl b/test/steadystatesystems.jl index 4f1b5ed063..a99e84d9d2 100644 --- a/test/steadystatesystems.jl +++ b/test/steadystatesystems.jl @@ -6,7 +6,7 @@ using ModelingToolkit: t_nounits as t, D_nounits as D @parameters r @variables x(t) eqs = [D(x) ~ x^2 - r] -@named de = ODESystem(eqs, t) +@named de = System(eqs, t) de = complete(de) for factor in [1e-1, 1e0, 1e10], diff --git a/test/stream_connectors.jl b/test/stream_connectors.jl index 834ebce1a7..23c648d9e1 100644 --- a/test/stream_connectors.jl +++ b/test/stream_connectors.jl @@ -15,7 +15,7 @@ using ModelingToolkit: t_nounits as t, D_nounits as D P(t) = P end - ODESystem(Equation[], t, vars, pars; name = name) + System(Equation[], t, vars, pars; name = name) end @connector function TwoPhaseFluid(; name, R, B, V) @@ -32,7 +32,7 @@ end # equations --------------------------- eqs = Equation[m_flow ~ 0] - ODESystem(eqs, t, vars, pars; name) + System(eqs, t, vars, pars; name) end function MassFlowSource_h(; name, @@ -57,7 +57,7 @@ function MassFlowSource_h(; name, push!(eqns, port.m_flow ~ -m_flow_in) push!(eqns, port.h_outflow ~ h_in) - compose(ODESystem(eqns, t, vars, pars; name = name), subs) + compose(System(eqns, t, vars, pars; name = name), subs) end # Simplified components. @@ -74,7 +74,7 @@ function AdiabaticStraightPipe(; name, eqns = Equation[] push!(eqns, connect(port_a, port_b)) - sys = ODESystem(eqns, t, vars, pars; name = name) + sys = System(eqns, t, vars, pars; name = name) sys = compose(sys, subs) end @@ -97,7 +97,7 @@ function SmallBoundary_Ph(; name, push!(eqns, port1.P ~ P) push!(eqns, port1.h_outflow ~ h) - compose(ODESystem(eqns, t, vars, pars; name = name), subs) + compose(System(eqns, t, vars, pars; name = name), subs) end # N1M1 model and test code. @@ -114,7 +114,7 @@ function N1M1(; name, push!(eqns, connect(source.port1, port_a)) - sys = ODESystem(eqns, t, [], [], name = name) + sys = System(eqns, t, [], [], name = name) sys = compose(sys, subs) end @@ -126,13 +126,13 @@ end eqns = [connect(n1m1.port_a, pipe.port_a) connect(pipe.port_b, sink.port)] -@named sys = ODESystem(eqns, t) +@named sys = System(eqns, t) eqns = [domain_connect(fluid, n1m1.port_a) connect(n1m1.port_a, pipe.port_a) connect(pipe.port_b, sink.port)] -@named n1m1Test = ODESystem(eqns, t, [], []; systems = [fluid, n1m1, pipe, sink]) +@named n1m1Test = System(eqns, t, [], []; systems = [fluid, n1m1, pipe, sink]) @test_nowarn structural_simplify(n1m1Test) @unpack source, port_a = n1m1 @@ -192,7 +192,7 @@ function N1M2(; name, push!(eqns, connect(source.port1, port_a)) push!(eqns, connect(source.port1, port_b)) - sys = ODESystem(eqns, t, [], [], name = name) + sys = System(eqns, t, [], [], name = name) sys = compose(sys, subs) end @@ -203,7 +203,7 @@ end eqns = [connect(n1m2.port_a, sink1.port) connect(n1m2.port_b, sink2.port)] -@named sys = ODESystem(eqns, t) +@named sys = System(eqns, t) @named n1m2Test = compose(sys, n1m2, sink1, sink2) @test_nowarn structural_simplify(n1m2Test) @@ -218,7 +218,7 @@ eqns = [connect(n1m2.port_a, pipe1.port_a) connect(n1m2.port_b, pipe2.port_a) connect(pipe2.port_b, sink2.port)] -@named sys = ODESystem(eqns, t) +@named sys = System(eqns, t) @named n1m2AltTest = compose(sys, n1m2, pipe1, pipe2, sink1, sink2) @test_nowarn structural_simplify(n1m2AltTest) @@ -236,7 +236,7 @@ function N2M2(; name, push!(eqns, connect(port_a, pipe.port_a)) push!(eqns, connect(pipe.port_b, port_b)) - sys = ODESystem(eqns, t, [], [], name = name) + sys = System(eqns, t, [], [], name = name) sys = compose(sys, subs) end @@ -247,14 +247,14 @@ end eqns = [connect(source.port, n2m2.port_a) connect(n2m2.port_b, sink.port1)] -@named sys = ODESystem(eqns, t) +@named sys = System(eqns, t) @named n2m2Test = compose(sys, n2m2, source, sink) @test_nowarn structural_simplify(n2m2Test) # stream var @named sp1 = TwoPhaseFluidPort() @named sp2 = TwoPhaseFluidPort() -@named sys = ODESystem([connect(sp1, sp2)], t) +@named sys = System([connect(sp1, sp2)], t) sys_exp = expand_connections(compose(sys, [sp1, sp2])) @test ssort(equations(sys_exp)) == ssort([0 ~ -sp1.m_flow - sp2.m_flow 0 ~ sp1.m_flow @@ -266,14 +266,14 @@ sys_exp = expand_connections(compose(sys, [sp1, sp2])) # array var @connector function VecPin(; name) sts = @variables v(t)[1:2]=[1.0, 0.0] i(t)[1:2]=1.0 [connect = Flow] - ODESystem(Equation[], t, [sts...;], []; name = name) + System(Equation[], t, [sts...;], []; name = name) end @named vp1 = VecPin() @named vp2 = VecPin() @named vp3 = VecPin() -@named simple = ODESystem([connect(vp1, vp2, vp3)], t) +@named simple = System([connect(vp1, vp2, vp3)], t) sys = expand_connections(compose(simple, [vp1, vp2, vp3])) @test ssort(equations(sys)) == ssort([0 .~ collect(vp1.i) 0 .~ collect(vp2.i) @@ -287,7 +287,7 @@ sys = expand_connections(compose(simple, [vp1, vp2, vp3])) @connector function VectorHeatPort(; name, N = 100, T0 = 0.0, Q0 = 0.0) @variables (T(t))[1:N]=T0 (Q(t))[1:N]=Q0 [connect = Flow] - ODESystem(Equation[], t, [T; Q], []; name = name) + System(Equation[], t, [T; Q], []; name = name) end @test_nowarn @named a = VectorHeatPort() @@ -319,7 +319,7 @@ csys = complete(n1m1Test) # equations --------------------------- eqs = Equation[] - ODESystem(eqs, t, vars, pars; name, defaults = [dm => 0]) + System(eqs, t, vars, pars; name, defaults = [dm => 0]) end @connector function Fluid(; name, R, B, V) @@ -338,7 +338,7 @@ end dm ~ 0 ] - ODESystem(eqs, t, vars, pars; name) + System(eqs, t, vars, pars; name) end function StepSource(; P, name) @@ -358,7 +358,7 @@ function StepSource(; P, name) H.p ~ p_int * (t > 0.01) ] - ODESystem(eqs, t, vars, pars; name, systems) + System(eqs, t, vars, pars; name, systems) end function StaticVolume(; P, V, name) @@ -387,7 +387,7 @@ function StaticVolume(; P, V, name) H.p ~ p H.dm ~ drho * V] - ODESystem(eqs, t, vars, pars; name, systems, + System(eqs, t, vars, pars; name, systems, defaults = [vrho => rho_0 * (1 + p_int / H.bulk)]) end @@ -410,7 +410,7 @@ function PipeBase(; P, R, name) 0 ~ HA.dm + HB.dm domain_connect(HA, HB)] - ODESystem(eqs, t, vars, pars; name, systems) + System(eqs, t, vars, pars; name, systems) end function Pipe(; P, R, name) @@ -432,7 +432,7 @@ function Pipe(; P, R, name) eqs = [connect(v1.H, p12.HA, HA) connect(v2.H, p12.HB, HB)] - ODESystem(eqs, t, vars, pars; name, systems) + System(eqs, t, vars, pars; name, systems) end function TwoFluidSystem(; name) @@ -460,7 +460,7 @@ function TwoFluidSystem(; name) connect(source_b.H, pipe_b.HA) connect(pipe_b.HB, volume_b.H)] - ODESystem(eqs, t, vars, pars; name, systems) + System(eqs, t, vars, pars; name, systems) end @named two_fluid_system = TwoFluidSystem() @@ -498,7 +498,7 @@ function OneFluidSystem(; name) connect(source_b.H, pipe_b.HA) connect(pipe_b.HB, volume_b.H)] - ODESystem(eqs, t, vars, pars; name, systems) + System(eqs, t, vars, pars; name, systems) end @named one_fluid_system = OneFluidSystem() diff --git a/test/structural_transformation/index_reduction.jl b/test/structural_transformation/index_reduction.jl index f9d6037022..26f73db4e7 100644 --- a/test/structural_transformation/index_reduction.jl +++ b/test/structural_transformation/index_reduction.jl @@ -12,7 +12,7 @@ using ModelingToolkit: t_nounits as t, D_nounits as D eqs2 = [D(D(x)) ~ T * x, D(D(y)) ~ T * y - g, 0 ~ x^2 + y^2 - L^2] -pendulum2 = ODESystem(eqs2, t, [x, y, T], [L, g], name = :pendulum) +pendulum2 = System(eqs2, t, [x, y, T], [L, g], name = :pendulum) lowered_sys = ModelingToolkit.ode_order_lowering(pendulum2) lowered_eqs = [D(xˍt) ~ T * x, @@ -20,7 +20,7 @@ lowered_eqs = [D(xˍt) ~ T * x, D(x) ~ xˍt, D(y) ~ yˍt, 0 ~ x^2 + y^2 - L^2] -@test ODESystem(lowered_eqs, t, [xˍt, yˍt, x, y, T], [L, g], name = :pendulum) == +@test System(lowered_eqs, t, [xˍt, yˍt, x, y, T], [L, g], name = :pendulum) == lowered_sys @test isequal(equations(lowered_sys), lowered_eqs) @@ -30,7 +30,7 @@ eqs = [D(x) ~ w, D(w) ~ T * x, D(z) ~ T * y - g, 0 ~ x^2 + y^2 - L^2] -pendulum = ODESystem(eqs, t, [x, y, w, z, T], [L, g], name = :pendulum) +pendulum = System(eqs, t, [x, y, w, z, T], [L, g], name = :pendulum) state = TearingState(pendulum) @unpack graph, var_to_diff = state.structure @@ -57,7 +57,7 @@ idx1_pendulum = [D(x) ~ w, # 2x*D(D(x)) + 2*D(x)*D(x) + 2y*D(D(y)) + 2*D(y)*D(y) and # substitute the rhs 0 ~ 2x * (T * x) + 2 * xˍt * xˍt + 2y * (T * y - g) + 2 * yˍt * yˍt] -@named idx1_pendulum = ODESystem(idx1_pendulum, t, [x, y, w, z, xˍt, yˍt, T], [L, g]) +@named idx1_pendulum = System(idx1_pendulum, t, [x, y, w, z, xˍt, yˍt, T], [L, g]) first_order_idx1_pendulum = complete(ode_order_lowering(idx1_pendulum)) using OrdinaryDiffEq @@ -90,7 +90,7 @@ sol = solve(prob_auto, Rodas5()); eqs2 = [D(D(x)) ~ T * x, D(D(y)) ~ T * y - g, 0 ~ x^2 + y^2 - L^2] -pendulum2 = ODESystem(eqs2, t, [x, y, T], [L, g], name = :pendulum) +pendulum2 = System(eqs2, t, [x, y, T], [L, g], name = :pendulum) # Turn into a first order differential equation system first_order_sys = ModelingToolkit.ode_order_lowering(pendulum2) @@ -126,7 +126,7 @@ eqs = [D(x) ~ w, D(w) ~ T * x, D(z) ~ T * y - g, 0 ~ x^2 + y^2 - L^2] -pendulum = ODESystem(eqs, t, [x, y, w, z, T], [L, g], name = :pendulum) +pendulum = System(eqs, t, [x, y, w, z, T], [L, g], name = :pendulum) let pss_pendulum = partial_state_selection(pendulum) # This currently selects `T` rather than `x` at top level. Needs tearing priorities to fix. @@ -159,7 +159,7 @@ let eqs = [D(D(x)) ~ λ * x D(D(y)) ~ λ * y - g x^2 + y^2 ~ 1] - @named pend = ODESystem(eqs, t) + @named pend = System(eqs, t) 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]) diff --git a/test/structural_transformation/tearing.jl b/test/structural_transformation/tearing.jl index e9cd92ec94..24083b3524 100644 --- a/test/structural_transformation/tearing.jl +++ b/test/structural_transformation/tearing.jl @@ -147,7 +147,7 @@ using ModelingToolkit, OrdinaryDiffEq, BenchmarkTools eqs = [D(x) ~ z * h 0 ~ x - y 0 ~ sin(z) + y - p * t] -@named daesys = ODESystem(eqs, t) +@named daesys = System(eqs, t) 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] @@ -164,7 +164,7 @@ prob.f(du, u, pr, tt) @test du≈[u[2], u[1] + sin(u[2]) - pr * tt] atol=1e-5 # test the initial guess is respected -@named sys = ODESystem(eqs, t, defaults = Dict(z => NaN)) +@named sys = System(eqs, t, defaults = Dict(z => NaN)) 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) @@ -177,7 +177,7 @@ function Translational_Mass(; name, m = 1.0) eqs = [D(s) ~ v D(v) ~ a m * a ~ 0.0] - ODESystem(eqs, t, sts, ps; name = name) + System(eqs, t, sts, ps; name = name) end m = 1.0 @@ -185,7 +185,7 @@ m = 1.0 ms_eqs = [] -@named _ms_model = ODESystem(ms_eqs, t) +@named _ms_model = System(ms_eqs, t) @named ms_model = compose(_ms_model, [mass]) diff --git a/test/structural_transformation/utils.jl b/test/structural_transformation/utils.jl index 67cf2f72a0..7008a31111 100644 --- a/test/structural_transformation/utils.jl +++ b/test/structural_transformation/utils.jl @@ -18,7 +18,7 @@ eqs = [D(x) ~ w, D(w) ~ T * x, D(z) ~ T * y - g, 0 ~ x^2 + y^2 - L^2] -pendulum = ODESystem(eqs, t, [x, y, w, z, T], [L, g], name = :pendulum) +pendulum = System(eqs, t, [x, y, w, z, T], [L, g], name = :pendulum) state = TearingState(pendulum) StructuralTransformations.find_solvables!(state) sss = state.structure @@ -48,7 +48,7 @@ end @variables x(t) y(t)[1:2] z(t)[1:2] @parameters foo(::AbstractVector)[1:2] _tmp_fn(x) = 2x - @mtkbuild sys = ODESystem( + @mtkbuild sys = System( [D(x) ~ z[1] + z[2] + foo(z)[1], y[1] ~ 2t, y[2] ~ 3t, z ~ foo(y)], t) @test length(equations(sys)) == 1 @test length(observed(sys)) == 7 @@ -74,7 +74,7 @@ end val[] += 1 return [x, 2x] end - @mtkbuild sys = ODESystem([D(x) ~ y[1] + y[2], y ~ foo(x)], t) + @mtkbuild sys = System([D(x) ~ y[1] + y[2], y ~ foo(x)], t) @test length(equations(sys)) == 1 @test length(observed(sys)) == 4 prob = ODEProblem(sys, [x => 1.0], (0.0, 1.0), [foo => _tmp_fn2]) @@ -93,7 +93,7 @@ end @testset "CSE hack in equations(sys)" begin val[] = 0 @variables z(t)[1:2] - @mtkbuild sys = ODESystem( + @mtkbuild sys = System( [D(y) ~ foo(x), D(x) ~ sum(y), zeros(2) ~ foo(prod(z))], t) @test length(equations(sys)) == 5 @test length(observed(sys)) == 2 @@ -119,7 +119,7 @@ end @variables x(t) y(t)[1:2] z(t)[1:2] @parameters foo(::AbstractVector)[1:2] _tmp_fn(x) = 2x - @named sys = ODESystem( + @named sys = System( [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) @@ -140,7 +140,7 @@ end @variables x(t) y(t)[1:2] z(t)[1:2] w(t) @parameters foo(::AbstractVector)[1:2] _tmp_fn(x) = 2x - @named sys = ODESystem( + @named sys = System( [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) @@ -160,7 +160,7 @@ end @testset "additional passes" begin @variables x(t) y(t) - @named sys = ODESystem([D(x) ~ x, y ~ x + t], t) + @named sys = System([D(x) ~ x, y ~ x + t], t) value = Ref(0) pass(sys; kwargs...) = (value[] += 1; return sys) structural_simplify(sys; additional_passes = [pass]) @@ -198,13 +198,13 @@ end end @testset "Requires simplified system" begin @variables x(t) y(t) - @named sys = ODESystem([D(x) ~ x, y ~ 2x], t) + @named sys = System([D(x) ~ x, y ~ 2x], t) sys = complete(sys) @test_throws ArgumentError map_variables_to_equations(sys) end @testset "`ODESystem`" begin @variables x(t) y(t) z(t) - @mtkbuild sys = ODESystem([D(x) ~ 2x + y, y ~ x + z, z^3 + x^3 ~ 12], t) + @mtkbuild sys = System([D(x) ~ 2x + y, y ~ x + z, z^3 + x^3 ~ 12], t) mapping = map_variables_to_equations(sys) @test mapping[x] == (D(x) ~ 2x + y) @test mapping[y] == (y ~ x + z) @@ -217,7 +217,7 @@ end eqs = [D(D(x)) ~ λ * x D(D(y)) ~ λ * y - g x^2 + y^2 ~ 1] - @mtkbuild sys = ODESystem(eqs, t) + @mtkbuild sys = System(eqs, t) mapping = map_variables_to_equations(sys) yt = default_toterm(unwrap(D(y))) diff --git a/test/substitute_component.jl b/test/substitute_component.jl index 9fb254136b..ad20dbcbc1 100644 --- a/test/substitute_component.jl +++ b/test/substitute_component.jl @@ -232,9 +232,9 @@ end @testset "Different indepvar" begin @independent_variables tt - @named empty = ODESystem(Equation[], t) - @named outer = ODESystem(Equation[], t; systems = [empty]) - @named empty = ODESystem(Equation[], tt) + @named empty = System(Equation[], t) + @named outer = System(Equation[], t; systems = [empty]) + @named empty = System(Equation[], tt) @test_throws ["independent variable"] substitute_component( outer, outer.empty => empty) end diff --git a/test/symbolic_events.jl b/test/symbolic_events.jl index 48faa76aa3..35a5369869 100644 --- a/test/symbolic_events.jl +++ b/test/symbolic_events.jl @@ -202,14 +202,14 @@ end end @testset "Condition Compilation" begin - @named sys = ODESystem(eqs, t, continuous_events = [x ~ 1]) + @named sys = System(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]) + @named sys2 = System([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) .== [ @@ -286,7 +286,7 @@ end @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 + @named sys = System(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 @@ -302,7 +302,7 @@ end root_eqs = [x ~ 0] affect = [v ~ -Pre(v)] - @named ball = ODESystem( + @named ball = System( [D(x) ~ v D(v) ~ -9.8], t, continuous_events = root_eqs => affect) @@ -323,7 +323,7 @@ end events = [[x ~ 0] => [vx ~ -Pre(vx)] [y ~ -1.5, y ~ 1.5] => [vy ~ -Pre(vy)]] - @named ball = ODESystem( + @named ball = System( [D(x) ~ vx D(y) ~ vy D(vx) ~ -9.8 @@ -363,7 +363,7 @@ 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( + @named ball = System( [D(x) ~ vx D(y) ~ vy D(vx) ~ -1 @@ -391,7 +391,7 @@ end 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) + @named sys = System(eq, t, continuous_events = ev) sys = structural_simplify(sys) prob = ODEProblem(sys, zeros(2), (0.0, 5.1)) sol = solve(prob, Tsit5()) @@ -410,22 +410,22 @@ end ps = @parameters m = m sts = @variables pos(t)=p vel(t)=v eqs = Dₜ(pos) ~ vel - ODESystem(eqs, t, [pos, vel], ps; name) + System(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) + System(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) + System(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), + compose(System(Equation[], t; name), spring, damper) end connect_sd(sd, m1, m2) = [ @@ -438,7 +438,7 @@ end 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 = System(eqs, t; observed = [y ~ mass2.pos]) @named model = compose(_model, mass1, mass2, sd) end model = Model(sin(30t)) @@ -471,7 +471,7 @@ end ∂ₜ = D eqs = [∂ₜ(A) ~ -k * A] - @named osys = ODESystem(eqs, t, [A], [k, t1, t2], discrete_events = [cb1, cb2]) + @named osys = System(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] @@ -482,7 +482,7 @@ end 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 osys1 = System(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] @@ -497,13 +497,13 @@ end # 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 osys‵ = System(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 osys3 = System(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) @@ -515,7 +515,7 @@ end nothing end cb2‵‵ = [2.0] => (affect!, [], [k], [k], nothing) - @named osys4 = ODESystem(eqs, t, [A], [k, t1], discrete_events = [cb1, cb2‵‵]) + @named osys4 = System(eqs, t, [A], [k, t1], discrete_events = [cb1, cb2‵‵]) @named ssys4 = SDESystem(eqs, [0.0], t, [A], [k, t1], discrete_events = [cb1, cb2‵‵]) oprob4 = ODEProblem(complete(osys4), u0, tspan, p) @@ -524,12 +524,12 @@ end # 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 osys5 = System(eqs, t, [A], [k, t1, t2], discrete_events = [cb1, cb2‵‵‵]) @named ssys5 = SDESystem(eqs, [0.0], t, [A], [k, t1, t2], discrete_events = [cb1, cb2‵‵‵]) 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 osys6 = System(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) @@ -539,7 +539,7 @@ end 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‵‵‵], + @named osys7 = System(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‵‵‵], @@ -625,19 +625,19 @@ end 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) + System(eqs, t, sts, ps, continuous_events = [ev]; name) end @named oscce = oscillator_ce() eqs = [oscce.F ~ 0] - @named eqs_sys = ODESystem(eqs, t) + @named eqs_sys = System(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 typeof(oneosc_ce_simpl) == System @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 @@ -653,7 +653,7 @@ end [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]) + @named trigsys = System(eqs, t; continuous_events = [evt1, evt2]) trigsys_ss = structural_simplify(trigsys) prob = ODEProblem(trigsys_ss, [], (0.0, 2π)) sol = solve(prob, Tsit5()) @@ -675,7 +675,7 @@ end 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]) + @named trigsys = System(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) @@ -699,7 +699,7 @@ end [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]) + @named trigsys = System(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) @@ -718,7 +718,7 @@ end 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]) + @named trigsys = System(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) @@ -742,7 +742,7 @@ end evt2 = ModelingToolkit.SymbolicContinuousCallback( [c2 ~ 0], (record_crossings, [c2 => :v], [], [], cr2); rootfind = SciMLBase.RightRootFind) - @named trigsys = ODESystem(eqs, t; continuous_events = [evt1, evt2]) + @named trigsys = System(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) @@ -762,7 +762,7 @@ end evt2 = ModelingToolkit.SymbolicContinuousCallback( [c2 ~ 0], (record_crossings, [c2 => :v], [], [], cr2); rootfind = SciMLBase.RightRootFind) - @named trigsys = ODESystem(eqs, t; continuous_events = [evt1, evt2]) + @named trigsys = System(eqs, t; continuous_events = [evt1, evt2]) trigsys_ss = structural_simplify(trigsys) prob = ODEProblem(trigsys_ss, [], (0.0, 2π)) sol = solve(prob, Tsit5()) @@ -780,7 +780,7 @@ end evt2 = ModelingToolkit.SymbolicContinuousCallback( [c2 ~ 0], (record_crossings, [c2 => :v], [], [], cr2); rootfind = SciMLBase.RightRootFind) - @named trigsys = ODESystem(eqs, t; continuous_events = [evt2, evt1]) + @named trigsys = System(eqs, t; continuous_events = [evt2, evt1]) trigsys_ss = structural_simplify(trigsys) prob = ODEProblem(trigsys_ss, [], (0.0, 2π)) sol = solve(prob, Tsit5()) @@ -882,7 +882,7 @@ 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]; + @mtkbuild sys = System(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] @@ -910,7 +910,7 @@ end ModelingToolkit.ImperativeAffect(modified = (; furnace_on)) do x, o, i, c @set! x.furnace_on = true end) - @named sys = ODESystem( + @named sys = System( 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)) @@ -930,7 +930,7 @@ end ModelingToolkit.ImperativeAffect(modified = (; furnace_on)) do x, o, c, i @set! x.furnace_on = true end) - @named sys = ODESystem( + @named sys = System( 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)) @@ -952,7 +952,7 @@ end 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]) + @named sys = System(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( @@ -968,7 +968,7 @@ end modified = (; furnace_on, tempsq), observed = (; furnace_on)) do x, o, c, i @set! x.furnace_on = false end) - @named sys = ODESystem( + @named sys = System( eqs, t, [temp, tempsq], params; continuous_events = [furnace_off]) ss = structural_simplify(sys) @test_throws "refers to missing variable(s)" prob=ODEProblem( @@ -981,7 +981,7 @@ end observed = (; furnace_on, not_actually_here)) do x, o, c, i @set! x.furnace_on = false end) - @named sys = ODESystem( + @named sys = System( eqs, t, [temp, tempsq], params; continuous_events = [furnace_off]) ss = structural_simplify(sys) @test_throws "refers to missing variable(s)" prob=ODEProblem( @@ -993,7 +993,7 @@ end observed = (; furnace_on)) do x, o, c, i return (; fictional2 = false) end) - @named sys = ODESystem( + @named sys = System( eqs, t, [temp, tempsq], params; continuous_events = [furnace_off]) ss = structural_simplify(sys) prob = ODEProblem( @@ -1053,7 +1053,7 @@ end @set! x.cnt += decoder(x.hA, x.hB, o.qA, x.qB) x end; rootfind = SciMLBase.RightRootFind) - @named sys = ODESystem( + @named sys = System( eqs, t, [theta, omega], params; continuous_events = [qAevt, qBevt]) ss = structural_simplify(sys) prob = ODEProblem(ss, [theta => 1e-5], (0.0, pi)) @@ -1068,7 +1068,7 @@ end 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]) + @mtkbuild sys = System(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 @@ -1089,7 +1089,7 @@ end 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]) + @mtkbuild sys = System(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 @@ -1103,7 +1103,7 @@ end finaled = false cb3 = ModelingToolkit.SymbolicDiscreteCallback( 1.0, [x ~ 2], initialize = a, finalize = b) - @mtkbuild sys = ODESystem(D(x) ~ -1, t, [x], []; discrete_events = [cb3]) + @mtkbuild sys = System(D(x) ~ -1, t, [x], []; discrete_events = [cb3]) prob = ODEProblem(sys, [x => 1.0], (0.0, 2), []) sol = solve(prob, Tsit5()) @test inited == true @@ -1116,7 +1116,7 @@ end 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]) + @mtkbuild sys = System(D(x) ~ -1, t, [x], []; discrete_events = [cb3]) prob = ODEProblem(sys, [x => 1.0], (0.0, 2), []) sol = solve(prob, Tsit5()) @test seen == true @@ -1127,7 +1127,7 @@ end 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]) + @mtkbuild sys = System(D(x) ~ -1, t, [x], []; discrete_events = [cb3]) prob = ODEProblem(sys, [x => 1.0], (0.0, 2), []) sol = solve(prob, Tsit5()) @test seen == true @@ -1140,7 +1140,7 @@ end finaled = false cb3 = ModelingToolkit.SymbolicDiscreteCallback( t == 1.0, f, initialize = a, finalize = b) - @mtkbuild sys = ODESystem(D(x) ~ -1, t, [x], []; discrete_events = [cb3]) + @mtkbuild sys = System(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 @@ -1152,17 +1152,17 @@ 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]) + @mtkbuild pend = System(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]) + @mtkbuild pend = System(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] => [x ~ 1, y ~ 1] - @mtkbuild pend = ODESystem(eqs, t; continuous_events = [cb]) + @mtkbuild pend = System(eqs, t; continuous_events = [cb]) prob = ODEProblem(pend, [x => 1], (0.0, 3.0), guesses = [y => x]) @test all(≈(0.0; atol = 1e-9), solve(prob, Rodas5())[[x, y]][end]) end @@ -1205,7 +1205,7 @@ end @set! m.x = 0.0 return m end - return ODESystem(eqs, t, vars, params; name = name, + return System(eqs, t, vars, params; name = name, continuous_events = [[x ~ max_time] => reset]) end @@ -1219,19 +1219,19 @@ end eqs = reduce(vcat, Symbolics.scalarize.([ D(x) ~ 1.0 ])) - return ODESystem(eqs, t, vars, params; name = name) # note no event + return System(eqs, t, vars, params; name = name) # note no event end @named wd1 = weird1(0.021) @named wd2 = weird2(0.021) - sys1 = structural_simplify(ODESystem([], t; name = :parent, + sys1 = structural_simplify(System([], 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 = structural_simplify(System([], 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 @@ -1253,7 +1253,7 @@ end D(D(y)) ~ λ * y - g x^2 + y^2 ~ 1] c_evt = [t ~ 5.0] => [x ~ Pre(x) + 0.1] - @mtkbuild pend = ODESystem(eqs, t, continuous_events = c_evt) + @mtkbuild pend = System(eqs, t, continuous_events = c_evt) 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) @@ -1261,7 +1261,7 @@ end # Implicit affect with Pre c_evt = [t ~ 5.0] => [x ~ Pre(x) + y^2] - @mtkbuild pend = ODESystem(eqs, t, continuous_events = c_evt) + @mtkbuild pend = System(eqs, t, continuous_events = c_evt) 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), @@ -1270,7 +1270,7 @@ end # Impossible affect errors c_evt = [t ~ 5.0] => [x ~ Pre(x) + 2] - @mtkbuild pend = ODESystem(eqs, t, continuous_events = c_evt) + @mtkbuild pend = System(eqs, t, continuous_events = c_evt) prob = ODEProblem(pend, [x => 1, y => 0], (0.0, 10.0), [g => 1], guesses = [λ => 1]) @test_throws UnsolvableCallbackError sol=solve(prob, FBDF()) @@ -1281,7 +1281,7 @@ end 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) + @mtkbuild pend = System(eqs, t, continuous_events = c_evt) 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] @@ -1291,7 +1291,7 @@ end 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) + @mtkbuild sys = System(eqs, t, continuous_events = c_evt) prob = ODEProblem(sys, [x => 1.0], (0.0, 10.0), [g => 2]) sol = solve(prob, FBDF()) @test sol.ps[g] ≈ [2.0, 3.0] @@ -1300,7 +1300,7 @@ end # 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) + @mtkbuild sys = System(eqs, t, continuous_events = c_evt) 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] @@ -1320,14 +1320,14 @@ end @set! m.x = 0.0 return m end - return ODESystem(eqs, t, vars, []; name = name, + return System(eqs, t, vars, []; name = name, continuous_events = [[x ~ max_time] => reset]) end shared_pars = @parameters begin vals(t)[1:2] = 0.0 end - @named sys = ODESystem(Equation[], t, [], Symbolics.scalarize(vals); + @named sys = System(Equation[], t, [], Symbolics.scalarize(vals); systems = [child(vals; name = :child)]) sys = structural_simplify(sys) sol = solve(ODEProblem(sys, [], (0.0, 1.0)), Tsit5()) diff --git a/test/symbolic_indexing_interface.jl b/test/symbolic_indexing_interface.jl index 613bfc8213..b99ece927e 100644 --- a/test/symbolic_indexing_interface.jl +++ b/test/symbolic_indexing_interface.jl @@ -3,11 +3,11 @@ using ModelingToolkit: t_nounits as t, D_nounits as D, ParameterIndex, SymbolicContinuousCallback using SciMLStructures: Tunable -@testset "ODESystem" begin +@testset "System" begin @parameters a b @variables x(t)=1.0 y(t)=2.0 xy(t) eqs = [D(x) ~ a * y + t, D(y) ~ b * t] - @named odesys = ODESystem(eqs, t, [x, y], [a, b]; observed = [xy ~ x + y]) + @named odesys = System(eqs, t, [x, y], [a, b]; observed = [xy ~ x + y]) odesys = complete(odesys) @test SymbolicIndexingInterface.supports_tuple_observed(odesys) @test all(is_variable.((odesys,), [x, y, 1, 2, :x, :y])) @@ -45,7 +45,7 @@ using SciMLStructures: Tunable @test getter(prob) isa Tuple @test_nowarn @inferred getter(prob) - @named odesys = ODESystem( + @named odesys = System( eqs, t, [x, y], [a, b]; defaults = [xy => 3.0], observed = [xy ~ x + y]) odesys = complete(odesys) @test default_values(odesys)[xy] == 3.0 @@ -78,7 +78,7 @@ end # D(x) ~ -x + u # y ~ x] -# @mtkbuild cl = ODESystem(eqs, t) +# @mtkbuild cl = System(eqs, t) # partition1_params = [Hold(ud1), Sample(t, dt)(y), ud1, yd1] # partition2_params = [Hold(ud2), Sample(t, dt2)(y), ud2, yd2] # @test all( @@ -187,7 +187,7 @@ using SymbolicIndexingInterface @parameters p1[1:2]=[1.0, 2.0] p2[1:2]=[0.0, 0.0] @variables x(t) = 0 -@named sys = ODESystem( +@named sys = System( [D(x) ~ sum(p1) * t + sum(p2)], t; ) @@ -198,7 +198,7 @@ get_dep = @test_nowarn getu(prob, 2p1) @testset "Observed functions with variables as `Symbol`s" begin @variables x(t) y(t) z(t)[1:2] @parameters p1 p2[1:2, 1:2] - @mtkbuild sys = ODESystem([D(x) ~ x * t + p1, y ~ 2x, D(z) ~ p2 * z], t) + @mtkbuild sys = System([D(x) ~ x * t + p1, y ~ 2x, D(z) ~ p2 * z], t) prob = ODEProblem( sys, [x => 1.0, z => ones(2)], (0.0, 1.0), [p1 => 2.0, p2 => ones(2, 2)]) @test getu(prob, x)(prob) == getu(prob, :x)(prob) @@ -211,7 +211,7 @@ end @testset "Parameter dependencies as symbols" begin @variables x(t) = 1.0 @parameters a=1 b - @named model = ODESystem(D(x) ~ x + a - b, t, parameter_dependencies = [b ~ a + 1]) + @named model = System(D(x) ~ x + a - b, t, parameter_dependencies = [b ~ a + 1]) sys = complete(model) prob = ODEProblem(sys, [], (0.0, 1.0)) @test prob.ps[b] == prob.ps[:b] @@ -220,7 +220,7 @@ end @testset "`get_all_timeseries_indexes` with non-split systems" begin @variables x(t) y(t) z(t) @parameters a - @named sys = ODESystem([D(x) ~ a * x, y ~ 2x, z ~ 0.0], t) + @named sys = System([D(x) ~ a * x, y ~ 2x, z ~ 0.0], t) 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() @@ -233,7 +233,7 @@ end @parameters p(t)[1:2, 1: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]) + @mtkbuild sys = System(D(x) ~ p * x, t; continuous_events = [ev]) p = ModelingToolkit.unwrap(p) @test timeseries_parameter_index(sys, p) === ParameterTimeseriesIndex(1, (1, 1)) @test timeseries_parameter_index(sys, p[1, 1]) === diff --git a/test/symbolic_parameters.jl b/test/symbolic_parameters.jl index f4fa21e614..281002f626 100644 --- a/test/symbolic_parameters.jl +++ b/test/symbolic_parameters.jl @@ -59,7 +59,7 @@ vars = @variables(begin end) der = Differential(t) eqs = [der(x) ~ x] -@named sys = ODESystem(eqs, t, vars, [x0]) +@named sys = System(eqs, t, vars, [x0]) sys = complete(sys) pars = [ x0 => 10.0 diff --git a/test/test_variable_metadata.jl b/test/test_variable_metadata.jl index aaf6addb59..7ce45bb211 100644 --- a/test/test_variable_metadata.jl +++ b/test/test_variable_metadata.jl @@ -123,7 +123,7 @@ Dₜ = Differential(t) @parameters k2 [tunable = false] eqs = [Dₜ(x) ~ (-k2 * x + k * u) / T y ~ x] -sys = ODESystem(eqs, t, name = :tunable_first_order) +sys = System(eqs, t, name = :tunable_first_order) unk_meta = ModelingToolkit.dump_unknowns(sys) @test length(unk_meta) == 3 @test all(iszero, meta.default for meta in unk_meta) @@ -169,14 +169,14 @@ sp = Set(p) @independent_variables t @variables u(t) [description = "A short description of u"] @parameters p [description = "A description of p"] -@named sys = ODESystem([u ~ p], t) +@named sys = System([u ~ p], t) @test_nowarn show(stdout, "text/plain", sys) # Defaults, guesses overridden by system, parameter dependencies @variables x(t)=1.0 y(t) [guess = 1.0] @parameters p=2.0 q -@named sys = ODESystem(Equation[], t, [x, y], [p]; defaults = Dict(x => 2.0, p => 3.0), +@named sys = System(Equation[], t, [x, y], [p]; defaults = Dict(x => 2.0, p => 3.0), guesses = Dict(y => 2.0), parameter_dependencies = [q => 2p]) unks_meta = ModelingToolkit.dump_unknowns(sys) unks_meta = Dict([ModelingToolkit.getname(meta.var) => meta for meta in unks_meta]) diff --git a/test/units.jl b/test/units.jl index ff0cd42ac3..267b526c4b 100644 --- a/test/units.jl +++ b/test/units.jl @@ -44,42 +44,42 @@ D = Differential(t) eqs = [D(E) ~ P - E / τ 0 ~ P] @test UMT.validate(eqs) -@named sys = ODESystem(eqs, t) +@named sys = System(eqs, t) @test !UMT.validate(D(D(E)) ~ P) @test !UMT.validate(0 ~ P + E * τ) # Disabling unit validation/checks selectively -@test_throws MT.ArgumentError ODESystem(eqs, t, [E, P, t], [τ], name = :sys) -ODESystem(eqs, t, [E, P, t], [τ], name = :sys, checks = MT.CheckUnits) +@test_throws MT.ArgumentError System(eqs, t, [E, P, t], [τ], name = :sys) +System(eqs, t, [E, P, t], [τ], name = :sys, checks = MT.CheckUnits) eqs = [D(E) ~ P - E / τ 0 ~ P + E * τ] -@test_throws MT.ValidationError ODESystem(eqs, t, name = :sys, checks = MT.CheckAll) -@test_throws MT.ValidationError ODESystem(eqs, t, name = :sys, checks = true) -ODESystem(eqs, t, name = :sys, checks = MT.CheckNone) -ODESystem(eqs, t, name = :sys, checks = false) -@test_throws MT.ValidationError ODESystem(eqs, t, name = :sys, +@test_throws MT.ValidationError System(eqs, t, name = :sys, checks = MT.CheckAll) +@test_throws MT.ValidationError System(eqs, t, name = :sys, checks = true) +System(eqs, t, name = :sys, checks = MT.CheckNone) +System(eqs, t, name = :sys, checks = false) +@test_throws MT.ValidationError System(eqs, t, name = :sys, checks = MT.CheckComponents | MT.CheckUnits) -@named sys = ODESystem(eqs, t, checks = MT.CheckComponents) -@test_throws MT.ValidationError ODESystem(eqs, t, [E, P, t], [τ], name = :sys, +@named sys = System(eqs, t, checks = MT.CheckComponents) +@test_throws MT.ValidationError System(eqs, t, [E, P, t], [τ], name = :sys, checks = MT.CheckUnits) # connection validation @connector function Pin(; name) sts = @variables(v(t)=1.0, [unit = u"V"], i(t)=1.0, [unit = u"A", connect = Flow]) - ODESystem(Equation[], t, sts, []; name = name) + System(Equation[], t, sts, []; name = name) end @connector function OtherPin(; name) sts = @variables(v(t)=1.0, [unit = u"mV"], i(t)=1.0, [unit = u"mA", connect = Flow]) - ODESystem(Equation[], t, sts, []; name = name) + System(Equation[], t, sts, []; name = name) end @connector function LongPin(; name) sts = @variables(v(t)=1.0, [unit = u"V"], i(t)=1.0, [unit = u"A", connect = Flow], x(t)=1.0, [unit = NoUnits]) - ODESystem(Equation[], t, sts, []; name = name) + System(Equation[], t, sts, []; name = name) end @named p1 = Pin() @named p2 = Pin() @@ -91,8 +91,8 @@ bad_length_eqs = [connect(op, lp)] @test UMT.validate(good_eqs) @test !UMT.validate(bad_eqs) @test !UMT.validate(bad_length_eqs) -@named sys = ODESystem(good_eqs, t, [], []) -@test_throws MT.ValidationError ODESystem(bad_eqs, t, [], []; name = :sys) +@named sys = System(good_eqs, t, [], []) +@test_throws MT.ValidationError System(bad_eqs, t, [], []; name = :sys) # Array variables @independent_variables t [unit = u"s"] @@ -100,7 +100,7 @@ bad_length_eqs = [connect(op, lp)] @variables x(t)[1:3] [unit = u"m"] D = Differential(t) eqs = D.(x) .~ v -ODESystem(eqs, t, name = :sys) +System(eqs, t, name = :sys) # Nonlinear system @parameters a [unit = u"kg"^-1] @@ -139,12 +139,12 @@ noiseeqs = [0.1u"MW" 0.1u"MW" D = Differential(t) eqs = [D(L) ~ v, V ~ L^3] -@named sys = ODESystem(eqs, t) +@named sys = System(eqs, t) sys_simple = structural_simplify(sys) eqs = [D(V) ~ r, V ~ L^3] -@named sys = ODESystem(eqs, t) +@named sys = System(eqs, t) sys_simple = structural_simplify(sys) @variables V [unit = u"m"^3] L [unit = u"m"] diff --git a/test/variable_scope.jl b/test/variable_scope.jl index 59647bf441..a7cfe0a1af 100644 --- a/test/variable_scope.jl +++ b/test/variable_scope.jl @@ -57,10 +57,10 @@ p = [a ParentScope(ParentScope(c)) GlobalScope(d)] -level0 = ODESystem(Equation[], t, [], p; name = :level0) -level1 = ODESystem(Equation[], t, [], []; name = :level1) ∘ level0 -level2 = ODESystem(Equation[], t, [], []; name = :level2) ∘ level1 -level3 = ODESystem(Equation[], t, [], []; name = :level3) ∘ level2 +level0 = System(Equation[], t, [], p; name = :level0) +level1 = System(Equation[], t, [], []; name = :level1) ∘ level0 +level2 = System(Equation[], t, [], []; name = :level2) ∘ level1 +level3 = System(Equation[], t, [], []; name = :level3) ∘ level2 ps = ModelingToolkit.getname.(parameters(level3)) @@ -73,8 +73,8 @@ ps = ModelingToolkit.getname.(parameters(level3)) # Tests from PR#2354 @parameters xx[1:2] arr_p = [ParentScope(xx[1]), xx[2]] -arr0 = ODESystem(Equation[], t, [], arr_p; name = :arr0) -arr1 = ODESystem(Equation[], t, [], []; name = :arr1) ∘ arr0 +arr0 = System(Equation[], t, [], arr_p; name = :arr0) +arr1 = System(Equation[], t, [], []; name = :arr1) ∘ arr0 arr_ps = ModelingToolkit.getname.(parameters(arr1)) @test isequal(arr_ps[1], Symbol("xx")) @test isequal(arr_ps[2], Symbol("arr0₊xx")) @@ -82,13 +82,13 @@ arr_ps = ModelingToolkit.getname.(parameters(arr1)) function Foo(; name, p = 1) @parameters p = p @variables x(t) - return ODESystem(D(x) ~ p, t; name) + return System(D(x) ~ p, t; name) end function Bar(; name, p = 2) @parameters p = p @variables x(t) @named foo = Foo(; p) - return ODESystem(D(x) ~ p + t, t; systems = [foo], name) + return System(D(x) ~ p + t, t; systems = [foo], name) end @named bar = Bar() bar = complete(bar) @@ -108,15 +108,15 @@ defs = ModelingToolkit.defaults(bar) p3 = ParentScope(ParentScope(p3)) p4 = GlobalScope(p4) - @named sys1 = ODESystem([D(x1) ~ p1, D(x2) ~ p2, D(x3) ~ p3, D(x4) ~ p4], t) + @named sys1 = System([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]) + @named sys2 = System(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) + @named sys3 = System(Equation[], t) sys3 = sys3 ∘ sys2 @test length(unknowns(sys3)) == 3 @test any(isequal(x3), unknowns(sys3)) diff --git a/test/variable_utils.jl b/test/variable_utils.jl index 1dc45e11ef..36b80a21dd 100644 --- a/test/variable_utils.jl +++ b/test/variable_utils.jl @@ -56,7 +56,7 @@ end ρ β end - sys = ODESystem( + sys = System( [D(D(x)) ~ σ * (y - x) D(y) ~ x * (ρ - z) - y D(z) ~ x * y - β * z], iv; name) @@ -68,12 +68,12 @@ end @parameters begin p[1:2, 1:2] end - sys = ODESystem([D(D(x)) ~ p * x], iv; name) + sys = System([D(D(x)) ~ p * x], iv; name) end function Outer(; name) @named 😄 = Lorenz() @named arr = ArrSys() - sys = ODESystem(Equation[], iv; name, systems = [😄, arr]) + sys = System(Equation[], iv; name, systems = [😄, arr]) end @mtkbuild sys = Outer() From e027e9324e819c8c4acaa0de9c1db6811993b606 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Tue, 22 Apr 2025 18:58:10 +0530 Subject: [PATCH 115/235] test: replace `NonlinearSystem` with `System` --- test/code_generation.jl | 2 +- test/components.jl | 4 +- test/dep_graphs.jl | 2 +- test/dq_units.jl | 6 +-- test/extensions/bifurcationkit.jl | 4 +- test/extensions/homotopy_continuation.jl | 42 +++++++++--------- test/initial_values.jl | 4 +- test/initializationsystem.jl | 10 ++--- test/modelingtoolkitize.jl | 2 +- test/namespacing.jl | 4 +- test/nonlinearsystem.jl | 54 +++++++++++------------ test/parameter_dependencies.jl | 4 +- test/reduction.jl | 4 +- test/scc_nonlinear_problem.jl | 20 ++++----- test/sciml_problem_inputs.jl | 2 +- test/structural_transformation/tearing.jl | 4 +- test/structural_transformation/utils.jl | 2 +- test/symbolic_indexing_interface.jl | 2 +- test/symbolic_parameters.jl | 4 +- test/units.jl | 6 +-- test/variable_scope.jl | 14 +++--- 21 files changed, 98 insertions(+), 98 deletions(-) diff --git a/test/code_generation.jl b/test/code_generation.jl index 87a485e198..0dc86871c5 100644 --- a/test/code_generation.jl +++ b/test/code_generation.jl @@ -31,7 +31,7 @@ using ModelingToolkit: t_nounits as t, D_nounits as D @test fn5(u0, p, 1.0) == 1.0 @variables x y[1:3] - sys = complete(NonlinearSystem(Equation[], [x; y], [p1, p2, p3, p4]; name = :sys)) + sys = complete(System(Equation[], [x; y], [p1, p2, p3, p4]; name = :sys)) p = MTKParameters(sys, []) fn1 = generate_custom_function(sys, x + y[1] + p1 + p2[1] + p3; expression = Val(false)) diff --git a/test/components.jl b/test/components.jl index 282c4c0aa7..ce3f7ecba2 100644 --- a/test/components.jl +++ b/test/components.jl @@ -335,8 +335,8 @@ end @test ModelingToolkit.get_metadata(sys) == "test" end @testset "NonlinearSystem" begin - @named inner = NonlinearSystem([0 ~ x^2 + 4x + 4], [x], []) - @named outer = NonlinearSystem( + @named inner = System([0 ~ x^2 + 4x + 4], [x], []) + @named outer = System( [0 ~ x^3 - y^3], [x, y], []; systems = [inner], metadata = "test") @test ModelingToolkit.get_metadata(outer) == "test" sys = complete(outer) diff --git a/test/dep_graphs.jl b/test/dep_graphs.jl index 76cc216635..3c7b88dd05 100644 --- a/test/dep_graphs.jl +++ b/test/dep_graphs.jl @@ -184,7 +184,7 @@ s_eqdeps = [[1], [2], [3]] eqs = [0 ~ σ * (y - x), 0 ~ ρ - y, 0 ~ y - β * z] -@named ns = NonlinearSystem(eqs, [x, y, z], [σ, ρ, β]) +@named ns = System(eqs, [x, y, z], [σ, ρ, β]) deps = equation_dependencies(ns) eq_sdeps = [[x, y], [y], [y, z]] @test all(i -> isequal(Set(deps[i]), Set(value.(eq_sdeps[i]))), 1:length(deps)) diff --git a/test/dq_units.jl b/test/dq_units.jl index 267e993971..c6cada3363 100644 --- a/test/dq_units.jl +++ b/test/dq_units.jl @@ -81,7 +81,7 @@ System(eqs, t, name = :sys) eqs = [ 0 ~ a * x ] -@named nls = NonlinearSystem(eqs, [x], [a]) +@named nls = System(eqs, [x], [a]) # SDE test w/ noise vector @parameters τ [unit = u"s"] Q [unit = u"W"] @@ -124,12 +124,12 @@ sys_simple = structural_simplify(sys) @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]) +@named sys = System(eqs, [V, L], [t, r]) sys_simple = structural_simplify(sys) eqs = [L ~ v * t, V ~ L^3] -@named sys = NonlinearSystem(eqs, [V, L], [t, r]) +@named sys = System(eqs, [V, L], [t, r]) sys_simple = structural_simplify(sys) #Jump System diff --git a/test/extensions/bifurcationkit.jl b/test/extensions/bifurcationkit.jl index 227cd175e0..7b95ffd82e 100644 --- a/test/extensions/bifurcationkit.jl +++ b/test/extensions/bifurcationkit.jl @@ -8,7 +8,7 @@ let @parameters μ α eqs = [0 ~ μ * x - x^3 + α * y, 0 ~ -y] - @named nsys = NonlinearSystem(eqs, [x, y], [μ, α]) + @named nsys = System(eqs, [x, y], [μ, α]) nsys = complete(nsys) # Creates BifurcationProblem bif_par = μ @@ -103,7 +103,7 @@ let eqs = [0 ~ μ - x^3 + 2x^2, 0 ~ p * μ - y, 0 ~ y - z] - @named nsys = NonlinearSystem(eqs, [x, y, z], [μ, p]) + @named nsys = System(eqs, [x, y, z], [μ, p]) nsys = structural_simplify(nsys) # Creates BifurcationProblem. diff --git a/test/extensions/homotopy_continuation.jl b/test/extensions/homotopy_continuation.jl index 65f7d765a4..607c27346c 100644 --- a/test/extensions/homotopy_continuation.jl +++ b/test/extensions/homotopy_continuation.jl @@ -33,7 +33,7 @@ end eqs = [0 ~ x^2 + y^2 + 2x * y 0 ~ x^2 + 4x + 4 0 ~ y * z + 4x^2] - @mtkbuild sys = NonlinearSystem(eqs) + @mtkbuild sys = System(eqs) u0 = [x => 1.0, y => 1.0, z => 1.0] prob = HomotopyContinuationProblem(sys, u0) @test prob isa NonlinearProblem @@ -60,7 +60,7 @@ end 0 ~ x^2 + 4x + q 0 ~ y * z + 4x^2 + wrapper(r)] - @mtkbuild sys = NonlinearSystem(eqs) + @mtkbuild sys = System(eqs) prob = HomotopyContinuationProblem(sys, [x => 1.0, y => 1.0, z => 1.0], [p => 2.0, q => 4, r => Wrapper([1.0 1.0; 0.0 0.0])]) @test prob.ps[p] == 2.0 @@ -78,7 +78,7 @@ end @parameters p[1:3] _x = collect(x) eqs = collect(0 .~ vec(sum(_x * _x'; dims = 2)) + collect(p)) - @mtkbuild sys = NonlinearSystem(eqs) + @mtkbuild sys = System(eqs) prob = HomotopyContinuationProblem(sys, [x => ones(3)], [p => 1:3]) @test prob[x] == ones(3) @test prob[p + x] == [2, 3, 4] @@ -93,7 +93,7 @@ end @testset "Parametric exponents" begin @variables x = 1.0 @parameters n::Integer = 4 - @mtkbuild sys = NonlinearSystem([x^n + x^2 - 1 ~ 0]) + @mtkbuild sys = System([x^n + x^2 - 1 ~ 0]) prob = HomotopyContinuationProblem(sys, []) sol = solve(prob, singlerootalg) test_single_root(sol) @@ -103,34 +103,34 @@ end @testset "Polynomial check and warnings" begin @variables x = 1.0 - @mtkbuild sys = NonlinearSystem([x^1.5 + x^2 - 1 ~ 0]) + @mtkbuild sys = System([x^1.5 + x^2 - 1 ~ 0]) @test_throws ["Cannot convert", "Unable", "symbolically solve", "Exponent", "not an integer", "not a polynomial"] HomotopyContinuationProblem( sys, []) - @mtkbuild sys = NonlinearSystem([x^x - x ~ 0]) + @mtkbuild sys = System([x^x - x ~ 0]) @test_throws ["Cannot convert", "Unable", "symbolically solve", "Exponent", "unknowns", "not a polynomial"] HomotopyContinuationProblem( sys, []) - @mtkbuild sys = NonlinearSystem([((x^2) / sin(x))^2 + x ~ 0]) + @mtkbuild sys = System([((x^2) / sin(x))^2 + x ~ 0]) @test_throws ["Cannot convert", "both polynomial", "non-polynomial", "recognized", "sin", "not a polynomial"] HomotopyContinuationProblem( sys, []) @variables y = 2.0 - @mtkbuild sys = NonlinearSystem([x^2 + y^2 + 2 ~ 0, y ~ sin(x)]) + @mtkbuild sys = System([x^2 + y^2 + 2 ~ 0, y ~ sin(x)]) @test_throws ["Cannot convert", "recognized", "sin", "not a polynomial"] HomotopyContinuationProblem( sys, []) - @mtkbuild sys = NonlinearSystem([x^2 + y^2 - 2 ~ 0, sin(x + y) ~ 0]) + @mtkbuild sys = System([x^2 + y^2 - 2 ~ 0, sin(x + y) ~ 0]) @test_throws ["Cannot convert", "function of multiple unknowns"] HomotopyContinuationProblem( sys, []) - @mtkbuild sys = NonlinearSystem([sin(x)^2 + 1 ~ 0, cos(y) - cos(x) - 1 ~ 0]) + @mtkbuild sys = System([sin(x)^2 + 1 ~ 0, cos(y) - cos(x) - 1 ~ 0]) @test_throws ["Cannot convert", "multiple non-polynomial terms", "same unknown"] HomotopyContinuationProblem( sys, []) - @mtkbuild sys = NonlinearSystem([sin(x^2)^2 + sin(x^2) - 1 ~ 0]) + @mtkbuild sys = System([sin(x^2)^2 + sin(x^2) - 1 ~ 0]) @test_throws ["import Nemo"] HomotopyContinuationProblem(sys, []) end @@ -138,7 +138,7 @@ import Nemo @testset "With Nemo" begin @variables x = 2.0 - @mtkbuild sys = NonlinearSystem([sin(x^2)^2 + sin(x^2) - 1 ~ 0]) + @mtkbuild sys = System([sin(x^2)^2 + sin(x^2) - 1 ~ 0]) prob = HomotopyContinuationProblem(sys, []) @test prob[1] ≈ 2.0 # singlerootalg doesn't converge @@ -151,8 +151,8 @@ end @variables x=0.25 y=0.125 a = sin(x^2 - 4x + 1) b = cos(3log(y) + 4) - @mtkbuild sys = NonlinearSystem([(a^2 - 5a * b + 6b^2) / (a - 0.25) ~ 0 - (a^2 - 0.75a + 0.125) ~ 0]) + @mtkbuild sys = System([(a^2 - 5a * b + 6b^2) / (a - 0.25) ~ 0 + (a^2 - 0.75a + 0.125) ~ 0]) prob = HomotopyContinuationProblem(sys, []) @test prob[x] ≈ 0.25 @test prob[y] ≈ 0.125 @@ -165,7 +165,7 @@ end @testset "Rational functions" begin @variables x=2.0 y=2.0 @parameters n = 5 - @mtkbuild sys = NonlinearSystem([ + @mtkbuild sys = System([ 0 ~ (x^2 - n * x + 6) * (x - 1) / (x - 2) / (x - 3) ]) prob = HomotopyContinuationProblem(sys, []) @@ -178,7 +178,7 @@ end end end - @named sys = NonlinearSystem( + @named sys = System( [ 0 ~ (x - 2) / (x - 4) + ((x - 3) / (y - 7)) / ((x^2 - 4x + y) / (x - 2.5)), 0 ~ ((y - 3) / (y - 4)) * (n / (y - 5)) + ((x - 1.5) / (x - 5.5))^2 @@ -209,7 +209,7 @@ end @testset "Rational function in observed" begin @variables x=1 y=1 - @mtkbuild sys = NonlinearSystem([x^2 + y^2 - 2x - 2 ~ 0, y ~ (x - 1) / (x - 2)]) + @mtkbuild sys = System([x^2 + y^2 - 2x - 2 ~ 0, y ~ (x - 1) / (x - 2)]) prob = HomotopyContinuationProblem(sys, []) @test any(prob.f.denominator([2.0], parameter_values(prob)) .≈ 0.0) @test SciMLBase.successful_retcode(solve(prob, singlerootalg)) @@ -217,7 +217,7 @@ end @testset "Rational function forced to common denominators" begin @variables x = 1 - @mtkbuild sys = NonlinearSystem([0 ~ 1 / (1 + x) - x]) + @mtkbuild sys = System([0 ~ 1 / (1 + x) - x]) prob = HomotopyContinuationProblem(sys, []) @test any(prob.f.denominator([-1.0], parameter_values(prob)) .≈ 0.0) sol = solve(prob, singlerootalg) @@ -228,7 +228,7 @@ end @testset "Non-polynomial observed not used in equations" begin @variables x=1 y - @mtkbuild sys = NonlinearSystem([x^2 - 2 ~ 0, y ~ sin(x)]) + @mtkbuild sys = System([x^2 - 2 ~ 0, y ~ sin(x)]) prob = HomotopyContinuationProblem(sys, []) sol = @test_nowarn solve(prob, singlerootalg) @test sol[x] ≈ √2.0 @@ -237,8 +237,8 @@ end @testset "`fraction_cancel_fn`" begin @variables x = 1 - @named sys = NonlinearSystem([0 ~ ((x^2 - 5x + 6) / (x - 2) - 1) * (x^2 - 7x + 12) / - (x - 4)^3]) + @named sys = System([0 ~ ((x^2 - 5x + 6) / (x - 2) - 1) * (x^2 - 7x + 12) / + (x - 4)^3]) sys = complete(sys) @testset "`simplify_fractions`" begin diff --git a/test/initial_values.jl b/test/initial_values.jl index c030555b82..aca533acbd 100644 --- a/test/initial_values.jl +++ b/test/initial_values.jl @@ -104,7 +104,7 @@ prob = ODEProblem(sys, [], (0.0, 1.0), [A1 => 0.3]) System(Equation[], t, [x, y], [p]; defaults = [y => nothing], name = :osys), SDESystem(Equation[], [], t, [x, y], [p]; defaults = [y => nothing], name = :ssys), JumpSystem(Equation[], t, [x, y], [p]; defaults = [y => nothing], name = :jsys), - NonlinearSystem(Equation[], [x, y], [p]; defaults = [y => nothing], name = :nsys), + System(Equation[], [x, y], [p]; defaults = [y => nothing], name = :nsys), OptimizationSystem( Equation[], [x, y], [p]; defaults = [y => nothing], name = :optsys), ConstraintsSystem( @@ -245,7 +245,7 @@ end 0 ~ p[1] - X[1], 0 ~ p[2] - X[2] ] - @named nlsys = NonlinearSystem(eqs) + @named nlsys = System(eqs) nlsys = complete(nlsys) # Creates the `NonlinearProblem`. diff --git a/test/initializationsystem.jl b/test/initializationsystem.jl index 22fd6faf88..93a0845bdb 100644 --- a/test/initializationsystem.jl +++ b/test/initializationsystem.jl @@ -784,7 +784,7 @@ end eqs = [0 ~ σ * (y - x), 0 ~ x * (ρ - z) - y, 0 ~ x * y - β * z] - @mtkbuild ns = NonlinearSystem(eqs, [x, y, z], [σ, ρ, β]) + @mtkbuild ns = System(eqs, [x, y, z], [σ, ρ, β]) prob = NonlinearProblem(ns, []) @test prob.f.initialization_data.update_initializeprob! === nothing @@ -834,7 +834,7 @@ end # https://github.com/SciML/NonlinearSolve.jl/issues/586 eqs = [0 ~ -c * z + (q - z) * (x^2) 0 ~ p * (-x + (q - z) * x)] - @named sys = NonlinearSystem(eqs; initialization_eqs = [p^2 + q^2 + 2p * q ~ 0]) + @named sys = System(eqs; initialization_eqs = [p^2 + q^2 + 2p * q ~ 0]) sys = complete(sys) # @mtkbuild sys = NonlinearSystem( # [p * x^2 + q * y^3 ~ 0, x - q ~ 0]; defaults = [q => missing], @@ -1447,7 +1447,7 @@ end end @testset "$Problem" for Problem in [NonlinearProblem, NonlinearLeastSquaresProblem] @parameters p1 p2 - @mtkbuild sys = NonlinearSystem([x^2 + y^2 ~ p1, (x - 1)^2 + (y - 1)^2 ~ p2]; + @mtkbuild sys = System([x^2 + y^2 ~ p1, (x - 1)^2 + (y - 1)^2 ~ p2]; parameter_dependencies = [p2 ~ 2p1], guesses = [p1 => 0.0], defaults = [p1 => missing]) prob = Problem(sys, [x => 1.0, y => 1.0], [p2 => 6.0]) @@ -1466,7 +1466,7 @@ end initialization_eqs = [ X2 ~ Γ[1] - X1 ] - @mtkbuild nlsys = NonlinearSystem(eqs, [X1, X2], [k1, k2, Γ]; initialization_eqs) + @mtkbuild nlsys = System(eqs, [X1, X2], [k1, k2, Γ]; initialization_eqs) @testset "throws if initialization_eqs contain unknowns" begin u0 = [X1 => 1.0, X2 => 2.0] @@ -1479,7 +1479,7 @@ end initialization_eqs = [ Initial(X2) ~ Γ[1] - Initial(X1) ] - @mtkbuild nlsys = NonlinearSystem(eqs, [X1, X2], [k1, k2, Γ]; initialization_eqs) + @mtkbuild nlsys = System(eqs, [X1, X2], [k1, k2, Γ]; initialization_eqs) @testset "solves initialization" begin u0 = [X1 => 1.0, X2 => 2.0] diff --git a/test/modelingtoolkitize.jl b/test/modelingtoolkitize.jl index d146fa95c9..04e6263c7c 100644 --- a/test/modelingtoolkitize.jl +++ b/test/modelingtoolkitize.jl @@ -391,7 +391,7 @@ sys = modelingtoolkitize(prob) @testset "Nonlinear" begin @variables x=1.0 y=2.0 @parameters p=3.0 q=4.0 - @mtkbuild nlsys = NonlinearSystem([0 ~ p * y^2 + x, 0 ~ x + exp(x) * q]) + @mtkbuild nlsys = System([0 ~ p * y^2 + x, 0 ~ x + exp(x) * q]) prob1 = NonlinearProblem(nlsys, []) newsys = complete(modelingtoolkitize(prob1)) @test is_variable(newsys, newsys.x) diff --git a/test/namespacing.jl b/test/namespacing.jl index 3871398144..de33f9e927 100644 --- a/test/namespacing.jl +++ b/test/namespacing.jl @@ -110,7 +110,7 @@ end @testset "NonlinearSystem" begin @variables x @parameters p - sys = NonlinearSystem([x ~ p * x^2 + 1]; name = :inner) + sys = System([x ~ p * x^2 + 1]; name = :inner) @test !iscomplete(sys) @test does_namespacing(sys) @@ -129,7 +129,7 @@ end @test isequal(p, nsys.p) @test !isequal(p, sys.p) - @test_throws ["namespacing", "inner"] NonlinearSystem( + @test_throws ["namespacing", "inner"] System( Equation[]; systems = [nsys], name = :a) end diff --git a/test/nonlinearsystem.jl b/test/nonlinearsystem.jl index 3a22cba409..e120e75e07 100644 --- a/test/nonlinearsystem.jl +++ b/test/nonlinearsystem.jl @@ -26,7 +26,7 @@ end eqs = [0 ~ σ * (y - x) * h, 0 ~ x * (ρ - z) - y, 0 ~ x * y - β * z] -@named ns = NonlinearSystem(eqs, [x, y, z], [σ, ρ, β], defaults = Dict(x => 2)) +@named ns = System(eqs, [x, y, z], [σ, ρ, β], defaults = Dict(x => 2)) @test eval(toexpr(ns)) == ns test_nlsys_inference("standard", ns, (x, y, z), (σ, ρ, β)) @test begin @@ -44,7 +44,7 @@ end eqs = [0 ~ σ * (y - x), y ~ x * (ρ - z), β * z ~ x * y] -@named ns = NonlinearSystem(eqs, [x, y, z], [σ, ρ, β]) +@named ns = System(eqs, [x, y, z], [σ, ρ, β]) jac = calculate_jacobian(ns) @testset "nlsys jacobian" begin @test canonequal(jac[1, 1], σ * -1) @@ -67,7 +67,7 @@ a = y - x eqs = [0 ~ σ * a * h, 0 ~ x * (ρ - z) - y, 0 ~ x * y - β * z] -@named ns = NonlinearSystem(eqs, [x, y, z], [σ, ρ, β]) +@named ns = System(eqs, [x, y, z], [σ, ρ, β]) ns = complete(ns) nlsys_func = generate_function(ns, [x, y, z], [σ, ρ, β]) nf = NonlinearFunction(ns) @@ -102,11 +102,11 @@ eqs1 = [ 0 ~ x + y - z - u ] -lorenz = name -> NonlinearSystem(eqs1, [x, y, z, u, F], [σ, ρ, β], name = name) +lorenz = name -> System(eqs1, [x, y, z, u, F], [σ, ρ, β], name = name) lorenz1 = lorenz(:lorenz1) @test_throws ArgumentError NonlinearProblem(complete(lorenz1), zeros(5), zeros(3)) lorenz2 = lorenz(:lorenz2) -@named connected = NonlinearSystem( +@named connected = System( [s ~ a + lorenz1.x lorenz2.y ~ s * h lorenz1.F ~ lorenz2.u @@ -135,7 +135,7 @@ sol = solve(prob, FBDF(), reltol = 1e-7, abstol = 1e-7) eqs = [0 ~ σ * (y - x), 0 ~ x * (ρ - z) - y, 0 ~ x * y - β * z * h] -@named ns = NonlinearSystem(eqs, [x, y, z], [σ, ρ, β]) +@named ns = System(eqs, [x, y, z], [σ, ρ, β]) np = NonlinearProblem( complete(ns), [0, 0, 0], [σ => 1, ρ => 2, β => 3], jac = true, sparse = true) @test calculate_jacobian(ns, sparse = true) isa SparseMatrixCSC @@ -146,13 +146,13 @@ np = NonlinearProblem( @parameters a @variables x f - NonlinearSystem([0 ~ -a * x + f], [x, f], [a]; name) + System([0 ~ -a * x + f], [x, f], [a]; name) end function issue819() sys1 = makesys(:sys1) sys2 = makesys(:sys1) - @test_throws ArgumentError NonlinearSystem([sys2.f ~ sys1.x, sys1.f ~ 0], [], [], + @test_throws ArgumentError System([sys2.f ~ sys1.x, sys1.f ~ 0], [], [], systems = [sys1, sys2], name = :foo) end issue819() @@ -169,8 +169,8 @@ end 0 ~ b * y ] - @named sys1 = NonlinearSystem(eqs1, [x], [a]) - @named sys2 = NonlinearSystem(eqs2, [y], [b]) + @named sys1 = System(eqs1, [x], [a]) + @named sys2 = System(eqs2, [y], [b]) @named sys3 = extend(sys1, sys2) @test isequal(union(Set(parameters(sys1)), Set(parameters(sys2))), @@ -183,7 +183,7 @@ end @independent_variables t @parameters τ @variables x(t) RHS(t) -@named fol = NonlinearSystem([0 ~ (1 - x * h) / τ], [x], [τ]; +@named fol = System([0 ~ (1 - x * h) / τ], [x], [τ]; observed = [RHS ~ (1 - x) / τ]) @test isequal(RHS, @nonamespace fol.RHS) RHS2 = RHS @@ -208,7 +208,7 @@ eq = [v1 ~ sin(2pi * t * h) u[3] ~ 1, u[4] ~ h] - sys = NonlinearSystem(eqs, collect(u[1:4]), Num[], defaults = Dict([]), name = :test) + sys = System(eqs, collect(u[1:4]), Num[], defaults = Dict([]), name = :test) sys = complete(sys) prob = NonlinearProblem(sys, ones(length(unknowns(sys)))) @@ -222,7 +222,7 @@ end eqs = [0 ~ a * x] testdict = Dict([:test => 1]) -@named sys = NonlinearSystem(eqs, [x], [a], metadata = testdict) +@named sys = System(eqs, [x], [a], metadata = testdict) @test get_metadata(sys) == testdict @testset "Remake" begin @@ -233,7 +233,7 @@ testdict = Dict([:test => 1]) eqs = [0 ~ a * (y - x) * h, 0 ~ x * (b - z) - y, 0 ~ x * y - c * z] - @named sys = NonlinearSystem(eqs, [x, y, z], [a, b, c], defaults = Dict(x => 2.0)) + @named sys = System(eqs, [x, y, z], [a, b, c], defaults = Dict(x => 2.0)) sys = complete(sys) prob = NonlinearProblem(sys, ones(length(unknowns(sys)))) @@ -254,7 +254,7 @@ end eqs = [0 ~ x + sin(y), 0 ~ z - cos(x), 0 ~ x * y] - @named ns = NonlinearSystem(eqs, [x, y, z], []) + @named ns = System(eqs, [x, y, z], []) ns = complete(ns) vs = [unknowns(ns); parameters(ns)] ss_mtk = structural_simplify(ns) @@ -268,7 +268,7 @@ end @variables X(t) alg_eqs = [0 ~ p - d * X] -sys = @test_nowarn NonlinearSystem(alg_eqs; name = :name) +sys = @test_nowarn System(alg_eqs; name = :name) @test isequal(only(unknowns(sys)), X) @test all(isequal.(parameters(sys), [p, d])) @@ -276,14 +276,14 @@ sys = @test_nowarn NonlinearSystem(alg_eqs; name = :name) @variables u1 u2 @parameters u3 u4 eqs = [u3 ~ u1 + u2, u4 ~ 2 * (u1 + u2), u3 + u4 ~ 3 * (u1 + u2)] -@named ns = NonlinearSystem(eqs, [u1, u2], [u3, u4]) +@named ns = System(eqs, [u1, u2], [u3, u4]) 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) +@named ns = System(alg_eqs) sys = structural_simplify(ns) @test length(equations(sys)) == 0 sys = structural_simplify(ns; conservative = true) @@ -298,7 +298,7 @@ sys = structural_simplify(ns; conservative = true) 0 ~ x * y - β * z] guesses = [x => 1.0, z => 0.0] ps = [σ => 10.0, ρ => 26.0, β => 8 / 3] - @mtkbuild ns = NonlinearSystem(eqs) + @mtkbuild ns = System(eqs) @test isequal(calculate_jacobian(ns), [(-1 - z + ρ)*σ -x*σ 2x*(-z + ρ) -β-(x^2)]) @@ -315,7 +315,7 @@ sys = structural_simplify(ns; conservative = true) # system that contains a chain of observed variables when simplified @variables x y z eqs = [0 ~ x^2 + 2z + y, z ~ y, y ~ x] # analytical solution x = y = z = 0 or -3 - @mtkbuild ns = NonlinearSystem(eqs) # solve for y with observed chain z -> x -> y + @mtkbuild ns = System(eqs) # solve for y with observed chain z -> x -> y @test isequal(expand.(calculate_jacobian(ns)), [3 // 2 + y;;]) @test isequal(calculate_hessian(ns), [[1;;]]) prob = NonlinearProblem(ns, unknowns(ns) .=> -4.0) # give guess < -3 to reach -3 @@ -325,7 +325,7 @@ end @testset "Passing `nothing` to `u0`" begin @variables x = 1 - @mtkbuild sys = NonlinearSystem([0 ~ x^2 - x^3 + 3]) + @mtkbuild sys = System([0 ~ x^2 - x^3 + 3]) prob = @test_nowarn NonlinearProblem(sys, nothing) @test_nowarn solve(prob) end @@ -337,7 +337,7 @@ end 2 -2 4 -1 1/2 -1] b = [1, -2, 0] - @named sys = NonlinearSystem(A * x ~ b, [x], []) + @named sys = System(A * x ~ b, [x], []) sys = structural_simplify(sys) prob = NonlinearProblem(sys, unknowns(sys) .=> 0.0) sol = solve(prob) @@ -347,7 +347,7 @@ end @testset "resid_prototype when system has no unknowns and an equation" begin @variables x @parameters p - @named sys = NonlinearSystem([x ~ 1, x^2 - p ~ 0]) + @named sys = System([x ~ 1, x^2 - p ~ 0]) for sys in [ structural_simplify(sys, fully_determined = false), structural_simplify(sys, fully_determined = false, split = false) @@ -365,7 +365,7 @@ end @testset "IntervalNonlinearProblem" begin @variables x @parameters p - @named nlsys = NonlinearSystem([0 ~ x * x - p]) + @named nlsys = System([0 ~ x * x - p]) for sys in [complete(nlsys), complete(nlsys; split = false)] prob = IntervalNonlinearProblem(sys, (0.0, 2.0), [p => 1.0]) @@ -375,7 +375,7 @@ end end @variables y - @mtkbuild sys = NonlinearSystem([0 ~ x * x - p * x + p, 0 ~ x * y + p]) + @mtkbuild sys = System([0 ~ x * x - p * x + p, 0 ~ x * y + p]) @test_throws ["single equation", "unknown"] IntervalNonlinearProblem(sys, (0.0, 1.0)) @test_throws ["single equation", "unknown"] IntervalNonlinearFunction(sys, (0.0, 1.0)) @test_throws ["single equation", "unknown"] IntervalNonlinearProblemExpr( @@ -388,7 +388,7 @@ end @variables x y @parameters p[1:2] (f::Function)(..) - @mtkbuild sys = NonlinearSystem([x^2 - p[1]^2 ~ 0, y^2 ~ f(p)]) + @mtkbuild sys = System([x^2 - p[1]^2 ~ 0, y^2 ~ f(p)]) @test !any(isequal(p[1]), parameters(sys)) @test is_parameter(sys, p) end @@ -445,7 +445,7 @@ end @testset "oop `NonlinearLeastSquaresProblem` with `u0 === nothing`" begin @variables x y - @named sys = NonlinearSystem([0 ~ x - y], [], []; observed = [x ~ 1.0, y ~ 1.0]) + @named sys = System([0 ~ x - y], [], []; observed = [x ~ 1.0, y ~ 1.0]) prob = NonlinearLeastSquaresProblem{false}(complete(sys), nothing) sol = solve(prob) resid = sol.resid diff --git a/test/parameter_dependencies.jl b/test/parameter_dependencies.jl index 6f4f56c140..eb42098fd3 100644 --- a/test/parameter_dependencies.jl +++ b/test/parameter_dependencies.jl @@ -328,7 +328,7 @@ end @parameters p1=1.0 p2=1.0 @variables x(t) eqs = [0 ~ p1 * x * exp(x) + p2] - @mtkbuild sys = NonlinearSystem(eqs; parameter_dependencies = [p2 => 2p1]) + @mtkbuild sys = System(eqs; parameter_dependencies = [p2 => 2p1]) @test isequal(only(parameters(sys)), p1) @test Set(full_parameters(sys)) == Set([p1, p2, Initial(p2), Initial(x)]) prob = NonlinearProblem(sys, [x => 1.0]) @@ -383,7 +383,7 @@ end @variables x(t) y(t) @named sys = System([D(x) ~ y + p2], t; parameter_dependencies = [p2 ~ 2p1]) @test is_parameter(sys, p1) - @named sys = NonlinearSystem([x * y^2 ~ y + p2]; parameter_dependencies = [p2 ~ 2p1]) + @named sys = System([x * y^2 ~ y + p2]; parameter_dependencies = [p2 ~ 2p1]) @test is_parameter(sys, p1) k = ShiftIndex(t) @named sys = DiscreteSystem( diff --git a/test/reduction.jl b/test/reduction.jl index 7e988a2d55..e75b2afdee 100644 --- a/test/reduction.jl +++ b/test/reduction.jl @@ -154,7 +154,7 @@ end eqs = [u1 ~ u2 u3 ~ u1 + u2 + p u3 ~ hypot(u1, u2) * p] -@named sys = NonlinearSystem(eqs, [u1, u2, u3], [p]) +@named sys = System(eqs, [u1, u2, u3], [p]) reducedsys = structural_simplify(sys) @test length(observed(reducedsys)) == 2 @@ -174,7 +174,7 @@ N = 5 @variables xs[1:N] A = reshape(1:(N^2), N, N) eqs = xs ~ A * xs -@named sys′ = NonlinearSystem(eqs, [xs], []) +@named sys′ = System(eqs, [xs], []) sys = structural_simplify(sys′) @test length(equations(sys)) == 3 && length(observed(sys)) == 3 diff --git a/test/scc_nonlinear_problem.jl b/test/scc_nonlinear_problem.jl index f98f588032..3759508c0d 100644 --- a/test/scc_nonlinear_problem.jl +++ b/test/scc_nonlinear_problem.jl @@ -21,7 +21,7 @@ using ModelingToolkit: t_nounits as t, D_nounits as D eqs = Any[0 for _ in 1:8] f!(eqs, u, nothing) eqs = 0 .~ eqs - @named model = NonlinearSystem(eqs) + @named model = System(eqs) @test_throws ["simplified", "required"] SCCNonlinearProblem(model, []) _model = structural_simplify(model; split = false) @test_throws ["not compatible"] SCCNonlinearProblem(_model, []) @@ -85,7 +85,7 @@ end @variables u[1:5] [irreducible = true] @parameters p1[1:6] p2 eqs = 0 .~ collect(nlf(u, (u0, (p1, p2)))) - @mtkbuild sys = NonlinearSystem(eqs, [u], [p1, p2]) + @mtkbuild sys = System(eqs, [u], [p1, p2]) sccprob = SCCNonlinearProblem(sys, [u => u0], [p1 => p[1], p2 => p[2][]]) sccsol = solve(sccprob, SimpleNewtonRaphson(); abstol = 1e-9) @test SciMLBase.successful_retcode(sccsol) @@ -141,7 +141,7 @@ end eqs = 0 .~ eqs subrules = Dict(Symbolics.unwrap(D(y[i])) => ((y[i] - u0[i]) / dt) for i in 1:8) eqs = substitute.(eqs, (subrules,)) - @mtkbuild sys = NonlinearSystem(eqs) + @mtkbuild sys = System(eqs) prob = NonlinearProblem(sys, [y => u0], [t => t0]) sol = solve(prob, NewtonRaphson(); abstol = 1e-12) @@ -159,10 +159,10 @@ end x + y end @register_symbolic func(x, y) - @mtkbuild sys = NonlinearSystem([0 ~ x[1]^3 + x[2]^3 - 5 - 0 ~ sin(x[1] - x[2]) - 0.5 - 0 ~ func(x[1], x[2]) * exp(x[3]) - x[4]^3 - 5 - 0 ~ func(x[1], x[2]) * exp(x[4]) - x[3]^3 - 4]) + @mtkbuild sys = System([0 ~ x[1]^3 + x[2]^3 - 5 + 0 ~ sin(x[1] - x[2]) - 0.5 + 0 ~ func(x[1], x[2]) * exp(x[3]) - x[4]^3 - 5 + 0 ~ func(x[1], x[2]) * exp(x[4]) - x[3]^3 - 4]) sccprob = SCCNonlinearProblem(sys, []) sccsol = solve(sccprob, NewtonRaphson()) @test SciMLBase.successful_retcode(sccsol) @@ -264,7 +264,7 @@ end @testset "Array variables split across SCCs" begin @variables x[1:3] @parameters (f::Function)(..) - @mtkbuild sys = NonlinearSystem([ + @mtkbuild sys = System([ 0 ~ x[1]^2 - 9, x[2] ~ 2x[1], 0 ~ x[3]^2 - x[1]^2 + f(x)]) prob = SCCNonlinearProblem(sys, [x => ones(3)], [f => sum]) sol = solve(prob, NewtonRaphson()) @@ -274,7 +274,7 @@ end @testset "SCCNonlinearProblem retains parameter order" begin @variables x y z @parameters σ β ρ - @mtkbuild fullsys = NonlinearSystem( + @mtkbuild fullsys = System( [0 ~ x^3 * β + y^3 * ρ - σ, 0 ~ x^2 + 2x * y + y^2, 0 ~ z^2 - 4z + 4], [x, y, z], [σ, β, ρ]) @@ -294,7 +294,7 @@ end @variables x y @parameters p[1:2] (f::Function)(..) - @mtkbuild sys = NonlinearSystem([x^2 - p[1]^2 ~ 0, y^2 ~ f(p)]) + @mtkbuild sys = System([x^2 - p[1]^2 ~ 0, y^2 ~ f(p)]) prob = SCCNonlinearProblem(sys, [x => 1.0, y => 1.0], [p => ones(2), f => sum]) @test_nowarn solve(prob, NewtonRaphson()) end diff --git a/test/sciml_problem_inputs.jl b/test/sciml_problem_inputs.jl index deae4b4772..a1b9d1e416 100644 --- a/test/sciml_problem_inputs.jl +++ b/test/sciml_problem_inputs.jl @@ -42,7 +42,7 @@ begin ssys = complete(SDESystem( diff_eqs, noise_eqs, t, [X, Y, Z], [kp, kd, k1, k2]; name = :ssys)) jsys = complete(JumpSystem(jumps, t, [X, Y, Z], [kp, kd, k1, k2]; name = :jsys)) - nsys = complete(NonlinearSystem(alg_eqs; name = :nsys)) + nsys = complete(System(alg_eqs; name = :nsys)) u0_alts = [ # Vectors not providing default values. diff --git a/test/structural_transformation/tearing.jl b/test/structural_transformation/tearing.jl index 24083b3524..5bf88e1dd5 100644 --- a/test/structural_transformation/tearing.jl +++ b/test/structural_transformation/tearing.jl @@ -18,7 +18,7 @@ eqs = [ 0 ~ u4 - hypot(u2, u3), 0 ~ u5 - hypot(u4, u1) ] -@named sys = NonlinearSystem(eqs, [u1, u2, u3, u4, u5], []) +@named sys = System(eqs, [u1, u2, u3, u4, u5], []) state = TearingState(sys) StructuralTransformations.find_solvables!(state) @@ -133,7 +133,7 @@ eqs = [ 0 ~ z + y, 0 ~ x + z ] -@named nlsys = NonlinearSystem(eqs, [x, y, z], []) +@named nlsys = System(eqs, [x, y, z], []) newsys = tearing(nlsys) @test length(equations(newsys)) <= 1 diff --git a/test/structural_transformation/utils.jl b/test/structural_transformation/utils.jl index 7008a31111..af3be46572 100644 --- a/test/structural_transformation/utils.jl +++ b/test/structural_transformation/utils.jl @@ -275,7 +275,7 @@ end end @testset "`NonlinearSystem`" begin @variables x y z - @mtkbuild sys = NonlinearSystem([x^2 ~ 2y^2 + 1, sin(z) ~ y, z^3 + 4z + 1 ~ 0]) + @mtkbuild sys = System([x^2 ~ 2y^2 + 1, sin(z) ~ y, z^3 + 4z + 1 ~ 0]) mapping = map_variables_to_equations(sys) @test mapping[x] == (0 ~ 2y^2 + 1 - x^2) @test mapping[y] == (y ~ sin(z)) diff --git a/test/symbolic_indexing_interface.jl b/test/symbolic_indexing_interface.jl index b99ece927e..9c478565fa 100644 --- a/test/symbolic_indexing_interface.jl +++ b/test/symbolic_indexing_interface.jl @@ -109,7 +109,7 @@ end eqs = [0 ~ σ * (y - x), 0 ~ x * (ρ - z) - y, 0 ~ x * y - β * z] - @named ns = NonlinearSystem(eqs, [x, y, z], [σ, ρ, β]) + @named ns = System(eqs, [x, y, z], [σ, ρ, β]) ns = complete(ns) @test SymbolicIndexingInterface.supports_tuple_observed(ns) @test !is_time_dependent(ns) diff --git a/test/symbolic_parameters.jl b/test/symbolic_parameters.jl index 281002f626..6a6a434ccc 100644 --- a/test/symbolic_parameters.jl +++ b/test/symbolic_parameters.jl @@ -20,7 +20,7 @@ u0 = [ y => σ, # default u0 from default p z => u - 0.1 ] -ns = NonlinearSystem(eqs, [x, y, z], [σ, ρ, β], name = :ns, defaults = [par; u0]) +ns = System(eqs, [x, y, z], [σ, ρ, β], name = :ns, defaults = [par; u0]) ns.y = u * 1.1 resolved = ModelingToolkit.varmap_to_vars(Dict(), parameters(ns), defaults = ModelingToolkit.defaults(ns)) @@ -32,7 +32,7 @@ sol = solve(prob, NewtonRaphson()) @variables a @parameters b -top = NonlinearSystem([0 ~ -a + ns.x + b], [a], [b], systems = [ns], name = :top) +top = System([0 ~ -a + ns.x + b], [a], [b], systems = [ns], name = :top) top.b = ns.σ * 0.5 top.ns.x = u * 0.5 diff --git a/test/units.jl b/test/units.jl index 267b526c4b..2bd67f2745 100644 --- a/test/units.jl +++ b/test/units.jl @@ -108,7 +108,7 @@ System(eqs, t, name = :sys) eqs = [ 0 ~ a * x ] -@named nls = NonlinearSystem(eqs, [x], [a]) +@named nls = System(eqs, [x], [a]) # SDE test w/ noise vector @independent_variables t [unit = u"ms"] @@ -151,12 +151,12 @@ sys_simple = structural_simplify(sys) @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]) +@named sys = System(eqs, [V, L], [t, r]) sys_simple = structural_simplify(sys) eqs = [L ~ v * t, V ~ L^3] -@named sys = NonlinearSystem(eqs, [V, L], [t, r]) +@named sys = System(eqs, [V, L], [t, r]) sys_simple = structural_simplify(sys) #Jump System diff --git a/test/variable_scope.jl b/test/variable_scope.jl index a7cfe0a1af..6d7d20d948 100644 --- a/test/variable_scope.jl +++ b/test/variable_scope.jl @@ -22,11 +22,11 @@ eqs = [0 ~ a 0 ~ b 0 ~ c 0 ~ d] -@named sub4 = NonlinearSystem(eqs, [a, b, c, d], []) -@named sub3 = NonlinearSystem(eqs, [a, b, c, d], []) -@named sub2 = NonlinearSystem([], [], [], systems = [sub3, sub4]) -@named sub1 = NonlinearSystem([], [], [], systems = [sub2]) -@named sys = NonlinearSystem([], [], [], systems = [sub1]) +@named sub4 = System(eqs, [a, b, c, d], []) +@named sub3 = System(eqs, [a, b, c, d], []) +@named sub2 = System([], [], [], systems = [sub3, sub4]) +@named sub1 = System([], [], [], systems = [sub2]) +@named sys = System([], [], [], systems = [sub1]) names = ModelingToolkit.getname.(unknowns(sys)) @test :d in names @@ -35,8 +35,8 @@ names = ModelingToolkit.getname.(unknowns(sys)) @test Symbol("sub1₊sub2₊sub3₊a") in names @test Symbol("sub1₊sub2₊sub4₊a") in names -@named foo = NonlinearSystem(eqs, [a, b, c, d], []) -@named bar = NonlinearSystem(eqs, [a, b, c, d], []) +@named foo = System(eqs, [a, b, c, d], []) +@named bar = System(eqs, [a, b, c, d], []) @test ModelingToolkit.getname(ModelingToolkit.namespace_expr( ModelingToolkit.namespace_expr(b, foo), From a0c13945cea8f14986bc704d726cb1610c48cd47 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Tue, 22 Apr 2025 18:45:51 +0530 Subject: [PATCH 116/235] test: replace `ImplicitDiscreteSystem` with `System` --- src/systems/callbacks.jl | 4 ++-- test/implicit_discrete_system.jl | 10 +++++----- test/namespacing.jl | 4 ++-- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/systems/callbacks.jl b/src/systems/callbacks.jl index 4e24f1b765..891943c6e7 100644 --- a/src/systems/callbacks.jl +++ b/src/systems/callbacks.jl @@ -57,7 +57,7 @@ end struct AffectSystem """The internal implicit discrete system whose equations are solved to obtain values after the affect.""" - system::ImplicitDiscreteSystem + system::AbstractSystem """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.""" @@ -307,7 +307,7 @@ function make_affect(affect::Vector{Equation}; discrete_parameters = Any[], affect = Symbolics.fast_substitute(affect, subs) alg_eqs = Symbolics.fast_substitute(alg_eqs, subs) - @named affectsys = ImplicitDiscreteSystem( + @named affectsys = System( vcat(affect, alg_eqs), iv, collect(union(_dvs, discretes)), collect(union(pre_params, sys_params))) affectsys = structural_simplify(affectsys; fully_determined = nothing) diff --git a/test/implicit_discrete_system.jl b/test/implicit_discrete_system.jl index 932b6c6981..45c89f969e 100644 --- a/test/implicit_discrete_system.jl +++ b/test/implicit_discrete_system.jl @@ -7,7 +7,7 @@ rng = StableRNG(22525) @testset "Correct ImplicitDiscreteFunction" begin @variables x(t) = 1 - @mtkbuild sys = ImplicitDiscreteSystem([x(k) ~ x(k) * x(k - 1) - 3], t) + @mtkbuild sys = System([x(k) ~ x(k) * x(k - 1) - 3], t) tspan = (0, 10) # u[2] - u_next[1] @@ -27,7 +27,7 @@ rng = StableRNG(22525) prob = ImplicitDiscreteProblem(sys, [], tspan) @test prob.u0 == [1.0, 1.0] @variables x(t) - @mtkbuild sys = ImplicitDiscreteSystem([x(k) ~ x(k) * x(k - 1) - 3], t) + @mtkbuild sys = System([x(k) ~ x(k) * x(k - 1) - 3], t) @test_throws ErrorException prob=ImplicitDiscreteProblem(sys, [], tspan) end @@ -35,7 +35,7 @@ end @variables x(t) y(t) eqs = [x(k) ~ x(k - 1) + x(k - 2), x^2 ~ 1 - y^2] - @mtkbuild sys = ImplicitDiscreteSystem(eqs, t) + @mtkbuild sys = System(eqs, t) f = ImplicitDiscreteFunction(sys) function correct_f(u_next, u, p, t) @@ -62,7 +62,7 @@ end eqs = [x(k) ~ x(k - 1) + x(k - 2), y(k) ~ x(k) + x(k - 2) * z(k - 1), x + y + z ~ 2] - @mtkbuild sys = ImplicitDiscreteSystem(eqs, t) + @mtkbuild sys = System(eqs, t) @test length(unknowns(sys)) == length(equations(sys)) == 3 @test occursin("var\"y(t)\"", string(ImplicitDiscreteFunctionExpr(sys))) @@ -70,6 +70,6 @@ end eqs = [z(k) ~ x(k) + sin(x(k)), y(k) ~ x(k - 1) + x(k - 2), z(k) * x(k) ~ 3] - @mtkbuild sys = ImplicitDiscreteSystem(eqs, t) + @mtkbuild sys = System(eqs, t) @test occursin("var\"Shift(t, 1)(z(t))\"", string(ImplicitDiscreteFunctionExpr(sys))) end diff --git a/test/namespacing.jl b/test/namespacing.jl index de33f9e927..7ae4702304 100644 --- a/test/namespacing.jl +++ b/test/namespacing.jl @@ -84,7 +84,7 @@ end @variables x(t) @parameters p k = ShiftIndex(t) - sys = ImplicitDiscreteSystem([x(k) ~ p + x(k - 1) * x(k)], t; name = :inner) + sys = System([x(k) ~ p + x(k - 1) * x(k)], t; name = :inner) @test !iscomplete(sys) @test does_namespacing(sys) @@ -103,7 +103,7 @@ end @test isequal(p, nsys.p) @test !isequal(p, sys.p) - @test_throws ["namespacing", "inner"] ImplicitDiscreteSystem( + @test_throws ["namespacing", "inner"] System( Equation[], t; systems = [nsys], name = :a) end From 36d0c766bce83d9efb529ca1776d42455675f290 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Tue, 22 Apr 2025 18:47:40 +0530 Subject: [PATCH 117/235] test: replace `DiscreteSystem` with `System` --- test/components.jl | 4 ++-- test/discrete_system.jl | 34 +++++++++++++++++----------------- test/namespacing.jl | 4 ++-- test/parameter_dependencies.jl | 2 +- 4 files changed, 22 insertions(+), 22 deletions(-) diff --git a/test/components.jl b/test/components.jl index ce3f7ecba2..19f529bd0a 100644 --- a/test/components.jl +++ b/test/components.jl @@ -344,8 +344,8 @@ end end k = ShiftIndex(t) @testset "DiscreteSystem" begin - @named inner = DiscreteSystem([x(k) ~ x(k - 1) + x(k - 2)], t, [x], []) - @named outer = DiscreteSystem([y(k) ~ y(k - 1) + y(k - 2)], t, [x, y], + @named inner = System([x(k) ~ x(k - 1) + x(k - 2)], t, [x], []) + @named outer = System([y(k) ~ y(k - 1) + y(k - 2)], t, [x, y], []; systems = [inner], metadata = "test") @test ModelingToolkit.get_metadata(outer) == "test" sys = complete(outer) diff --git a/test/discrete_system.jl b/test/discrete_system.jl index 43ee771b2b..0c5412977f 100644 --- a/test/discrete_system.jl +++ b/test/discrete_system.jl @@ -10,7 +10,7 @@ using ModelingToolkit: get_metadata, MTKParameters # Make sure positive shifts error @variables x(t) k = ShiftIndex(t) -@test_throws ErrorException @mtkbuild sys = DiscreteSystem([x(k + 1) ~ x + x(k - 1)], t) +@test_throws ErrorException @mtkbuild sys = System([x(k + 1) ~ x + x(k - 1)], t) @inline function rate_to_proportion(r, t) 1 - exp(-r * t) @@ -30,7 +30,7 @@ eqs = [S ~ S(k - 1) - infection * h, R ~ R(k - 1) + recovery] # System -@named sys = DiscreteSystem(eqs, t, [S, I, R], [c, nsteps, δt, β, γ]) +@named sys = System(eqs, t, [S, I, R], [c, nsteps, δt, β, γ]) syss = structural_simplify(sys) @test syss == syss @@ -72,7 +72,7 @@ eqs2 = [S ~ S(k - 1) - infection2, R ~ R(k - 1) + recovery2, R2 ~ R] -@mtkbuild sys = DiscreteSystem( +@mtkbuild sys = System( eqs2, t, [S, I, R, R2], [c, nsteps, δt, β, γ]; controls = [β, γ], tspan) @test ModelingToolkit.defaults(sys) != Dict() @@ -127,7 +127,7 @@ sol_map2 = solve(prob_map, FunctionMap()); # ] # # System -# @named sys = DiscreteSystem(eqs, t, [x(t), x(t - 1.5), x(t - 3), y(t), y(t - 2), z], []) +# @named sys = System(eqs, t, [x(t), x(t - 1.5), x(t - 3), y(t), y(t - 2), z], []) # eqs2, max_delay = ModelingToolkit.linearize_eqs(sys; return_max_delay = true) @@ -143,7 +143,7 @@ sol_map2 = solve(prob_map, FunctionMap()); # observed variable handling @variables x(t) RHS(t) @parameters τ -@named fol = DiscreteSystem( +@named fol = System( [x ~ (1 - x(k - 1)) / τ], t, [x, RHS], [τ]; observed = [RHS ~ (1 - x) / τ * h]) @test isequal(RHS, @nonamespace fol.RHS) RHS2 = RHS @@ -198,7 +198,7 @@ RHS2 = RHS # Δ(us[i]) ~ dummy_identity(buffer[i], us[i]) # end -# @mtkbuild sys = DiscreteSystem(eqs, t, us, ps; defaults = defs, preface = preface) +# @mtkbuild sys = System(eqs, t, us, ps; defaults = defs, preface = preface) # prob = DiscreteProblem(sys, [], (0.0, 1.0)) # sol = solve(prob, FunctionMap(); dt = dt) # @test c[1] + 1 == length(sol) @@ -206,14 +206,14 @@ RHS2 = RHS @variables x(t) y(t) testdict = Dict([:test => 1]) -@named sys = DiscreteSystem([x(k + 1) ~ 1.0], t, [x], []; metadata = testdict) +@named sys = System([x(k + 1) ~ 1.0], t, [x], []; metadata = testdict) @test get_metadata(sys) == testdict @variables x(t) y(t) u(t) eqs = [u ~ 1 x ~ x(k - 1) + u y ~ x + u] -@mtkbuild de = DiscreteSystem(eqs, t) +@mtkbuild de = System(eqs, t) prob = DiscreteProblem(de, [x(k - 1) => 0.0], (0, 10)) sol = solve(prob, FunctionMap()) @@ -231,7 +231,7 @@ function SampledData(; name, buffer) @variables output(t) time(t) eqs = [time ~ time(k - 1) + 1 output ~ getdata(buffer, time)] - return DiscreteSystem(eqs, t; name) + return System(eqs, t; name) end function System(; name, buffer) @named y_sys = SampledData(; buffer = buffer) @@ -245,7 +245,7 @@ function System(; name, buffer) # y[t] = 0.5 * y[t - 1] + 0.5 * y[t + 1] + y_shk[t] y(k - 1) ~ α * y(k - 2) + (β * y(k) + y_shk(k - 1))] - DiscreteSystem(eqs, t, vars, pars; systems = [y_sys], name = name) + System(eqs, t, vars, pars; systems = [y_sys], name = name) end @test_nowarn @mtkbuild sys = System(; buffer = ones(10)) @@ -253,12 +253,12 @@ end # Ensure discrete systems with algebraic equations throw @variables x(t) y(t) k = ShiftIndex(t) -@named sys = DiscreteSystem([x ~ x^2 + y^2, y ~ x(k - 1) + y(k - 1)], t) +@named sys = System([x ~ x^2 + y^2, y ~ x(k - 1) + y(k - 1)], t) @testset "Passing `nothing` to `u0`" begin @variables x(t) = 1 k = ShiftIndex() - @mtkbuild sys = DiscreteSystem([x(k) ~ x(k - 1) + 1], t) + @mtkbuild sys = System([x(k) ~ x(k - 1) + 1], t) prob = @test_nowarn DiscreteProblem(sys, nothing, (0.0, 1.0)) @test_nowarn solve(prob, FunctionMap()) end @@ -266,7 +266,7 @@ end @testset "Initialization" begin # test that default values apply to the entire history @variables x(t) = 1.0 - @mtkbuild de = DiscreteSystem([x ~ x(k - 1) + x(k - 2)], t) + @mtkbuild de = System([x ~ x(k - 1) + x(k - 2)], t) prob = DiscreteProblem(de, [], (0, 10)) @test prob[x] == 2.0 @test prob[x(k - 1)] == 1.0 @@ -289,14 +289,14 @@ end # Test missing initial throws error @variables x(t) - @mtkbuild de = DiscreteSystem([x ~ x(k - 1) + x(k - 2) * x(k - 3)], t) + @mtkbuild de = System([x ~ x(k - 1) + x(k - 2) * x(k - 3)], t) @test_throws ErrorException prob=DiscreteProblem(de, [x(k - 3) => 2.0], (0, 10)) @test_throws ErrorException prob=DiscreteProblem( de, [x(k - 3) => 2.0, x(k - 1) => 3.0], (0, 10)) # Test non-assigned initials are given default value @variables x(t) = 2.0 - @mtkbuild de = DiscreteSystem([x ~ x(k - 1) + x(k - 2) * x(k - 3)], t) + @mtkbuild de = System([x ~ x(k - 1) + x(k - 2) * x(k - 3)], t) prob = DiscreteProblem(de, [x(k - 3) => 12.0], (0, 10)) @test prob[x] == 26.0 @test prob[x(k - 1)] == 2.0 @@ -306,7 +306,7 @@ end @variables xₜ₋₂(t) zₜ₋₁(t) z(t) eqs = [x ~ x(k - 1) + z(k - 2), z ~ x(k - 2) * x(k - 3) - z(k - 1)^2] - @mtkbuild de = DiscreteSystem(eqs, t) + @mtkbuild de = System(eqs, t) u0 = [x(k - 1) => 3, xₜ₋₂(k - 1) => 4, x(k - 2) => 1, @@ -333,7 +333,7 @@ end y[1](k) ~ y[1](k - 1) + y[1](k - 2), y[2](k) ~ y[2](k - 1) + y[2](k - 2) ] - @mtkbuild sys = DiscreteSystem(eqs, t) + @mtkbuild sys = System(eqs, t) prob = DiscreteProblem(sys, [x(k - 1) => ones(2), x(k - 2) => zeros(2), y[1](k - 1) => 1.0, y[1](k - 2) => 0.0, y[2](k - 1) => 1.0, y[2](k - 2) => 0.0], diff --git a/test/namespacing.jl b/test/namespacing.jl index 7ae4702304..50cbd7e3a3 100644 --- a/test/namespacing.jl +++ b/test/namespacing.jl @@ -57,7 +57,7 @@ end @variables x(t) @parameters p k = ShiftIndex(t) - sys = DiscreteSystem([x(k) ~ p * x(k - 1)], t; name = :inner) + sys = System([x(k) ~ p * x(k - 1)], t; name = :inner) @test !iscomplete(sys) @test does_namespacing(sys) @@ -76,7 +76,7 @@ end @test isequal(p, nsys.p) @test !isequal(p, sys.p) - @test_throws ["namespacing", "inner"] DiscreteSystem( + @test_throws ["namespacing", "inner"] System( Equation[], t; systems = [nsys], name = :a) end diff --git a/test/parameter_dependencies.jl b/test/parameter_dependencies.jl index eb42098fd3..52a064d0be 100644 --- a/test/parameter_dependencies.jl +++ b/test/parameter_dependencies.jl @@ -386,7 +386,7 @@ end @named sys = System([x * y^2 ~ y + p2]; parameter_dependencies = [p2 ~ 2p1]) @test is_parameter(sys, p1) k = ShiftIndex(t) - @named sys = DiscreteSystem( + @named sys = System( [x(k - 1) ~ x(k) + y(k) + p2], t; parameter_dependencies = [p2 ~ 2p1]) @test is_parameter(sys, p1) end From dfd16e50d0975d65cfcd03de8302a3350a2b3da2 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Fri, 25 Apr 2025 15:48:28 +0530 Subject: [PATCH 118/235] fix: ensure equations are `Vector{Equation}` in `generate_initializesystem` --- src/systems/nonlinear/initializesystem.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/systems/nonlinear/initializesystem.jl b/src/systems/nonlinear/initializesystem.jl index 1e7e4dc85c..646ce81112 100644 --- a/src/systems/nonlinear/initializesystem.jl +++ b/src/systems/nonlinear/initializesystem.jl @@ -247,7 +247,7 @@ function generate_initializesystem_timeindependent(sys::AbstractSystem; # so add scalarized versions as well scalarize_varmap!(paramsubs) - eqs_ics = Symbolics.substitute.(eqs_ics, (paramsubs,)) + eqs_ics = Vector{Equation}(Symbolics.substitute.(eqs_ics, (paramsubs,))) for k in keys(defs) defs[k] = substitute(defs[k], paramsubs) end From 9b35a001557d2ce2f3c31688dd30b0e35ac97cb7 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Fri, 25 Apr 2025 15:48:51 +0530 Subject: [PATCH 119/235] test: fix usage of array equations in test --- test/dq_units.jl | 2 +- test/units.jl | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/dq_units.jl b/test/dq_units.jl index c6cada3363..4d8c245e06 100644 --- a/test/dq_units.jl +++ b/test/dq_units.jl @@ -72,7 +72,7 @@ good_eqs = [connect(op, op2)] # Array variables @variables x(t)[1:3] [unit = u"m"] @parameters v[1:3]=[1, 2, 3] [unit = u"m/s"] -eqs = D.(x) .~ v +eqs = [D(x) ~ v] System(eqs, t, name = :sys) # Nonlinear system diff --git a/test/units.jl b/test/units.jl index 2bd67f2745..b7d141f347 100644 --- a/test/units.jl +++ b/test/units.jl @@ -99,7 +99,7 @@ bad_length_eqs = [connect(op, lp)] @parameters v[1:3]=[1, 2, 3] [unit = u"m/s"] @variables x(t)[1:3] [unit = u"m"] D = Differential(t) -eqs = D.(x) .~ v +eqs = [D(x) ~ v] System(eqs, t, name = :sys) # Nonlinear system From 56741c94994a8646a51ea8a17d637beadd158a36 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Fri, 25 Apr 2025 15:49:13 +0530 Subject: [PATCH 120/235] test: ensure equations passed to system are `Vector{Equation}` --- test/variable_scope.jl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/variable_scope.jl b/test/variable_scope.jl index 6d7d20d948..2ecd62ec1f 100644 --- a/test/variable_scope.jl +++ b/test/variable_scope.jl @@ -24,9 +24,9 @@ eqs = [0 ~ a 0 ~ d] @named sub4 = System(eqs, [a, b, c, d], []) @named sub3 = System(eqs, [a, b, c, d], []) -@named sub2 = System([], [], [], systems = [sub3, sub4]) -@named sub1 = System([], [], [], systems = [sub2]) -@named sys = System([], [], [], systems = [sub1]) +@named sub2 = System(Equation[], [], [], systems = [sub3, sub4]) +@named sub1 = System(Equation[], [], [], systems = [sub2]) +@named sys = System(Equation[], [], [], systems = [sub1]) names = ModelingToolkit.getname.(unknowns(sys)) @test :d in names From 5ea5f81d9cecfec78aa693b2e69c75cb3b388375 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Fri, 25 Apr 2025 15:49:31 +0530 Subject: [PATCH 121/235] refactor: remove `process_equations` --- src/utils.jl | 56 ---------------------------------------------------- 1 file changed, 56 deletions(-) diff --git a/src/utils.jl b/src/utils.jl index a12b251384..fe8fb66c53 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -1205,62 +1205,6 @@ function guesses_from_metadata!(guesses, vars) end end -""" - $(TYPEDSIGNATURES) - -Find all the unknowns and parameters from the equations of a System. Return re-ordered -equations, differential variables, all variables, and parameters. -""" -function process_equations(eqs, iv) - if eltype(eqs) <: AbstractVector - eqs = reduce(vcat, eqs) - end - eqs = collect(eqs) - - diffvars = OrderedSet() - allunknowns = OrderedSet() - ps = OrderedSet() - - # NOTE: this assumes that the order of algebraic equations doesn't matter - # reorder equations such that it is in the form of `diffeq, algeeq` - diffeq = Equation[] - algeeq = Equation[] - # initial loop for finding `iv` - if iv === nothing - for eq in eqs - if !(eq.lhs isa Number) # assume eq.lhs is either Differential or Number - iv = iv_from_nested_derivative(eq.lhs) - break - end - end - end - iv = value(iv) - iv === nothing && throw(ArgumentError("Please pass in independent variables.")) - - compressed_eqs = Equation[] # equations that need to be expanded later, like `connect(a, b)` - for eq in eqs - eq.lhs isa Union{Symbolic, Number} || (push!(compressed_eqs, eq); continue) - collect_vars!(allunknowns, ps, eq, iv) - if isdiffeq(eq) - diffvar, _ = var_from_nested_derivative(eq.lhs) - if check_scope_depth(getmetadata(diffvar, SymScope, LocalScope()), 0) - isequal(iv, iv_from_nested_derivative(eq.lhs)) || - throw(ArgumentError("A system of differential equations can only have one independent variable.")) - diffvar in diffvars && - throw(ArgumentError("The differential variable $diffvar is not unique in the system of equations.")) - !has_diffvar_type(diffvar) && - throw(ArgumentError("Differential variable $diffvar has type $(symtype(diffvar)). Differential variables should be of a continuous, non-concrete number type: Real, Complex, AbstractFloat, or Number.")) - push!(diffvars, diffvar) - end - push!(diffeq, eq) - else - push!(algeeq, eq) - end - end - - diffvars, allunknowns, ps, Equation[diffeq; algeeq; compressed_eqs] -end - function has_diffvar_type(diffvar) st = symtype(diffvar) st === Real || eltype(st) === Real || st === Complex || eltype(st) === Complex || From 8a277f46a93d8c2f80991c6ab79c70cbfc738bad Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Fri, 25 Apr 2025 15:49:56 +0530 Subject: [PATCH 122/235] docs: document `collect_var!` --- src/utils.jl | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/utils.jl b/src/utils.jl index fe8fb66c53..15c90c149d 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -604,6 +604,13 @@ function collect_vars!(unknowns, parameters, p::Pair, iv; depth = 0, op = Differ return nothing end +""" + $(TYPEDSIGNATURES) + +Identify whether `var` belongs to the current system using `depth` and scoping information. +Add `var` to `unknowns` or `parameters` appropriately, and search through any expressions +in known metadata of `var` using `collect_vars!`. +""" function collect_var!(unknowns, parameters, var, iv; depth = 0) isequal(var, iv) && return nothing if Symbolics.iswrapped(var) From 7678ffcb974a42939b90eadeb50aaad118c10d77 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Fri, 25 Apr 2025 15:50:34 +0530 Subject: [PATCH 123/235] refactor: change default operator in `collect_vars!` to `Symbolics.Operator` --- src/utils.jl | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/utils.jl b/src/utils.jl index 15c90c149d..ad10ac157c 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -566,7 +566,7 @@ function collect_scoped_vars!(unknowns, parameters, sys, iv; depth = 1, op = Dif end end -function collect_vars!(unknowns, parameters, expr, iv; depth = 0, op = Differential) +function collect_vars!(unknowns, parameters, expr, iv; depth = 0, op = Symbolics.Operator) if issym(expr) collect_var!(unknowns, parameters, expr, iv; depth) else @@ -592,13 +592,14 @@ eqtype_supports_collect_vars(eq::Inequality) = true eqtype_supports_collect_vars(eq::Pair) = true function collect_vars!(unknowns, parameters, eq::Union{Equation, Inequality}, iv; - depth = 0, op = Differential) + depth = 0, op = Symbolics.Operator) collect_vars!(unknowns, parameters, eq.lhs, iv; depth, op) collect_vars!(unknowns, parameters, eq.rhs, iv; depth, op) return nothing end -function collect_vars!(unknowns, parameters, p::Pair, iv; depth = 0, op = Differential) +function collect_vars!( + unknowns, parameters, p::Pair, iv; depth = 0, op = Symbolics.Operator) collect_vars!(unknowns, parameters, p[1], iv; depth, op) collect_vars!(unknowns, parameters, p[2], iv; depth, op) return nothing From 1b7766689eb107fe881b3df76f0db8e536f816d8 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Fri, 25 Apr 2025 15:50:58 +0530 Subject: [PATCH 124/235] feat: add `validate_operator` --- src/discretedomain.jl | 21 +++++++++++++++ src/systems/callbacks.jl | 3 +++ src/utils.jl | 55 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 79 insertions(+) diff --git a/src/discretedomain.jl b/src/discretedomain.jl index 2d5410ee79..1eeec4c014 100644 --- a/src/discretedomain.jl +++ b/src/discretedomain.jl @@ -7,6 +7,8 @@ SymbolicUtils.promote_symtype(::Type{<:SampleTime}, t...) = Real Base.nameof(::SampleTime) = :SampleTime SymbolicUtils.isbinop(::SampleTime) = false +function validate_operator(op::SampleTime, args, iv; context = nothing) end + # Shift """ @@ -72,6 +74,13 @@ Base.hash(D::Shift, u::UInt) = hash(D.steps, hash(D.t, xor(u, 0x055640d6d952f101 Base.:^(D::Shift, n::Integer) = Shift(D.t, D.steps * n) Base.literal_pow(f::typeof(^), D::Shift, ::Val{n}) where {n} = Shift(D.t, D.steps * n) +function validate_operator(op::Shift, args, iv; context = nothing) + isequal(op.t, iv) || throw(OperatorIndepvarMismatchError(op, iv, context)) + op.steps <= 0 || error(""" + Only non-positive shifts are allowed. Found shift of $(op.steps) in $context. + """) +end + hasshift(eq::Equation) = hasshift(eq.lhs) || hasshift(eq.rhs) """ @@ -132,6 +141,13 @@ Base.show(io::IO, D::Sample) = print(io, "Sample(", D.clock, ")") Base.:(==)(D1::Sample, D2::Sample) = isequal(D1.clock, D2.clock) Base.hash(D::Sample, u::UInt) = hash(D.clock, xor(u, 0x055640d6d952f101)) +function validate_operator(op::Sample, args, iv; context = nothing) + arg = unwrap(only(args)) + if !is_variable_floatingpoint(arg) + throw(ContinuousOperatorDiscreteArgumentError(op, arg, context)) + end +end + """ hassample(O) @@ -160,6 +176,11 @@ SymbolicUtils.isbinop(::Hold) = false Hold(x) = Hold()(x) +function validate_operator(op::Hold, args, iv; context = nothing) + # TODO: maybe validate `VariableTimeDomain`? + return nothing +end + """ hashold(O) diff --git a/src/systems/callbacks.jl b/src/systems/callbacks.jl index 891943c6e7..d0bd18a65a 100644 --- a/src/systems/callbacks.jl +++ b/src/systems/callbacks.jl @@ -155,6 +155,9 @@ end haspre(eq::Equation) = haspre(eq.lhs) || haspre(eq.rhs) haspre(O) = recursive_hasoperator(Pre, O) +function validate_operator(op::Pre, args, iv; context = nothing) +end + ############################### ###### Continuous events ###### ############################### diff --git a/src/utils.jl b/src/utils.jl index ad10ac157c..293a3fdd69 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -566,6 +566,61 @@ function collect_scoped_vars!(unknowns, parameters, sys, iv; depth = 1, op = Dif end end +""" + $(TYPEDSIGNATURES) + +Check whether the usage of operator `op` is valid in a system with independent variable +`iv`. If the system is time-independent, `iv` should be `nothing`. Throw an appropriate +error if `op` is invalid. `args` are the arguments to `op`. + +# Keyword arguments + +- `context`: The place where the operator occurs in the system/expression, or any other + relevant information. Useful for providing extra information in the error message. +""" +function validate_operator(op, args, iv; context = nothing) + error("`$validate_operator` is not implemented for operator `$op` in $context.") +end + +function validate_operator(op::Differential, args, iv; context = nothing) + isequal(op.x, iv) || throw(OperatorIndepvarMismatchError(op, iv, context)) + arg = unwrap(only(args)) + if !is_variable_floatingpoint(arg) + throw(ContinuousOperatorDiscreteArgumentError(op, arg, context)) + end +end + +struct ContinuousOperatorDiscreteArgumentError <: Exception + op::Any + arg::Any + context::Any +end + +function Base.showerror(io::IO, err::ContinuousOperatorDiscreteArgumentError) + print(io, """ + Operator $(err.op) expects continuous arguments, with a `symtype` such as `Number`, + `Real`, `Complex` or a subtype of `AbstractFloat`. Found $(err.arg) with a symtype of + $(symtype(err.arg))$(err.context === nothing ? "." : "in $(err.context).") + """) +end + +struct OperatorIndepvarMismatchError <: Exception + op::Any + iv::Any + context::Any +end + +function Base.showerror(io::IO, err::OperatorIndepvarMismatchError) + print(io, """ + Encountered operator `$(err.op)` which has different independent variable than the \ + one used in the system `$(err.iv)`. + """) + if err.context !== nothing + println(io) + print(io, "Context:\n$(err.context)") + end +end + function collect_vars!(unknowns, parameters, expr, iv; depth = 0, op = Symbolics.Operator) if issym(expr) collect_var!(unknowns, parameters, expr, iv; depth) From a2b712aae0e1a6f81c66540e41af2b1d0b60e43b Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Fri, 25 Apr 2025 15:51:29 +0530 Subject: [PATCH 125/235] refactor: document `collect_vars!` and use `validate_operator` --- src/utils.jl | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/src/utils.jl b/src/utils.jl index 293a3fdd69..6d0140e7e9 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -621,16 +621,30 @@ function Base.showerror(io::IO, err::OperatorIndepvarMismatchError) end end +""" + $(TYPEDSIGNATURES) + +Search through `expr` for all symbolic variables present in it. Populate `dvs` with +unknowns and `ps` with parameters present. `iv` should be the independent variable of the +system or `nothing` for time-independent systems. Expressions where the operator `isa op` +go through `validate_operator`. + +`depth` is a keyword argument which indicates how many levels down `expr` is from the root +of the system hierarchy. This is used to resolve scoping operators. The scope of a variable +can be checked using `check_scope_depth`. + +This function should return `nothing`. +""" function collect_vars!(unknowns, parameters, expr, iv; depth = 0, op = Symbolics.Operator) if issym(expr) - collect_var!(unknowns, parameters, expr, iv; depth) - else - for var in vars(expr; op) - if iscall(var) && operation(var) isa Differential - var, _ = var_from_nested_derivative(var) - end - collect_var!(unknowns, parameters, var, iv; depth) + return collect_var!(unknowns, parameters, expr, iv; depth) + end + for var in vars(expr; op) + while iscall(var) && operation(var) isa op + validate_operator(operation(var), arguments(var), iv; context = expr) + var = arguments(var)[1] end + collect_var!(unknowns, parameters, var, iv; depth) end return nothing end From d5ee2877f94ef696b3b63f2189d0a33a50094208 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Fri, 25 Apr 2025 17:22:07 +0530 Subject: [PATCH 126/235] feat: add `is_floatingpoint_symtype` --- src/utils.jl | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/utils.jl b/src/utils.jl index 6d0140e7e9..9996635235 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -1113,6 +1113,16 @@ function is_variable_floatingpoint(sym) T = symtype(sym) return T == Real || T <: AbstractFloat || T <: AbstractArray{Real} || T <: AbstractArray{<:AbstractFloat} + +""" + $(TYPEDSIGNATURES) + +Check if `T` is an appropriate symtype for a symbolic variable representing a floating +point number or array of such numbers. +""" +function is_floatingpoint_symtype(T::Type) + return T == Real || T == Number || T <: AbstractFloat || + T <: AbstractArray && is_floatingpoint_symtype(eltype(T)) end """ From f7dfb961c34ebb10c7a0e143bb511a8965058cd5 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Fri, 25 Apr 2025 17:22:22 +0530 Subject: [PATCH 127/235] refactor: use `is_floatingpoint_symtype` in `is_variable_floatingpoint` --- src/utils.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/utils.jl b/src/utils.jl index 9996635235..804976ae28 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -1111,8 +1111,8 @@ Check if `sym` represents a symbolic floating point number or array of such numb function is_variable_floatingpoint(sym) sym = unwrap(sym) T = symtype(sym) - return T == Real || T <: AbstractFloat || T <: AbstractArray{Real} || - T <: AbstractArray{<:AbstractFloat} + is_floatingpoint_symtype(T) +end """ $(TYPEDSIGNATURES) From b7258df36fd995ac2821a04e4c4099657839c8fa Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Fri, 25 Apr 2025 19:15:14 +0530 Subject: [PATCH 128/235] test: don't pass dvs/ps to `ODEFunction` --- test/precompile_test/ODEPrecompileTest.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/precompile_test/ODEPrecompileTest.jl b/test/precompile_test/ODEPrecompileTest.jl index 911f45152e..2111f7ba64 100644 --- a/test/precompile_test/ODEPrecompileTest.jl +++ b/test/precompile_test/ODEPrecompileTest.jl @@ -15,7 +15,7 @@ function system(; kwargs...) @named de = System(eqs, t) de = complete(de) - return ODEFunction(de, [x, y, z], [σ, ρ, β]; kwargs...) + return ODEFunction(de; kwargs...) end # Build an ODEFunction as part of the module's precompilation. These cases From 43f32bd11db3316730be75931ca1f89eb4eecca9 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Fri, 25 Apr 2025 19:15:38 +0530 Subject: [PATCH 129/235] test: fix shadowing of `System` in tests --- test/domain_connectors.jl | 4 ++-- test/state_selection.jl | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/test/domain_connectors.jl b/test/domain_connectors.jl index 03d6ff264a..e35cee8f30 100644 --- a/test/domain_connectors.jl +++ b/test/domain_connectors.jl @@ -123,7 +123,7 @@ function Valve2Port(; p_s_int, p_r_int, p_int, name) System(eqs, t, vars, pars; name, systems) end -function System(; name) +function HydraulicSystem(; name) vars = [] pars = [] systems = @named begin @@ -142,7 +142,7 @@ function System(; name) return System(eqs, t, vars, pars; systems, name) end -@named odesys = System() +@named odesys = HydraulicSystem() esys = ModelingToolkit.expand_connections(odesys) @test length(equations(esys)) == length(unknowns(esys)) diff --git a/test/state_selection.jl b/test/state_selection.jl index b8bec5d7b7..6db8e8c5a0 100644 --- a/test/state_selection.jl +++ b/test/state_selection.jl @@ -100,7 +100,7 @@ let D(v) * rho * L ~ (fluid_port_a.p - fluid_port_b.p - dp_z)] compose(System(eqs, t, sts, ps; name = name), [fluid_port_a, fluid_port_b]) end - function System(; name, L = 10.0) + function HydraulicSystem(; name, L = 10.0) @named compensator = Compensator() @named source = Source() @named substation = Substation() @@ -116,7 +116,7 @@ let compose(System(eqs, t, [], ps; name = name), subs) end - @named system = System(L = 10) + @named system = HydraulicSystem(L = 10) @unpack supply_pipe, return_pipe = system sys = structural_simplify(system) u0 = [ From 1aaa64b8c47e69ca9882ee3801cddf56c4cb808b Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Fri, 25 Apr 2025 19:15:56 +0530 Subject: [PATCH 130/235] test: pass `u0map` and `tspan` to `ODEProblem` --- test/parameter_dependencies.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/parameter_dependencies.jl b/test/parameter_dependencies.jl index 52a064d0be..fc3960534c 100644 --- a/test/parameter_dependencies.jl +++ b/test/parameter_dependencies.jl @@ -59,7 +59,7 @@ end t; parameter_dependencies = [p2 => 2p1] ) - prob = ODEProblem(complete(sys)) + prob = ODEProblem(complete(sys), [], (0.0, 1.0)) setp1! = setp(prob, p1) get_p1 = getp(prob, p1) get_p2 = getp(prob, p2) @@ -113,7 +113,7 @@ end t; parameter_dependencies = [p2 => 2p1] ) - prob = ODEProblem(complete(sys)) + prob = ODEProblem(complete(sys), [], (0.0, 1.0)) get_dep = getu(prob, 2p1) @test get_dep(prob) == [2.0, 4.0] end From 268947dad1882e572fa3567df9d9470d6a817a96 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Fri, 25 Apr 2025 19:16:56 +0530 Subject: [PATCH 131/235] test: create `JumpProblem` directly --- test/jumpsystem.jl | 74 +++++++++++++++++----------------- test/parameter_dependencies.jl | 8 ++-- 2 files changed, 41 insertions(+), 41 deletions(-) diff --git a/test/jumpsystem.jl b/test/jumpsystem.jl index c5dbe1c56c..d80ee40666 100644 --- a/test/jumpsystem.jl +++ b/test/jumpsystem.jl @@ -69,8 +69,9 @@ p = (0.1 / 1000, 0.01); tspan = (0.0, 250.0); u₀map = [S => 999, I => 1, R => 0] parammap = [β => 0.1 / 1000, γ => 0.01] -dprob = DiscreteProblem(js2, u₀map, tspan, parammap) -jprob = JumpProblem(js2, dprob, Direct(); save_positions = (false, false), rng) +jprob = JumpProblem(js2, u₀map, tspan, parammap; aggregator = Direct(), + save_positions = (false, false), rng) +@test jprob.prob isa DiscreteProblem Nsims = 30000 function getmean(jprob, Nsims; use_stepper = true) m = 0.0 @@ -83,7 +84,7 @@ end m = getmean(jprob, Nsims) # test auto-alg selection works -jprobb = JumpProblem(js2, dprob; save_positions = (false, false), rng) +jprobb = JumpProblem(js2, u₀map, tspan, parammap; save_positions = (false, false), rng) mb = getmean(jprobb, Nsims; use_stepper = false) @test abs(m - mb) / m < 0.01 @@ -91,13 +92,15 @@ mb = getmean(jprobb, Nsims; use_stepper = false) obs = [S2 ~ 2 * S] @named js2b = JumpSystem([j₁, j₃], t, [S, I, R], [β, γ], observed = obs) js2b = complete(js2b) -dprob = DiscreteProblem(js2b, u₀map, tspan, parammap) -jprob = JumpProblem(js2b, dprob, Direct(); save_positions = (false, false), rng) +jprob = JumpProblem(js2b, u₀map, tspan, parammap; aggregator = Direct(), + save_positions = (false, false), rng) +@test jprob.prob isa DiscreteProblem sol = solve(jprob, SSAStepper(); saveat = tspan[2] / 10) @test all(2 .* sol[S] .== sol[S2]) # test save_positions is working -jprob = JumpProblem(js2, dprob, Direct(); save_positions = (false, false), rng) +jprob = JumpProblem(js2, u₀map, tspan, parammap; aggregator = Direct(), + save_positions = (false, false), rng) sol = solve(jprob, SSAStepper(); saveat = 1.0) @test all((sol.t) .== collect(0.0:tspan[2])) @@ -143,18 +146,20 @@ maj1 = MassActionJump(2 * β / 2, [S => 1, I => 1], [S => -1, I => 1]) maj2 = MassActionJump(γ, [I => 1], [I => -1, R => 1]) @named js3 = JumpSystem([maj1, maj2], t, [S, I, R], [β, γ]) js3 = complete(js3) -dprob = DiscreteProblem(js3, u₀map, tspan, parammap) -jprob = JumpProblem(js3, dprob, Direct(); rng) +jprob = JumpProblem(js3, u₀map, tspan, parammap; aggregator = Direct(), rng) +@test jprob.prob isa DiscreteProblem m3 = getmean(jprob, Nsims) @test abs(m - m3) / m < 0.01 # maj jump test with various dep graphs @named js3b = JumpSystem([maj1, maj2], t, [S, I, R], [β, γ]) js3b = complete(js3b) -jprobb = JumpProblem(js3b, dprob, NRM(); rng) +jprobb = JumpProblem(js3b, u₀map, tspan, parammap; aggregator = NRM(), rng) +@test jprobb.prob isa DiscreteProblem m4 = getmean(jprobb, Nsims) @test abs(m - m4) / m < 0.01 -jprobc = JumpProblem(js3b, dprob, RSSA(); rng) +jprobc = JumpProblem(js3b, u₀map, tspan, parammap; aggregator = RSSA(), rng) +@test jprobc.prob isa DiscreteProblem m4 = getmean(jprobc, Nsims) @test abs(m - m4) / m < 0.01 @@ -163,8 +168,9 @@ maj1 = MassActionJump(2.0, [0 => 1], [S => 1]) maj2 = MassActionJump(γ, [S => 1], [S => -1]) @named js4 = JumpSystem([maj1, maj2], t, [S], [β, γ]) js4 = complete(js4) -dprob = DiscreteProblem(js4, [S => 999], (0, 1000.0), [β => 100.0, γ => 0.01]) -jprob = JumpProblem(js4, dprob, Direct(); rng) +jprob = JumpProblem( + js4, [S => 999], (0, 1000.0), [β => 100.0, γ => 0.01]; aggregator = Direct(), rng) +@test jprob.prob isa DiscreteProblem m4 = getmean(jprob, Nsims) @test abs(m4 - 2.0 / 0.01) * 0.01 / 2.0 < 0.01 @@ -173,8 +179,9 @@ maj1 = MassActionJump(2.0, [0 => 1], [S => 1]) maj2 = MassActionJump(γ, [S => 2], [S => -1]) @named js4 = JumpSystem([maj1, maj2], t, [S], [β, γ]) js4 = complete(js4) -dprob = DiscreteProblem(js4, [S => 999], (0, 1000.0), [β => 100.0, γ => 0.01]) -jprob = JumpProblem(js4, dprob, Direct(); rng) +jprob = JumpProblem( + js4, [S => 999], (0, 1000.0), [β => 100.0, γ => 0.01]; aggregator = Direct(), rng) +@test jprob.prob isa DiscreteProblem sol = solve(jprob, SSAStepper()); # issue #819 @@ -196,8 +203,9 @@ let p = [k1 => 2.0, k2 => 0.0, k3 => 0.5] u₀ = [A => 100, B => 0] tspan = (0.0, 2000.0) - dprob = DiscreteProblem(js5, u₀, tspan, p) - jprob = JumpProblem(js5, dprob, Direct(); save_positions = (false, false), rng) + jprob = JumpProblem( + js5, u₀, tspan, p; aggregator = Direct(), save_positions = (false, false), rng) + @test jprob.prob isa DiscreteProblem @test all(jprob.massaction_jump.scaled_rates .== [1.0, 0.0]) pcondit(u, t, integrator) = t == 1000.0 @@ -260,15 +268,10 @@ u0 = [X => 10] tspan = (0.0, 1.0) ps = [k => 1.0] -dp1 = DiscreteProblem(js1, u0, tspan, ps) -dp2 = DiscreteProblem(js2, u0, tspan) -dp3 = DiscreteProblem(js3, u0, tspan, ps) -dp4 = DiscreteProblem(js4, u0, tspan) - -@test_nowarn jp1 = JumpProblem(js1, dp1, Direct()) -@test_nowarn jp2 = JumpProblem(js2, dp2, Direct()) -@test_nowarn jp3 = JumpProblem(js3, dp3, Direct()) -@test_nowarn jp4 = JumpProblem(js4, dp4, Direct()) +@test_nowarn jp1 = JumpProblem(js1, u0, tspan, ps; aggregator = Direct()) +@test_nowarn jp2 = JumpProblem(js2, u0, tspan; aggregator = Direct()) +@test_nowarn jp3 = JumpProblem(js3, u0, tspan, ps; aggregator = Direct()) +@test_nowarn jp4 = JumpProblem(js4, u0, tspan; aggregator = Direct()) # Ensure `structural_simplify` (and `@mtkbuild`) works on JumpSystem (by doing nothing) # Issue#2558 @@ -293,8 +296,7 @@ let for (N, algtype) in zip(Nv, algtypes) @named jsys = JumpSystem([deepcopy(j1) for _ in 1:N], t, [X], [k]) jsys = complete(jsys) - dprob = DiscreteProblem(jsys, [X => 10], (0.0, 10.0), [k => 1]) - jprob = JumpProblem(jsys, dprob) + jprob = JumpProblem(jsys, [X => 10], (0.0, 10.0), [k => 1]) @test jprob.aggregator isa algtype end end @@ -307,8 +309,9 @@ let @parameters k 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) + jprob = JumpProblem( + js, [A => 0, C => 0], (0.0, 10.0), [k => 1.0]; aggregtor = Direct(), rng) + @test jprob.prob isa ODEProblem sol = solve(jprob, Tsit5()) # test observed and symbolic indexing work @@ -440,8 +443,7 @@ let k2val = 20.0 p = [k1 => k1val, k2 => k2val] tspan = (0.0, 10.0) - oprob = ODEProblem(jsys, u0, tspan, p) - jprob = JumpProblem(jsys, oprob; rng, save_positions = (false, false)) + jprob = JumpProblem(jsys, u0, tspan, p; rng, save_positions = (false, false)) times = range(0.0, tspan[2], length = 100) Nsims = 4000 @@ -480,8 +482,7 @@ let u0map = [X => p.X₀, Y => p.Y₀] pmap = [α => p.α, β => p.β] tspan = (0.0, 20.0) - oprob = ODEProblem(jsys, u0map, tspan, pmap) - jprob = JumpProblem(jsys, oprob; rng, save_positions = (false, false)) + jprob = JumpProblem(jsys, u0, tspan, pmap; rng, save_positions = (false, false)) times = range(0.0, tspan[2], length = 100) Nsims = 4000 Xv = zeros(length(times)) @@ -519,8 +520,7 @@ let continuous_events = cevents) jsys = complete(jsys) tspan = (0.0, 200.0) - oprob = ODEProblem(jsys, u0map, tspan, pmap) - jprob = JumpProblem(jsys, oprob; rng, save_positions = (false, false)) + jprob = JumpProblem(jsys, u0, tspan, pmap; rng, save_positions = (false, false)) Xsamp = 0.0 Nsims = 4000 for n in 1:Nsims @@ -545,8 +545,8 @@ end # Works. @mtkbuild js = JumpSystem([j1, j2], t, [X], [p, d]) - dprob = DiscreteProblem(js, [X => 15], (0.0, 10.0), [p => 2.0, d => 0.5]) - jprob = JumpProblem(js, dprob, Direct()) + jprob = JumpProblem( + js, [X => 15], (0.0, 10.0), [p => 2.0, d => 0.5]; aggregator = Direct()) sol = solve(jprob, SSAStepper()) @test eltype(sol[X]) === Int64 end diff --git a/test/parameter_dependencies.jl b/test/parameter_dependencies.jl index fc3960534c..71ca78a101 100644 --- a/test/parameter_dependencies.jl +++ b/test/parameter_dependencies.jl @@ -303,8 +303,8 @@ end tspan = (0.0, 250.0) u₀map = [S => 999, I => 1, R => 0] parammap = [γ => 0.01] - dprob = DiscreteProblem(js2, u₀map, tspan, parammap) - jprob = JumpProblem(js2, dprob, Direct(), save_positions = (false, false), rng = rng) + jprob = JumpProblem(js2, u₀map, tspan, parammap; aggregator = Direct(), + save_positions = (false, false), rng = rng) @test jprob.ps[γ] == 0.01 @test jprob.ps[β] == 0.0001 @test_nowarn solve(jprob, SSAStepper()) @@ -314,8 +314,8 @@ end 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) + jprob = JumpProblem(js2, u₀map, tspan, parammap; aggregator = Direct(), + save_positions = (false, false), rng = rng) integ = init(jprob, SSAStepper()) @test integ.ps[γ] == 0.01 @test integ.ps[β] == 0.0001 From ddeeee17b5fe6da85c6a6f3051e5c1695912388e Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Fri, 25 Apr 2025 19:17:09 +0530 Subject: [PATCH 132/235] test: pass `Vector{Equation}` to `System` --- test/structural_transformation/tearing.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/structural_transformation/tearing.jl b/test/structural_transformation/tearing.jl index 5bf88e1dd5..0cbabbfa3b 100644 --- a/test/structural_transformation/tearing.jl +++ b/test/structural_transformation/tearing.jl @@ -183,7 +183,7 @@ end m = 1.0 @named mass = Translational_Mass(m = m) -ms_eqs = [] +ms_eqs = Equation[] @named _ms_model = System(ms_eqs, t) @named ms_model = compose(_ms_model, From 6b5dab8a704dc1d30b4174d3edb286926d7bdfd7 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Sun, 27 Apr 2025 13:38:32 +0530 Subject: [PATCH 133/235] refactor: remove `build_torn_function`, `tearing_assignments` --- .../StructuralTransformations.jl | 4 +- src/structural_transformation/codegen.jl | 139 ------------------ .../symbolics_tearing.jl | 13 -- 3 files changed, 2 insertions(+), 154 deletions(-) diff --git a/src/structural_transformation/StructuralTransformations.jl b/src/structural_transformation/StructuralTransformations.jl index 16d3a75464..06d8e440cc 100644 --- a/src/structural_transformation/StructuralTransformations.jl +++ b/src/structural_transformation/StructuralTransformations.jl @@ -57,11 +57,11 @@ using DocStringExtensions export tearing, partial_state_selection, dae_index_lowering, check_consistency export dummy_derivative -export build_torn_function, build_observed_function, ODAEProblem +export build_observed_function, ODAEProblem export sorted_incidence_matrix, pantelides!, pantelides_reassemble, tearing_reassemble, find_solvables!, linear_subsys_adjmat! -export tearing_assignments, tearing_substitution +export tearing_substitution export torn_system_jacobian_sparsity export full_equations export but_ordered_incidence, lowest_order_variable_mask, highest_order_variable_mask diff --git a/src/structural_transformation/codegen.jl b/src/structural_transformation/codegen.jl index d2ca5b8748..144e19aa31 100644 --- a/src/structural_transformation/codegen.jl +++ b/src/structural_transformation/codegen.jl @@ -226,145 +226,6 @@ function gen_nlsolve!(is_not_prepended_assignment, eqs, vars, u0map::AbstractDic nlsolve_expr end -function build_torn_function(sys; - expression = false, - jacobian_sparsity = true, - checkbounds = false, - max_inlining_size = nothing, - kw...) - max_inlining_size = something(max_inlining_size, MAX_INLINE_NLSOLVE_SIZE) - rhss = [] - eqs = equations(sys) - eqs_idxs = Int[] - for (i, eq) in enumerate(eqs) - isdiffeq(eq) || continue - push!(eqs_idxs, i) - push!(rhss, eq.rhs) - end - - state = get_or_construct_tearing_state(sys) - fullvars = state.fullvars - var_eq_matching, var_sccs = algebraic_variables_scc(state) - condensed_graph = MatchedCondensationGraph( - DiCMOBiGraph{true}(complete(state.structure.graph), - complete(var_eq_matching)), - var_sccs) - toporder = topological_sort_by_dfs(condensed_graph) - var_sccs = var_sccs[toporder] - - unknowns_idxs = collect(diffvars_range(state.structure)) - mass_matrix_diag = ones(length(unknowns_idxs)) - - assignments, deps, sol_states = tearing_assignments(sys) - invdeps = map(_ -> BitSet(), deps) - for (i, d) in enumerate(deps) - for a in d - push!(invdeps[a], i) - end - end - var2assignment = Dict{Any, Int}(eq.lhs => i for (i, eq) in enumerate(assignments)) - is_not_prepended_assignment = trues(length(assignments)) - - torn_expr = Assignment[] - - defs = defaults(sys) - nlsolve_scc_idxs = Int[] - - needs_extending = false - @views for (i, scc) in enumerate(var_sccs) - torn_vars_idxs = Int[var for var in scc if var_eq_matching[var] !== unassigned] - torn_eqs_idxs = [var_eq_matching[var] for var in torn_vars_idxs] - isempty(torn_eqs_idxs) && continue - if length(torn_eqs_idxs) <= max_inlining_size - nlsolve_expr = gen_nlsolve!(is_not_prepended_assignment, eqs[torn_eqs_idxs], - fullvars[torn_vars_idxs], defs, assignments, - (deps, invdeps), var2assignment, - checkbounds = checkbounds) - append!(torn_expr, nlsolve_expr) - push!(nlsolve_scc_idxs, i) - else - needs_extending = true - append!(eqs_idxs, torn_eqs_idxs) - append!(rhss, map(x -> x.rhs, eqs[torn_eqs_idxs])) - append!(unknowns_idxs, torn_vars_idxs) - append!(mass_matrix_diag, zeros(length(torn_eqs_idxs))) - end - end - sort!(unknowns_idxs) - - mass_matrix = needs_extending ? Diagonal(mass_matrix_diag) : I - - out = Sym{Any}(gensym("out")) - funbody = SetArray(!checkbounds, - out, - rhss) - - unknown_vars = Any[fullvars[i] for i in unknowns_idxs] - @set! sys.solved_unknowns = unknown_vars - - pre = get_postprocess_fbody(sys) - cpre = get_preprocess_constants(rhss) - pre2 = x -> pre(cpre(x)) - - expr = SymbolicUtils.Code.toexpr( - Func( - [out - DestructuredArgs(unknown_vars, - inbounds = !checkbounds) - DestructuredArgs(parameters(sys), - inbounds = !checkbounds) - independent_variables(sys)], - [], - pre2(Let([torn_expr; - assignments[is_not_prepended_assignment]], - funbody, - false))), - sol_states) - if expression - expr, unknown_vars - else - observedfun = let state = state, - dict = Dict(), - is_solver_unknown_idxs = insorted.(1:length(fullvars), (unknowns_idxs,)), - assignments = assignments, - deps = (deps, invdeps), - sol_states = sol_states, - var2assignment = var2assignment - - function generated_observed(obsvar, args...) - obs = get!(dict, value(obsvar)) do - build_observed_function(state, obsvar, var_eq_matching, var_sccs, - is_solver_unknown_idxs, assignments, deps, - sol_states, var2assignment, - checkbounds = checkbounds) - end - if args === () - let obs = obs - (u, p, t) -> obs(u, p, t) - end - else - obs(args...) - end - end - end - - ODEFunction{true, SciMLBase.AutoSpecialize}( - drop_expr(@RuntimeGeneratedFunction(expr)), - sparsity = jacobian_sparsity ? - torn_system_with_nlsolve_jacobian_sparsity(state, - var_eq_matching, - var_sccs, - nlsolve_scc_idxs, - eqs_idxs, - unknowns_idxs) : - nothing, - observed = observedfun, - mass_matrix = mass_matrix, - sys = sys), - unknown_vars - end -end - """ find_solve_sequence(sccs, vars) diff --git a/src/structural_transformation/symbolics_tearing.jl b/src/structural_transformation/symbolics_tearing.jl index 04c88d1d52..65a9982c06 100644 --- a/src/structural_transformation/symbolics_tearing.jl +++ b/src/structural_transformation/symbolics_tearing.jl @@ -154,19 +154,6 @@ function tearing_substitution(sys::AbstractSystem; kwargs...) @set! sys.schedule = nothing end -function tearing_assignments(sys::AbstractSystem) - if empty_substitutions(sys) - assignments = [] - deps = Int[] - sol_states = Code.LazyState() - else - @unpack subs, deps = get_substitutions(sys) - assignments = [Assignment(eq.lhs, eq.rhs) for eq in subs] - sol_states = Code.NameState(Dict(eq.lhs => Symbol(eq.lhs) for eq in subs)) - end - return assignments, deps, sol_states -end - function solve_equation(eq, var, simplify) rhs = value(symbolic_linear_solve(eq, var; simplify = simplify, check = false)) occursin(var, rhs) && throw(EquationSolveErrors(eq, var, rhs)) From 14d8d40da8d1cbdef16b6a93cd3ef89251748460 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Sun, 27 Apr 2025 13:40:26 +0530 Subject: [PATCH 134/235] refactor: remove `get_substitutions`, `has_substitutions` field getters --- src/systems/abstractsystem.jl | 1 - 1 file changed, 1 deletion(-) diff --git a/src/systems/abstractsystem.jl b/src/systems/abstractsystem.jl index 099d569d2f..d4a57cddc4 100644 --- a/src/systems/abstractsystem.jl +++ b/src/systems/abstractsystem.jl @@ -895,7 +895,6 @@ for prop in [:eqs :initialization_eqs :schedule :tearing_state - :substitutions :metadata :gui_metadata :is_initializesystem From a7f7ae62f99952ca857bc0aa44b113d07b288214 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Sun, 27 Apr 2025 13:43:43 +0530 Subject: [PATCH 135/235] refactor: implement `empty_substitutions` and `get_substitutions` using `observed` --- src/utils.jl | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/utils.jl b/src/utils.jl index 804976ae28..2c50f2be47 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -832,12 +832,6 @@ end isarray(x) = x isa AbstractArray || x isa Symbolics.Arr -function empty_substitutions(sys) - has_substitutions(sys) || return true - subs = get_substitutions(sys) - isnothing(subs) || isempty(subs.deps) -end - function get_cmap(sys, exprs = nothing) #Inject substitutions for constants => values buffer = [] @@ -880,6 +874,12 @@ function get_substitutions_and_solved_unknowns(sys, exprs = nothing; no_postproc end end return pre, sol_states +function empty_substitutions(sys) + isempty(observed(sys)) +end + +function get_substitutions(sys) + Dict([eq.lhs => eq.rhs for eq in observed(sys)]) end function mergedefaults(defaults, varmap, vars) From c6a15013eca5554e4137a064a166420ac8c87e79 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Sun, 27 Apr 2025 13:43:51 +0530 Subject: [PATCH 136/235] refactor: remove `get_substitutions_and_solved_unknowns` --- src/utils.jl | 23 ----------------------- 1 file changed, 23 deletions(-) diff --git a/src/utils.jl b/src/utils.jl index 2c50f2be47..cf6855999c 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -851,29 +851,6 @@ function get_cmap(sys, exprs = nothing) return cmap, cs end -function get_substitutions_and_solved_unknowns(sys, exprs = nothing; no_postprocess = false) - cmap, cs = get_cmap(sys, exprs) - if empty_substitutions(sys) && isempty(cs) - sol_states = Code.LazyState() - pre = no_postprocess ? (ex -> ex) : get_postprocess_fbody(sys) - else # Have to do some work - if !empty_substitutions(sys) - @unpack subs = get_substitutions(sys) - else - subs = [] - end - subs = [cmap; subs] # The constants need to go first - sol_states = Code.NameState(Dict(eq.lhs => Symbol(eq.lhs) for eq in subs)) - if no_postprocess - pre = ex -> Let(Assignment[Assignment(eq.lhs, eq.rhs) for eq in subs], ex, - false) - else - process = get_postprocess_fbody(sys) - pre = ex -> Let(Assignment[Assignment(eq.lhs, eq.rhs) for eq in subs], - process(ex), false) - end - end - return pre, sol_states function empty_substitutions(sys) isempty(observed(sys)) end From 26b3304f0187b3891a7b42bff932ab648e45638b Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Sun, 27 Apr 2025 13:39:06 +0530 Subject: [PATCH 137/235] refactor: update `tearing_substitute_expr`, `full_equations` to use `observed` --- .../symbolics_tearing.jl | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/src/structural_transformation/symbolics_tearing.jl b/src/structural_transformation/symbolics_tearing.jl index 65a9982c06..43c3e78e32 100644 --- a/src/structural_transformation/symbolics_tearing.jl +++ b/src/structural_transformation/symbolics_tearing.jl @@ -107,9 +107,7 @@ end function tearing_substitute_expr(sys::AbstractSystem, expr; simplify = false) empty_substitutions(sys) && return expr substitutions = get_substitutions(sys) - @unpack subs = substitutions - solved = Dict(eq.lhs => eq.rhs for eq in subs) - return tearing_sub(expr, solved, simplify) + return tearing_sub(expr, substitutions, simplify) end """ @@ -121,20 +119,17 @@ These equations matches generated numerical code. See also [`equations`](@ref) and [`ModelingToolkit.get_eqs`](@ref). """ function full_equations(sys::AbstractSystem; simplify = false) - empty_substitutions(sys) && return equations(sys) - substitutions = get_substitutions(sys) - substitutions.subed_eqs === nothing || return substitutions.subed_eqs - @unpack subs = substitutions - solved = Dict(eq.lhs => eq.rhs for eq in subs) + isempty(observed(sys)) && return equations(sys) + subs = Dict([eq.lhs => eq.rhs for eq in observed(sys)]) neweqs = map(equations(sys)) do eq if iscall(eq.lhs) && operation(eq.lhs) isa Union{Shift, Differential} - return tearing_sub(eq.lhs, solved, simplify) ~ tearing_sub(eq.rhs, solved, + return tearing_sub(eq.lhs, subs, simplify) ~ tearing_sub(eq.rhs, subs, simplify) else if !(eq.lhs isa Number && eq.lhs == 0) eq = 0 ~ eq.rhs - eq.lhs end - rhs = tearing_sub(eq.rhs, solved, simplify) + rhs = tearing_sub(eq.rhs, subs, simplify) if rhs isa Symbolic return 0 ~ rhs else # a number @@ -143,7 +138,6 @@ function full_equations(sys::AbstractSystem; simplify = false) end eq end - substitutions.subed_eqs = neweqs return neweqs end From b07c935d748a18c3deeb6c5d51a578a85bcbecfe Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Sun, 27 Apr 2025 13:39:43 +0530 Subject: [PATCH 138/235] refactor: do not use `get_substitutions` in `get_cmap` --- src/utils.jl | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/utils.jl b/src/utils.jl index cf6855999c..b3df0b957f 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -840,9 +840,6 @@ function get_cmap(sys, exprs = nothing) has_op(sys) && push!(buffer, get_op(sys)) has_constraints(sys) && append!(buffer, get_constraints(sys)) cs = collect_constants(buffer) #ctrls? what else? - if !empty_substitutions(sys) - cs = [cs; collect_constants(get_substitutions(sys).subs)] - end if exprs !== nothing cs = [cs; collect_constants(exprs)] end From a6f889a6be187f028ee603faae9fbeb26ee43b76 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Sun, 27 Apr 2025 13:41:41 +0530 Subject: [PATCH 139/235] test: fix mass matrix tests --- test/mass_matrix.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/mass_matrix.jl b/test/mass_matrix.jl index f181bdfbde..2e828cda64 100644 --- a/test/mass_matrix.jl +++ b/test/mass_matrix.jl @@ -10,7 +10,7 @@ eqs = [D(y[1]) ~ -k[1] * y[1] + k[3] * y[2] * y[3], @named sys = System(eqs, t, collect(y), [k]) sys = complete(sys) -@test_throws ArgumentError System(eqs, y[1]) +@test_throws ModelingToolkit.OperatorIndepvarMismatchError System(eqs, y[1]) M = calculate_massmatrix(sys) @test M isa Diagonal @test M == [1 0 0 From b7d48902c4d5984db1e6e235e6b660f56f1d682e Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Sun, 27 Apr 2025 18:23:44 +0530 Subject: [PATCH 140/235] test: fix odesystem tests --- test/odesystem.jl | 72 ++++++++++++++++++++++------------------------- 1 file changed, 33 insertions(+), 39 deletions(-) diff --git a/test/odesystem.jl b/test/odesystem.jl index 75914e40a7..7f82807459 100644 --- a/test/odesystem.jl +++ b/test/odesystem.jl @@ -38,8 +38,6 @@ ssort(eqs) = sort(eqs, by = string) @test eval(toexpr(de)) == de @test hash(deepcopy(de)) == hash(de) -generate_function(de) - function test_diffeq_inference(name, sys, iv, dvs, ps) @testset "System construction: $name" begin @test isequal(independent_variables(sys)[1], value(iv)) @@ -50,13 +48,12 @@ function test_diffeq_inference(name, sys, iv, dvs, ps) end test_diffeq_inference("standard", de, t, [x, y, z], [ρ, σ, β]) -generate_function(de, [x, y, z], [σ, ρ, β]) jac_expr = generate_jacobian(de) jac = calculate_jacobian(de) jacfun = eval(jac_expr[2]) de = complete(de) -f = ODEFunction(de, [x, y, z], [σ, ρ, β], tgrad = true, jac = true) +f = ODEFunction(de, tgrad = true, jac = true) # system @test f.sys === de @@ -87,7 +84,7 @@ f.jac(J, u, p, t) @test J == f.jac(u, p, t) #check iip_config -f = ODEFunction(de, [x, y, z], [σ, ρ, β], iip_config = (false, true)) +f = ODEFunction(de; iip_config = (false, true)) du = zeros(3) u = collect(1:3) p = ModelingToolkit.MTKParameters(de, [σ, ρ, β] .=> 4.0:6.0) @@ -143,7 +140,7 @@ eqs = [D(x) ~ σ(t - 1) * (y - x), D(z) ~ x * y - β * z * κ] @named de = System(eqs, t) test_diffeq_inference("single internal iv-varying", de, t, (x, y, z), (σ, ρ, β)) -f = generate_function(de, [x, y, z], [σ, ρ, β], expression = Val{false})[2] +f = generate_rhs(de, [x, y, z], [σ, ρ, β], expression = Val{false}) 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] @@ -151,37 +148,36 @@ f(du, [1.0, 2.0, 3.0], [x -> x + 7, 2, 3], 5.0) eqs = [D(x) ~ x + 10σ(t - 1) + 100σ(t - 2) + 1000σ(t^2)] @named de = System(eqs, t) test_diffeq_inference("many internal iv-varying", de, t, (x,), (σ,)) -f = generate_function(de, [x], [σ], expression = Val{false})[2] +f = generate_rhs(de, [x], [σ], expression = Val{false}) du = [0.0] f(du, [1.0], [t -> t + 2], 5.0) @test du ≈ [27561] -# Conversion to first-order ODEs #17 -D3 = D^3 -D2 = D^2 -@variables u(t) uˍtt(t) uˍt(t) xˍt(t) -eqs = [D3(u) ~ 2(D2(u)) + D(u) + D(x) + 1 - D2(x) ~ D(x) + 2] -@named de = System(eqs, t) -de1 = ode_order_lowering(de) -lowered_eqs = [D(uˍtt) ~ 2uˍtt + uˍt + xˍt + 1 - D(xˍt) ~ xˍt + 2 - D(uˍt) ~ uˍtt - D(u) ~ uˍt - D(x) ~ xˍt] - -#@test de1 == System(lowered_eqs) - -# issue #219 -@test all(isequal.( - [ModelingToolkit.var_from_nested_derivative(eq.lhs)[1] - for eq in equations(de1)], - unknowns(@named lowered = System(lowered_eqs, t)))) - -test_diffeq_inference("first-order transform", de1, t, [uˍtt, xˍt, uˍt, u, x], []) -du = zeros(5) -ODEFunction(complete(de1), [uˍtt, xˍt, uˍt, u, x], [])(du, ones(5), nothing, 0.1) -@test du == [5.0, 3.0, 1.0, 1.0, 1.0] +@testset "Issue#17: Conversion to first order ODEs" begin + D3 = D^3 + D2 = D^2 + @variables u(t) uˍtt(t) uˍt(t) xˍt(t) + eqs = [D3(u) ~ 2(D2(u)) + D(u) + D(x) + 1 + D2(x) ~ D(x) + 2] + @named de = System(eqs, t) + de1 = ode_order_lowering(de) + + @testset "Issue#219: Ordering of equations in `ode_order_lowering`" begin + lowered_eqs = [D(uˍtt) ~ 2uˍtt + uˍt + xˍt + 1 + D(xˍt) ~ xˍt + 2 + D(uˍt) ~ uˍtt + D(u) ~ uˍt + D(x) ~ xˍt] + @test isequal( + [ModelingToolkit.var_from_nested_derivative(eq.lhs)[1] for eq in equations(de1)], + unknowns(@named lowered = System(lowered_eqs, t))) + end + + test_diffeq_inference("first-order transform", de1, t, [uˍtt, xˍt, uˍt, u, x], []) + du = zeros(5) + ODEFunction(complete(de1))(du, ones(5), nothing, 0.1) + @test du == [5.0, 3.0, 1.0, 1.0, 1.0] +end # Internal calculations @parameters σ @@ -190,12 +186,11 @@ eqs = [D(x) ~ σ * a, D(y) ~ x * (ρ - z) - y, D(z) ~ x * y - β * z * κ] @named de = System(eqs, t) -generate_function(de, [x, y, z], [σ, ρ, β]) jac = calculate_jacobian(de) @test ModelingToolkit.jacobian_sparsity(de).colptr == sparse(jac).colptr @test ModelingToolkit.jacobian_sparsity(de).rowval == sparse(jac).rowval -f = ODEFunction(complete(de), [x, y, z], [σ, ρ, β]) +f = ODEFunction(complete(de)) @parameters A B C _x = y / C @@ -204,11 +199,10 @@ eqs = [D(x) ~ -A * x, @named de = System(eqs, t) @test begin local f, du - f = generate_function(de, [x, y], [A, B, C], expression = Val{false})[2] + f = generate_rhs(de, [x, y], [A, B, C], expression = Val{false}) du = [0.0, 0.0] f(du, [1.0, 2.0], [1, 2, 3], 0.0) du ≈ [-1, -1 / 3] - f = generate_function(de, [x, y], [A, B, C], expression = Val{false})[1] du ≈ f([1.0, 2.0], [1, 2, 3], 0.0) end @@ -1290,11 +1284,11 @@ end [D(u) ~ (sum(u) + sum(x) + sum(p) + sum(o)) * x, o ~ prod(u) * x], t, [u..., x..., o...], [p...]) sys1 = structural_simplify(sys, inputs = [x...], outputs = []) - fn1, = ModelingToolkit.generate_function(sys1; expression = Val{false}) + fn1, = ModelingToolkit.generate_rhs(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, inputs = [x...], outputs = [], split = false) - fn2, = ModelingToolkit.generate_function(sys2; expression = Val{false}) + fn2, = ModelingToolkit.generate_rhs(sys2; expression = Val{false}) ps = zeros(8) setp(sys2, x)(ps, 2ones(2)) setp(sys2, p)(ps, 2ones(2, 2)) From 9ec0721efb5115f0da6d75b3da49a6d915e7769b Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Sun, 27 Apr 2025 13:44:47 +0530 Subject: [PATCH 141/235] refactor: rename `generate_function` to `generate_rhs` --- src/ModelingToolkit.jl | 2 +- src/systems/abstractsystem.jl | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ModelingToolkit.jl b/src/ModelingToolkit.jl index 23741dfe43..4ca60e4e63 100644 --- a/src/ModelingToolkit.jl +++ b/src/ModelingToolkit.jl @@ -311,7 +311,7 @@ export structural_simplify, expand_connections, linearize, linearization_functio export solve export Pre -export calculate_jacobian, generate_jacobian, generate_function, generate_custom_function, +export calculate_jacobian, generate_jacobian, generate_rhs, generate_custom_function, generate_W export calculate_control_jacobian, generate_control_jacobian export calculate_tgrad, generate_tgrad diff --git a/src/systems/abstractsystem.jl b/src/systems/abstractsystem.jl index d4a57cddc4..feb98b2926 100644 --- a/src/systems/abstractsystem.jl +++ b/src/systems/abstractsystem.jl @@ -132,7 +132,7 @@ generate_function(sys::AbstractSystem, dvs = unknowns(sys), ps = parameters(sys) Generate a function to evaluate the system's equations. """ -function generate_function end +function generate_rhs end """ ```julia From 23ac3d8d651e151b7257ebc7ef1e06a494125431 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Tue, 29 Apr 2025 15:23:56 +0530 Subject: [PATCH 142/235] fix: fix type-piracy of `Symbolics.rename` --- src/systems/abstractsystem.jl | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/systems/abstractsystem.jl b/src/systems/abstractsystem.jl index feb98b2926..23b4859484 100644 --- a/src/systems/abstractsystem.jl +++ b/src/systems/abstractsystem.jl @@ -982,7 +982,7 @@ end end end -rename(x, name) = @set x.name = name +Symbolics.rename(x::AbstractSystem, name) = @set x.name = name function Base.propertynames(sys::AbstractSystem; private = false) if private @@ -2305,6 +2305,10 @@ function _named_idxs(name::Symbol, idxs, call; extra_args = "") end, $idxs)) end +function setname(x, name) + @set x.name = name +end + function single_named_expr(expr) name, call = split_assign(expr) if Meta.isexpr(name, :ref) @@ -2313,7 +2317,7 @@ function single_named_expr(expr) var = gensym(name) ex = quote $var = $(_named(name, call)) - $name = map(i -> $rename($var, Symbol($(Meta.quot(name)), :_, i)), $idxs) + $name = map(i -> $setname($var, Symbol($(Meta.quot(name)), :_, i)), $idxs) end ex else From 9b1617f10c629362ff15ac329051bdc5f61ad342 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Tue, 29 Apr 2025 15:24:28 +0530 Subject: [PATCH 143/235] refactor: remove `systems/diffeqs/modelingtoolkitize.jl` --- src/ModelingToolkit.jl | 1 - 1 file changed, 1 deletion(-) diff --git a/src/ModelingToolkit.jl b/src/ModelingToolkit.jl index 4ca60e4e63..a08aeca013 100644 --- a/src/ModelingToolkit.jl +++ b/src/ModelingToolkit.jl @@ -187,7 +187,6 @@ 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/pde/pdesystem.jl") From c6d20eeacba7b641335e30e7186f17a64256da4a Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Tue, 29 Apr 2025 15:24:56 +0530 Subject: [PATCH 144/235] feat: add `modelingtoolkitize` for `ODEProblem` feat: support passing `t::Nothing` to `trace_rhs` feat: support time-independent variable declaration in mtkize utils fix: handle bounds in `construct_vars` when `prob.f.sys isa System` --- src/ModelingToolkit.jl | 4 + src/modelingtoolkitize/common.jl | 393 +++++++++++++++++++++++++++ src/modelingtoolkitize/odeproblem.jl | 59 ++++ 3 files changed, 456 insertions(+) create mode 100644 src/modelingtoolkitize/common.jl create mode 100644 src/modelingtoolkitize/odeproblem.jl diff --git a/src/ModelingToolkit.jl b/src/ModelingToolkit.jl index a08aeca013..f4ffb8459b 100644 --- a/src/ModelingToolkit.jl +++ b/src/ModelingToolkit.jl @@ -181,6 +181,10 @@ include("problems/jumpproblem.jl") include("problems/initializationproblem.jl") include("problems/sccnonlinearproblem.jl") include("problems/bvproblem.jl") + +include("modelingtoolkitize/common.jl") +include("modelingtoolkitize/odeproblem.jl") + include("systems/optimization/modelingtoolkitize.jl") include("systems/nonlinear/homotopy_continuation.jl") diff --git a/src/modelingtoolkitize/common.jl b/src/modelingtoolkitize/common.jl new file mode 100644 index 0000000000..a3d059a66a --- /dev/null +++ b/src/modelingtoolkitize/common.jl @@ -0,0 +1,393 @@ + +""" + $(TYPEDSIGNATURES) + +Check if the length of variables `vars` matches the number of names for those variables, +given by `names`. `is_unknowns` denotes whether the variable are unknowns or parameters. +""" +function varnames_length_check(vars, names; is_unknowns = false) + length(names) == length(vars) && return + throw(ArgumentError(""" + Number of $(is_unknowns ? "unknowns" : "parameters") ($(length(vars))) \ + does not match number of names ($(length(names))). + """)) +end + +""" + $(TYPEDSIGNATURES) + +Define a subscripted time-dependent variable with name `x` and subscript `i`. Equivalent +to `@variables \$name(..)`. `T` is the desired symtype of the variable when called with +the independent variable. +""" +_defvaridx(x, i; T = Real) = variable(x, i, T = SymbolicUtils.FnType{Tuple, T}) +""" + $(TYPEDSIGNATURES) + +Define a time-dependent variable with name `x`. Equivalent to `@variables \$x(..)`. +`T` is the desired symtype of the variable when called with the independent variable. +""" +_defvar(x; T = Real) = variable(x, T = SymbolicUtils.FnType{Tuple, T}) + +""" + $(TYPEDSIGNATURES) + +Define an array of symbolic unknowns of the appropriate type and size for `u` with +independent variable `t`. +""" +function define_vars(u, t) + [_defvaridx(:x, i)(t) for i in eachindex(u)] +end + +function define_vars(u, ::Nothing) + [variable(:x, i) for i in eachindex(u)] +end + +""" + $(TYPEDSIGNATURES) + +Return a symbolic state for the given proble `prob.`. `t` is the independent variable. +`u_names` optionally contains the names to use for the created symbolic variables. +""" +function construct_vars(prob, t, u_names = nothing) + if prob.u0 === nothing + return [] + end + # construct `_vars`, AbstractSciMLFunction, AbstractSciMLFunction, a list of MTK variables for `prob.u0`. + if u_names !== nothing + # explicitly provided names + varnames_length_check(state_values(prob), u_names; is_unknowns = true) + if t === nothing + _vars = [variable(name) for name in u_names] + else + _vars = [_defvar(name)(t) for name in u_names] + end + elseif SciMLBase.has_sys(prob.f) + # get names from the system + varnames = getname.(variable_symbols(prob.f.sys)) + varidxs = variable_index.((prob.f.sys,), varnames) + invpermute!(varnames, varidxs) + if t === nothing + _vars = [variable(name) for name in varnames] + else + _vars = [_defvar(name)(t) for name in varnames] + end + if prob.f.sys isa System + for (i, sym) in enumerate(variable_symbols(prob.f.sys)) + if hasbounds(sym) + _vars[i] = Symbolics.setmetadata( + _vars[i], VariableBounds, getbounds(sym)) + end + end + end + else + # auto-generate names + _vars = define_vars(state_values(prob), t) + end + + # Handle different types of arrays + return prob.u0 isa Number ? _vars : ArrayInterface.restructure(prob.u0, _vars) +end + +""" + $(METHODLIST) + +Define symbolic names for each value in parameter object `p`. `t` is the independent +variable of the system. `names` is a collection mapping indexes of `p` to their +names, or `nothing` to automatically generate names. + +The returned value has the same structure as `p`, but symbolic variables instead of +values. +""" +function define_params(p, t, _ = nothing) + throw(ModelingtoolkitizeParametersNotSupportedError(typeof(p))) +end + +function define_params(p::AbstractArray, t, names = nothing) + if names === nothing + [toparam(variable(:α, i)) for i in eachindex(p)] + else + varnames_length_check(p, names) + [toparam(variable(names[i])) for i in eachindex(p)] + end +end + +function define_params(p::Number, t, names = nothing) + if names === nothing + [toparam(variable(:α))] + elseif names isa Union{AbstractArray, AbstractDict} + varnames_length_check(p, names) + [toparam(variable(names[i])) for i in eachindex(p)] + else + [toparam(variable(names))] + end +end + +function define_params(p::AbstractDict, t, names = nothing) + if names === nothing + OrderedDict(k => toparam(variable(:α, i)) for (i, k) in zip(1:length(p), keys(p))) + else + varnames_length_check(p, names) + OrderedDict(k => toparam(variable(names[k])) for k in keys(p)) + end +end + +function define_params(p::Tuple, t, names = nothing) + if names === nothing + tuple((toparam(variable(:α, i)) for i in eachindex(p))...) + else + varnames_length_check(p, names) + tuple((toparam(variable(names[i])) for i in eachindex(p))...) + end +end + +function define_params(p::NamedTuple, t, names = nothing) + if names === nothing + NamedTuple(x => toparam(variable(x)) for x in keys(p)) + else + varnames_length_check(p, names) + NamedTuple(x => toparam(variable(names[x])) for x in keys(p)) + end +end + +function define_params(p::MTKParameters, t, names = nothing) + if names === nothing + ps = [] + i = 1 + # tunables are all treated as scalar reals + for x in p.tunable + push!(ps, toparam(variable(:α, i))) + i += 1 + end + # ignore initials + # discretes should be time-dependent + for buf in p.discrete + T = eltype(buf) + for val in buf + # respect array sizes + shape = val isa AbstractArray ? axes(val) : nothing + push!(ps, declare_timevarying_parameter(:α, i, t; T, shape)) + i += 1 + end + end + # handle constants + for buf in p.constant + T = eltype(buf) + for val in buf + # respect array sizes + shape = val isa AbstractArray ? axes(val) : nothing + push!(ps, declare_parameter(:α, i; T, shape)) + i += 1 + end + end + # handle nonnumerics + for buf in p.nonnumeric + T = eltype(buf) + for val in buf + # respect array sizes + shape = val isa AbstractArray ? axes(val) : nothing + push!(ps, declare_parameter(:α, i; T, shape)) + i += 1 + end + end + return identity.(ps) + else + new_p = as_any_buffer(p) + @set! new_p.initials = [] + for (k, v) in names + val = p[k] + shape = val isa AbstractArray ? axes(val) : nothing + T = typeof(val) + if k.portion == SciMLStructures.Initials() + continue + end + if k.portion == SciMLStructures.Tunable() + T = Real + end + if k.portion == SciMLStructures.Discrete() + var = declare_timevarying_parameter(getname(v), nothing, t; T, shape) + else + var = declare_parameter(getname(v), nothing; T, shape) + end + new_p[k] = var + end + return new_p + end +end + +""" + $(TYPEDSIGNATURES) + +Given a parameter object `p` containing symbolic variables instead of values, return +a vector of the symbolic variables. +""" +function to_paramvec(p) + vec(collect(values(p))) +end + +function to_paramvec(p::MTKParameters) + reduce(vcat, collect(p); init = []) +end + +""" + $(TYPEDSIGNATURES) + +Create a time-varying parameter with name `x`, subscript `i`, independent variable `t` +which stores values of type `T`. `shape` denotes the shape of array values, or `nothing` +for scalars. + +To ignore the subscript, pass `nothing` for `i`. +""" +function declare_timevarying_parameter(x::Symbol, i, t; T, shape = nothing) + # turn specific floating point numbers to `Real` + if T <: Union{AbstractFloat, ForwardDiff.Dual} + T = Real + end + if T <: Array{<:Union{AbstractFloat, ForwardDiff.Dual}, N} where {N} + T = Array{Real, ndims(T)} + end + + if i === nothing + var = _defvar(x; T) + else + var = _defvaridx(x, i; T) + end + var = toparam(unwrap(var(t))) + if shape !== nothing + var = setmetadata(var, Symbolics.ArrayShapeCtx, shape) + end + return var +end + +""" + $(TYPEDSIGNATURES) + +Create a time-varying parameter with name `x` and subscript `i`, which stores values of +type `T`. `shape` denotes the shape of array values, or `nothing` for scalars. + +To ignore the subscript, pass `nothing` for `i`. +""" +function declare_parameter(x::Symbol, i; T, shape = nothing) + # turn specific floating point numbers to `Real` + if T <: Union{AbstractFloat, ForwardDiff.Dual} + T = Real + end + if T <: Array{<:Union{AbstractFloat, ForwardDiff.Dual}, N} where {N} + T = Array{Real, ndims(T)} + end + + i = i === nothing ? () : (i,) + var = toparam(unwrap(variable(x, i...; T))) + if shape !== nothing + var = setmetadata(var, Symbolics.ArrayShapeCtx, shape) + end + return var +end + +""" + $(TYPEDSIGNATURES) + +Return a symbolic parameter object for the given proble `prob.`. `t` is the independent +variable. `p_names` optionally contains the names to use for the created symbolic +variables. +""" +function construct_params(prob, t, p_names = nothing) + p = parameter_values(prob) + has_p = !(p isa Union{DiffEqBase.NullParameters, Nothing}) + + # Get names of parameters + if has_p + if p_names === nothing && SciMLBase.has_sys(prob.f) + # get names from the system + p_names = Dict(parameter_index(prob.f.sys, sym) => sym + for sym in parameter_symbols(prob.f.sys)) + end + params = define_params(p, t, p_names) + if p isa Number + params = params[1] + elseif p isa AbstractArray + params = ArrayInterface.restructure(p, params) + end + else + params = [] + end + + return params +end + +""" + $(TYPEDSIGNATURES) + +Given the differential operator `D`, mass matrix `mm` and ordered list of unknowns `vars`, +return the list of +""" +function lhs_from_mass_matrix(D, mm, vars) + var_set = Set(vars) + # calculate equation LHS from mass matrix + if mm === I + lhs = map(v -> D(v), vars) + else + lhs = map(mm * vars) do v + if iszero(v) + 0 + elseif v in var_set + D(v) + else + error("Non-permutation mass matrix is not supported.") + end + end + end + return lhs +end + +""" + $(TYPEDSIGNATURES) + +Given a problem `prob`, the symbolic unknowns and params and the independent variable, +trace through `prob.f` and return the resultant expression. +""" +function trace_rhs(prob, vars, params, t) + args = (vars, params) + if t !== nothing + args = (args..., t) + end + # trace prob.f to get equation RHS + if SciMLBase.isinplace(prob.f) + rhs = ArrayInterface.restructure(prob.u0, similar(vars, Num)) + fill!(rhs, 0) + if prob.f isa SciMLBase.AbstractSciMLFunction && + prob.f.f isa FunctionWrappersWrappers.FunctionWrappersWrapper + prob.f.f.fw[1].obj[](rhs, args...) + else + prob.f(rhs, args...) + end + else + rhs = prob.f(args...) + end + return rhs +end + +""" + $(TYPEDSIGNATURES) + +Obtain default values for unknowns `vars` and parameters `paramvec` +given the problem `prob` and symbolic parameter object `paramobj`. +""" +function defaults_from_u0_p(prob, vars, paramobj, paramvec) + u0 = state_values(prob) + p = parameter_values(prob) + defaults = Dict{Any, Any}(vec(vars) .=> vec(collect(u0))) + if !(p isa Union{SciMLBase.NullParameters, Nothing}) + if p isa Union{NamedTuple, AbstractDict} + merge!(defaults, Dict(v => p[k] for (k, v) in pairs(paramobj))) + elseif p isa MTKParameters + pvals = [p.tunable; reduce(vcat, p.discrete; init = []); + reduce(vcat, p.constant; init = []); + reduce(vcat, p.nonnumeric; init = [])] + merge!(defaults, Dict(paramvec .=> pvals)) + else + merge!(defaults, Dict(paramvec .=> vec(collect(p)))) + end + end + return defaults +end diff --git a/src/modelingtoolkitize/odeproblem.jl b/src/modelingtoolkitize/odeproblem.jl new file mode 100644 index 0000000000..3bc74d8887 --- /dev/null +++ b/src/modelingtoolkitize/odeproblem.jl @@ -0,0 +1,59 @@ +""" + $(TYPEDSIGNATURES) + +Convert an `ODEProblem` to a `ModelingToolkit.System`. + +# Keyword arguments + +- `u_names`: An array of names of the same size as `prob.u0` to use as the names of the + unknowns of the system. The names should be given as `Symbol`s. +- `p_names`: A collection of names to use for parameters of the system. The collection + should have keys corresponding to indexes of `prob.p`. For example, if `prob.p` is an + associative container like `NamedTuple`, then `p_names` should map keys of `prob.p` to + the name that the corresponding parameter should have in the returned system. The names + should be given as `Symbol`s. +- INTERNAL `return_symbolic_u0_p`: Also return the symbolic state and parameter objects. + +All other keyword arguments are forwarded to the created `System`. +""" +function modelingtoolkitize(prob::ODEProblem; u_names = nothing, p_names = nothing, + return_symbolic_u0_p = false, kwargs...) + if prob.f isa DiffEqBase.AbstractParameterizedFunction + return prob.f.sys + end + + t = t_nounits + p = prob.p + has_p = !(p isa Union{DiffEqBase.NullParameters, Nothing}) + + vars = construct_vars(prob, t, u_names) + params = construct_params(prob, t, p_names) + + lhs = lhs_from_mass_matrix(D_nounits, prob.f.mass_matrix, vars) + rhs = trace_rhs(prob, vars, params, t) + eqs = vcat([lhs[i] ~ rhs[i] for i in eachindex(prob.u0)]...) + + sts = vec(collect(vars)) + + # turn `params` into a list of symbolic variables as opposed to + # a parameter object containing symbolic variables. + _params = params + params = to_paramvec(params) + + defaults = defaults_from_u0_p(prob, vars, _params, params) + # In case initials crept in, specifically from when we constructed parameters + # using prob.f.sys + filter!(x -> !iscall(x) || !(operation(x) isa Initial), params) + filter!(x -> !iscall(x[1]) || !(operation(x[1]) isa Initial), defaults) + + sys = System(eqs, t, sts, params; + defaults, + name = gensym(:MTKizedODE), + kwargs...) + + if return_symbolic_u0_p + return sys, vars, _params + else + return sys + end +end From 0425dc26691fd8d305ce3760c7011511c148351e Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Tue, 29 Apr 2025 15:25:04 +0530 Subject: [PATCH 145/235] feat: add `modelingtoolkitize` for `SDEProblem` --- src/ModelingToolkit.jl | 1 + src/modelingtoolkitize/sdeproblem.jl | 53 ++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+) create mode 100644 src/modelingtoolkitize/sdeproblem.jl diff --git a/src/ModelingToolkit.jl b/src/ModelingToolkit.jl index f4ffb8459b..ccfbaf9a52 100644 --- a/src/ModelingToolkit.jl +++ b/src/ModelingToolkit.jl @@ -184,6 +184,7 @@ include("problems/bvproblem.jl") include("modelingtoolkitize/common.jl") include("modelingtoolkitize/odeproblem.jl") +include("modelingtoolkitize/sdeproblem.jl") include("systems/optimization/modelingtoolkitize.jl") diff --git a/src/modelingtoolkitize/sdeproblem.jl b/src/modelingtoolkitize/sdeproblem.jl new file mode 100644 index 0000000000..ff8c238559 --- /dev/null +++ b/src/modelingtoolkitize/sdeproblem.jl @@ -0,0 +1,53 @@ +""" + $(TYPEDSIGNATURES) + +Convert an `SDEProblem` to a `ModelingToolkit.System`. + +# Keyword arguments + +- `u_names`: an array of names of the same size as `prob.u0` to use as the names of the + unknowns of the system. The names should be given as `Symbol`s. +- `p_names`: a collection of names to use for parameters of the system. The collection + should have keys corresponding to indexes of `prob.p`. For example, if `prob.p` is an + associative container like `NamedTuple`, then `p_names` should map keys of `prob.p` to + the name that the corresponding parameter should have in the returned system. The names + should be given as `Symbol`s. + +All other keyword arguments are forwarded to the created `System`. +""" +function modelingtoolkitize( + prob::SDEProblem; u_names = nothing, p_names = nothing, kwargs...) + if prob.f isa DiffEqBase.AbstractParameterizedFunction + return prob.f.sys + end + + # just create the equivalent ODEProblem, `modelingtoolkitize` that + # and add on the noise + odefn = ODEFunction{SciMLBase.isinplace(prob)}( + prob.f.f; mass_matrix = prob.f.mass_matrix, sys = prob.f.sys) + odeprob = ODEProblem(odefn, prob.u0, prob.tspan, prob.p) + sys, vars, params = modelingtoolkitize( + odeprob; u_names, p_names, return_symbolic_u0_p = true, + name = gensym(:MTKizedSDE), kwargs...) + t = get_iv(sys) + + if SciMLBase.isinplace(prob) + if SciMLBase.is_diagonal_noise(prob) + neqs = similar(vars, Any) + prob.g(neqs, vars, params, t) + else + neqs = similar(prob.noise_rate_prototype, Any) + prob.g(neqs, vars, params, t) + end + else + if SciMLBase.is_diagonal_noise(prob) + neqs = prob.g(vars, params, t) + else + neqs = prob.g(vars, params, t) + end + end + + @set! sys.noise_eqs = neqs + + return sys +end From cee2a6d35c911937947caf5dcd747149355781db Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Tue, 29 Apr 2025 16:05:33 +0530 Subject: [PATCH 146/235] feat: add `add_accumulations` --- src/ModelingToolkit.jl | 2 +- src/systems/diffeqs/basic_transformations.jl | 38 ++++++++++++++++++++ test/basic_transformations.jl | 22 ++++++++++++ 3 files changed, 61 insertions(+), 1 deletion(-) diff --git a/src/ModelingToolkit.jl b/src/ModelingToolkit.jl index ccfbaf9a52..c22d78f30f 100644 --- a/src/ModelingToolkit.jl +++ b/src/ModelingToolkit.jl @@ -302,7 +302,7 @@ export isinput, isoutput, getbounds, hasbounds, getguess, hasguess, isdisturbanc hasunit, getunit, hasconnect, getconnect, hasmisc, getmisc, state_priority export ode_order_lowering, dae_order_lowering, liouville_transform, - change_independent_variable, substitute_component + change_independent_variable, substitute_component, add_accumulations export PDESystem export Differential, expand_derivatives, @derivatives export Equation, ConstrainedEquation diff --git a/src/systems/diffeqs/basic_transformations.jl b/src/systems/diffeqs/basic_transformations.jl index 21dcc7977a..c9d5234751 100644 --- a/src/systems/diffeqs/basic_transformations.jl +++ b/src/systems/diffeqs/basic_transformations.jl @@ -409,3 +409,41 @@ function Girsanov_transform(sys::System, u; θ0 = 1.0) end return sys end + +""" + $(TYPEDSIGNATURES) + +Add accumulation variables for `vars`. For every unknown `x` in `vars`, add +`D(accumulation_x) ~ x` as an equation. +""" +function add_accumulations(sys::System, vars = unknowns(sys)) + avars = [rename(v, Symbol(:accumulation_, getname(v))) for v in vars] + return add_accumulations(sys, avars .=> vars) +end + +""" + $(TYPEDSIGNATURES) + +Add accumulation variables for `vars`. `vars` is a vector of pairs in the form +of + +```julia +[cumulative_var1 => x + y, cumulative_var2 => x^2] +``` +Then, cumulative variables `cumulative_var1` and `cumulative_var2` that computes +the cumulative `x + y` and `x^2` would be added to `sys`. + +All accumulation variables have a default of zero. +""" +function add_accumulations(sys::System, vars::Vector{<:Pair}) + eqs = get_eqs(sys) + avars = map(first, vars) + if (ints = intersect(avars, unknowns(sys)); !isempty(ints)) + error("$ints already exist in the system!") + end + D = Differential(get_iv(sys)) + @set! sys.eqs = [eqs; Equation[D(a) ~ v[2] for (a, v) in zip(avars, vars)]] + @set! sys.unknowns = [get_unknowns(sys); avars] + @set! sys.defaults = merge(get_defaults(sys), Dict(a => 0.0 for a in avars)) + return sys +end diff --git a/test/basic_transformations.jl b/test/basic_transformations.jl index a414120ed1..50f4c0feaf 100644 --- a/test/basic_transformations.jl +++ b/test/basic_transformations.jl @@ -304,3 +304,25 @@ end sol = solve(prob, Tsit5(); reltol = 1e-5) @test sol[new_sys.y][end] ≈ 0.75 end + +@testset "`add_accumulations`" begin + @parameters a + @variables x(t) y(t) z(t) + @named sys = System([D(x) ~ y, 0 ~ x + z, 0 ~ x - y], t, [z, y, x], []) + asys = add_accumulations(sys) + @variables accumulation_x(t) accumulation_y(t) accumulation_z(t) + eqs = [0 ~ x + z + 0 ~ x - y + D(accumulation_x) ~ x + D(accumulation_y) ~ y + D(accumulation_z) ~ z + D(x) ~ y] + @test issetequal(equations(asys), eqs) + @variables ac(t) + asys = add_accumulations(sys, [ac => (x + y)^2]) + eqs = [0 ~ x + z + 0 ~ x - y + D(ac) ~ (x + y)^2 + D(x) ~ y] + @test issetequal(equations(asys), eqs) +end From 1e4cb23943873b7bb9eb8b0ec3df582d130216c3 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Tue, 29 Apr 2025 16:27:10 +0530 Subject: [PATCH 147/235] test: fix `odesystem` tests --- test/odesystem.jl | 164 +++++++++++++--------------------------------- 1 file changed, 47 insertions(+), 117 deletions(-) diff --git a/test/odesystem.jl b/test/odesystem.jl index 7f82807459..defef0bbaa 100644 --- a/test/odesystem.jl +++ b/test/odesystem.jl @@ -93,29 +93,29 @@ f.f(du, u, p, 0.1) @test_throws ArgumentError f.f(u, p, 0.1) #check iip -f = eval(ODEFunctionExpr(de, [x, y, z], [σ, ρ, β])) -f2 = ODEFunction(de, [x, y, z], [σ, ρ, β]) +f = eval(ODEFunction(de; expression = Val{true})) +f2 = ODEFunction(de) @test SciMLBase.isinplace(f) === SciMLBase.isinplace(f2) @test SciMLBase.specialization(f) === SciMLBase.specialization(f2) for iip in (true, false) - f = eval(ODEFunctionExpr{iip}(de, [x, y, z], [σ, ρ, β])) - f2 = ODEFunction{iip}(de, [x, y, z], [σ, ρ, β]) + f = eval(ODEFunction{iip}(de; expression = Val{true})) + f2 = ODEFunction{iip}(de) @test SciMLBase.isinplace(f) === SciMLBase.isinplace(f2) === iip @test SciMLBase.specialization(f) === SciMLBase.specialization(f2) for specialize in (SciMLBase.AutoSpecialize, SciMLBase.FullSpecialize) - f = eval(ODEFunctionExpr{iip, specialize}(de, [x, y, z], [σ, ρ, β])) - f2 = ODEFunction{iip, specialize}(de, [x, y, z], [σ, ρ, β]) + f = eval(ODEFunction{iip, specialize}(de; expression = Val{true})) + f2 = ODEFunction{iip, specialize}(de) @test SciMLBase.isinplace(f) === SciMLBase.isinplace(f2) === iip @test SciMLBase.specialization(f) === SciMLBase.specialization(f2) === specialize end end #check sparsity -f = eval(ODEFunctionExpr(de, [x, y, z], [σ, ρ, β], sparsity = true)) +f = eval(ODEFunction(de, sparsity = true, expression = Val{true})) @test f.sparsity == ModelingToolkit.jacobian_sparsity(de) -f = eval(ODEFunctionExpr(de, [x, y, z], [σ, ρ, β], sparsity = false)) +f = eval(ODEFunction(de, sparsity = false, expression = Val{true})) @test isnothing(f.sparsity) eqs = [D(x) ~ σ * (y - x), @@ -138,9 +138,9 @@ tgrad_iip(du, u, p, t) eqs = [D(x) ~ σ(t - 1) * (y - x), D(y) ~ x * (ρ - z) - y, D(z) ~ x * y - β * z * κ] -@named de = System(eqs, t) +@named de = System(eqs, t, [x, y, z], [σ, ρ, β]) test_diffeq_inference("single internal iv-varying", de, t, (x, y, z), (σ, ρ, β)) -f = generate_rhs(de, [x, y, z], [σ, ρ, β], expression = Val{false}) +f = generate_rhs(de, expression = Val{false}, wrap_gfw = Val{true}) 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] @@ -148,7 +148,7 @@ f(du, [1.0, 2.0, 3.0], [x -> x + 7, 2, 3], 5.0) eqs = [D(x) ~ x + 10σ(t - 1) + 100σ(t - 2) + 1000σ(t^2)] @named de = System(eqs, t) test_diffeq_inference("many internal iv-varying", de, t, (x,), (σ,)) -f = generate_rhs(de, [x], [σ], expression = Val{false}) +f = generate_rhs(de, [x], [σ], expression = Val{false}, wrap_gfw = Val{true}) du = [0.0] f(du, [1.0], [t -> t + 2], 5.0) @test du ≈ [27561] @@ -199,7 +199,7 @@ eqs = [D(x) ~ -A * x, @named de = System(eqs, t) @test begin local f, du - f = generate_rhs(de, [x, y], [A, B, C], expression = Val{false}) + f = generate_rhs(de, [x, y], [A, B, C], expression = Val{false}, wrap_gfw = Val{true}) du = [0.0, 0.0] f(du, [1.0, 2.0], [1, 2, 3], 0.0) du ≈ [-1, -1 / 3] @@ -295,7 +295,7 @@ sol_dpmap = solve(prob_dpmap, Rodas5()) sys = makecombinedsys() @unpack sys1, b = sys - prob = ODEProblem(sys, Pair[]) + prob = ODEProblem(sys, Pair[], (0.0, 1.0)) prob_new = SciMLBase.remake(prob, p = Dict(sys1.a => 3.0, b => 4.0), u0 = Dict(sys1.x => 1.0)) @test prob_new.p isa MTKParameters @@ -322,7 +322,7 @@ du0 = [D(y₁) => -0.04 D(y₂) => 0.04 D(y₃) => 0.0] prob4 = DAEProblem(sys, du0, u0, tspan, p2) -prob5 = eval(DAEProblemExpr(sys, du0, u0, tspan, p2)) +prob5 = eval(DAEProblem(sys, du0, u0, tspan, p2; expression = Val{true})) for prob in [prob4, prob5] local sol @test prob.differential_vars == [true, true, false] @@ -336,42 +336,27 @@ eqs = [D(x) ~ σ * (y - x), D(y) ~ x - β * y, x + z ~ y] @named sys = System(eqs, t) -@test all(isequal.(unknowns(sys), [x, y, z])) -@test all(isequal.(parameters(sys), [σ, β])) +@test issetequal(unknowns(sys), [x, y, z]) +@test issetequal(parameters(sys), [σ, β]) @test equations(sys) == eqs @test ModelingToolkit.isautonomous(sys) -# issue 701 -using ModelingToolkit -@parameters a -@variables x(t) -@named sys = System([D(x) ~ a], t) -@test issym(equations(sys)[1].rhs) +@testset "Issue#701: `collect_vars!` handles non-call symbolics" begin + @parameters a + @variables x(t) + @named sys = System([D(x) ~ a], t) + @test issym(equations(sys)[1].rhs) +end -# issue 708 -@parameters a -@variables x(t) y(t) z(t) -@named sys = System([D(x) ~ y, 0 ~ x + z, 0 ~ x - y], t, [z, y, x], []) -asys = add_accumulations(sys) -@variables accumulation_x(t) accumulation_y(t) accumulation_z(t) -eqs = [0 ~ x + z - 0 ~ x - y - D(accumulation_x) ~ x - D(accumulation_y) ~ y - D(accumulation_z) ~ z - D(x) ~ y] -@test ssort(equations(asys)) == ssort(eqs) -@variables ac(t) -asys = add_accumulations(sys, [ac => (x + y)^2]) -eqs = [0 ~ x + z - 0 ~ x - y - D(ac) ~ (x + y)^2 - D(x) ~ y] -@test ssort(equations(asys)) == ssort(eqs) - -sys2 = ode_order_lowering(sys) -M = ModelingToolkit.calculate_massmatrix(sys2) -@test M == Diagonal([1, 0, 0]) +@testset "Issue#708" begin + @parameters a + @variables x(t) y(t) z(t) + @named sys = System([D(x) ~ y, 0 ~ x + z, 0 ~ x - y], t, [z, y, x], []) + + sys2 = ode_order_lowering(sys) + M = ModelingToolkit.calculate_massmatrix(sys2) + @test M == Diagonal([1, 0, 0]) +end # issue #609 @variables x1(t) x2(t) @@ -404,7 +389,8 @@ eq = D(x) ~ r * x sys1 = makesys(:sys1) sys2 = makesys(:sys1) - @test_throws ArgumentError System([sys2.f ~ sys1.x, D(sys1.f) ~ 0], t, + @test_throws ModelingToolkit.NonUniqueSubsystemsError System( + [sys2.f ~ sys1.x, D(sys1.f) ~ 0], t, systems = [sys1, sys2], name = :foo) end issue808() @@ -436,14 +422,14 @@ eqs = [D(D(x)) ~ -b / M * D(x) - k / M * x] ps = [M, b, k] default_u0 = [D(x) => 0.0, x => 10.0] default_p = [M => 1.0, b => 1.0, k => 1.0] -@named sys = System(eqs, t, [x], ps; defaults = [default_u0; default_p], tspan) +@named sys = System(eqs, t, [x], ps; defaults = [default_u0; default_p]) sys = ode_order_lowering(sys) sys = complete(sys) -prob = ODEProblem(sys) +prob = ODEProblem(sys, nothing, tspan) sol = solve(prob, Tsit5()) @test sol.t[end] == tspan[end] @test sum(abs, sol.u[end]) < 1 -prob = ODEProblem{false}(sys; u0_constructor = x -> SVector(x...)) +prob = ODEProblem{false}(sys, nothing, tspan; u0_constructor = x -> SVector(x...)) @test prob.u0 isa SVector # check_eqs_u0 kwarg test @@ -454,23 +440,9 @@ sys = complete(sys) @test_throws ArgumentError ODEProblem(sys, [1.0, 1.0], (0.0, 1.0)) @test_nowarn ODEProblem(sys, [1.0, 1.0], (0.0, 1.0), check_length = false) -# check inputs -let - @parameters k d - @variables x(t) ẋ(t) f(t) [input = true] - - eqs = [D(x) ~ ẋ, D(ẋ) ~ f - k * x - d * ẋ] - @named sys = System(eqs, t, [x, ẋ], [f, d, k]) - sys = structural_simplify(sys; inputs = [f]) - - @test isequal(calculate_control_jacobian(sys), - reshape(Num[0, 1], 2, 1)) -end - -# issue 1109 -let +@testset "Issue#1109" begin @variables x(t)[1:3, 1:3] - @named sys = System(D.(x) .~ x, t) + @named sys = System(D(x) ~ x, t) @test_nowarn structural_simplify(sys) end @@ -739,51 +711,6 @@ let @test length(equations(structural_simplify(sys))) == 2 end -let - eq_to_lhs(eq) = eq.lhs - eq.rhs ~ 0 - eqs_to_lhs(eqs) = eq_to_lhs.(eqs) - - @parameters σ=10 ρ=28 β=8 / 3 sigma rho beta - @variables x(t)=1 y(t)=0 z(t)=0 x2(t)=1 y2(t)=0 z2(t)=0 u(t)[1:3] - - eqs = [D(x) ~ σ * (y - x), - D(y) ~ x * (ρ - z) - y, - D(z) ~ x * y - β * z] - - eqs2 = [ - D(y2) ~ x2 * (rho - z2) - y2, - D(x2) ~ sigma * (y2 - x2), - D(z2) ~ x2 * y2 - beta * z2 - ] - - # array u - eqs3 = [D(u[1]) ~ sigma * (u[2] - u[1]), - D(u[2]) ~ u[1] * (rho - u[3]) - u[2], - D(u[3]) ~ u[1] * u[2] - beta * u[3]] - eqs3 = eqs_to_lhs(eqs3) - - eqs4 = [ - D(y2) ~ x2 * (rho - z2) - y2, - D(x2) ~ sigma * (y2 - x2), - D(z2) ~ y2 - beta * z2 # missing x2 term - ] - - @named sys1 = System(eqs, t) - @named sys2 = System(eqs2, t) - @named sys3 = System(eqs3, t) - ssys3 = structural_simplify(sys3) - @named sys4 = System(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, sys4) - - # 1281 - iv2 = only(independent_variables(sys2)) - @test isequal(only(independent_variables(convert_system(System, sys1, iv2))), iv2) -end - let vars = @variables sP(t) spP(t) spm(t) sph(t) pars = @parameters a b @@ -1572,14 +1499,16 @@ end @parameters p d @variables X(t)::Int64 eq = D(X) ~ p - d * X - @test_throws ArgumentError @mtkbuild osys = System([eq], t) + @test_throws ModelingToolkit.ContinuousOperatorDiscreteArgumentError @mtkbuild osys = System( + [eq], t) @variables Y(t)[1:3]::String eq = D(Y) ~ [p, p, p] - @test_throws ArgumentError @mtkbuild osys = System([eq], t) + @test_throws ModelingToolkit.ContinuousOperatorDiscreteArgumentError @mtkbuild osys = System( + [eq], t) @variables X(t)::Complex - eq = D(X) ~ p - d * X - @test_nowarn @named osys = System([eq], t) + eqs = D(X) ~ p - d * X + @test_nowarn @named osys = System(eqs, t) end # Test `isequal` @@ -1662,7 +1591,8 @@ end @test sol.t[end] == tspan[end] @test sum(abs, sol.u[end]) < 1 - prob = ODEProblem{false}(lowered_dae_sys; u0_constructor = x -> SVector(x...)) + prob = ODEProblem{false}( + lowered_dae_sys, nothing, tspan; u0_constructor = x -> SVector(x...)) @test prob.u0 isa SVector end @@ -1710,7 +1640,7 @@ end eqs = D(x(t)) ~ mat * x(t) cons = [x(3) ~ [2, 3, 3, 5, 4]] @mtkbuild ode = System(D(x(t)) ~ mat * x(t), t; constraints = cons) - @test length(constraints(ModelingToolkit.get_constraintsystem(ode))) == 5 + @test length(constraints(ode)) == 1 end @testset "`build_explicit_observed_function` with `expression = true` returns `Expr`" begin From a815755de4798965589651ea1bc277af5e01eedb Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Tue, 29 Apr 2025 21:20:29 +0530 Subject: [PATCH 148/235] fix: validate that `Sample` operates on unknowns --- src/discretedomain.jl | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/discretedomain.jl b/src/discretedomain.jl index 1eeec4c014..f8a1a17009 100644 --- a/src/discretedomain.jl +++ b/src/discretedomain.jl @@ -146,6 +146,11 @@ function validate_operator(op::Sample, args, iv; context = nothing) if !is_variable_floatingpoint(arg) throw(ContinuousOperatorDiscreteArgumentError(op, arg, context)) end + if isparameter(arg) + throw(ArgumentError(""" + Expected argument of $op to be an unknown, found $arg which is a parameter. + """)) + end end """ From c7d37e5728fae05f3ce3a09ec20f1ddef611fec3 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Tue, 29 Apr 2025 21:20:57 +0530 Subject: [PATCH 149/235] test: fix `test/structural_transformation/utils.jl` --- test/structural_transformation/utils.jl | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/test/structural_transformation/utils.jl b/test/structural_transformation/utils.jl index af3be46572..7611ab5d33 100644 --- a/test/structural_transformation/utils.jl +++ b/test/structural_transformation/utils.jl @@ -191,11 +191,6 @@ end end @testset "`map_variables_to_equations`" begin - @testset "Not supported for systems without `.tearing_state`" begin - @variables x - @mtkbuild sys = OptimizationSystem(x^2) - @test_throws ArgumentError map_variables_to_equations(sys) - end @testset "Requires simplified system" begin @variables x(t) y(t) @named sys = System([D(x) ~ x, y ~ 2x], t) @@ -299,7 +294,7 @@ end eqs = [D(x) ~ dx D(dx) ~ ddx dx ~ (k - x) / T] - return ODESystem(eqs, t, vars, params; systems, name) + return System(eqs, t, vars, params; systems, name) end @component function FilteredInputExplicit(; name, x0 = 0, T = 0.1) @@ -317,7 +312,7 @@ end D(dx) ~ ddx D(k[1]) ~ 1.0 dx ~ (k[1] - x) / T] - return ODESystem(eqs, t, vars, params; systems, name) + return System(eqs, t, vars, params; systems, name) end @component function FilteredInputErr(; name, x0 = 0, T = 0.1) @@ -335,7 +330,7 @@ end D(dx) ~ ddx dx ~ (k - x) / T D(k) ~ missing] - return ODESystem(eqs, t, vars, params; systems, name) + return System(eqs, t, vars, params; systems, name) end @named sys = FilteredInputErr() @@ -376,7 +371,7 @@ end eqs = [D(x) ~ dx D(dx) ~ ddx dx ~ (k(t) - x) / T] - return ODESystem(eqs, t, vars, params; systems, name) + return System(eqs, t, vars, params; systems, name) end @mtkbuild sys = FilteredInput2() From 2b7124bdc1319fecc0a3bd45c8adcb95ea4e8ea0 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Tue, 29 Apr 2025 21:21:27 +0530 Subject: [PATCH 150/235] test: simplify test for metadata retention in `complete` --- test/components.jl | 37 +++++-------------------------------- 1 file changed, 5 insertions(+), 32 deletions(-) diff --git a/test/components.jl b/test/components.jl index 19f529bd0a..7680afc50c 100644 --- a/test/components.jl +++ b/test/components.jl @@ -327,36 +327,9 @@ end @testset "Issue#3275: Metadata retained on `complete`" begin @variables x(t) y(t) - @testset "ODESystem" begin - @named inner = System(D(x) ~ x, t) - @named outer = System(D(y) ~ y, t; systems = [inner], metadata = "test") - @test ModelingToolkit.get_metadata(outer) == "test" - sys = complete(outer) - @test ModelingToolkit.get_metadata(sys) == "test" - end - @testset "NonlinearSystem" begin - @named inner = System([0 ~ x^2 + 4x + 4], [x], []) - @named outer = System( - [0 ~ x^3 - y^3], [x, y], []; systems = [inner], metadata = "test") - @test ModelingToolkit.get_metadata(outer) == "test" - sys = complete(outer) - @test ModelingToolkit.get_metadata(sys) == "test" - end - k = ShiftIndex(t) - @testset "DiscreteSystem" begin - @named inner = System([x(k) ~ x(k - 1) + x(k - 2)], t, [x], []) - @named outer = System([y(k) ~ y(k - 1) + y(k - 2)], t, [x, y], - []; systems = [inner], metadata = "test") - @test ModelingToolkit.get_metadata(outer) == "test" - sys = complete(outer) - @test ModelingToolkit.get_metadata(sys) == "test" - end - @testset "OptimizationSystem" begin - @named inner = OptimizationSystem(x^2 + y^2 - 3, [x, y], []) - @named outer = OptimizationSystem( - x^3 - y, [x, y], []; systems = [inner], metadata = "test") - @test ModelingToolkit.get_metadata(outer) == "test" - sys = complete(outer) - @test ModelingToolkit.get_metadata(sys) == "test" - end + @named inner = System(D(x) ~ x, t) + @named outer = System(D(y) ~ y, t; systems = [inner], metadata = "test") + @test ModelingToolkit.get_metadata(outer) == "test" + sys = complete(outer) + @test ModelingToolkit.get_metadata(sys) == "test" end From 22a175f8b3d898d8288c71147c702c944d65344c Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Tue, 29 Apr 2025 21:22:11 +0530 Subject: [PATCH 151/235] test: improve readability of dependency graph tests --- test/dep_graphs.jl | 290 +++++++++++++++++++++------------------------ 1 file changed, 136 insertions(+), 154 deletions(-) diff --git a/test/dep_graphs.jl b/test/dep_graphs.jl index 3c7b88dd05..04e6bf159a 100644 --- a/test/dep_graphs.jl +++ b/test/dep_graphs.jl @@ -6,74 +6,7 @@ import ModelingToolkit: value ################################# # testing for Jumps / all dgs ################################# -@parameters k1 k2 -@variables S(t) I(t) R(t) -j₁ = MassActionJump(k1, [0 => 1], [S => 1]) -j₂ = MassActionJump(k1, [S => 1], [S => -1]) -j₃ = MassActionJump(k2, [S => 1, I => 1], [S => -1, I => 1]) -j₄ = MassActionJump(k2, [S => 2, R => 1], [R => -1]) -j₅ = ConstantRateJump(k1 * I, [R ~ R + 1]) -j₆ = VariableRateJump(k1 * k2 / (1 + t) * S, [S ~ S - 1, R ~ R + 1]) -eqs = [j₁, j₂, j₃, j₄, j₅, j₆] -@named js = JumpSystem(eqs, t, [S, I, R], [k1, k2]) -S = value(S) -I = value(I) -R = value(R) -k1 = value(k1) -k2 = value(k2) -# eq to vars they depend on -eq_sdeps = [Variable[], [S], [S, I], [S, R], [I], [S]] -eq_sidepsf = [Int[], [1], [1, 2], [1, 3], [2], [1]] -eq_sidepsb = [[2, 3, 4, 6], [3, 5], [4]] -deps = equation_dependencies(js) -@test all(i -> isequal(Set(eq_sdeps[i]), Set(deps[i])), 1:length(eqs)) -depsbg = asgraph(js) -@test depsbg.fadjlist == eq_sidepsf -@test depsbg.badjlist == eq_sidepsb - -# eq to params they depend on -eq_pdeps = [[k1], [k1], [k2], [k2], [k1], [k1, k2]] -eq_pidepsf = [[1], [1], [2], [2], [1], [1, 2]] -eq_pidepsb = [[1, 2, 5, 6], [3, 4, 6]] -deps = equation_dependencies(js, variables = parameters(js)) -@test all(i -> isequal(Set(eq_pdeps[i]), Set(deps[i])), 1:length(eqs)) -depsbg2 = asgraph(js, variables = parameters(js)) -@test depsbg2.fadjlist == eq_pidepsf -@test depsbg2.badjlist == eq_pidepsb - -# var to eqs that modify them -s_eqdepsf = [[1, 2, 3, 6], [3], [4, 5, 6]] -s_eqdepsb = [[1], [1], [1, 2], [3], [3], [1, 3]] -ne = 8 -bg = BipartiteGraph(ne, s_eqdepsf, s_eqdepsb) -deps2 = variable_dependencies(js) -@test isequal(bg, deps2) - -# eq to eqs that depend on them -eq_eqdeps = [[2, 3, 4, 6], [2, 3, 4, 6], [2, 3, 4, 5, 6], [4], [4], [2, 3, 4, 6]] -dg = SimpleDiGraph(6) -for (eqidx, eqdeps) in enumerate(eq_eqdeps) - for eqdepidx in eqdeps - add_edge!(dg, eqidx, eqdepidx) - end -end -dg3 = eqeq_dependencies(depsbg, deps2) -@test dg == dg3 - -# var to vars that depend on them -var_vardeps = [[1, 2, 3], [1, 2, 3], [3]] -ne = 7 -dg = SimpleDiGraph(3) -for (vidx, vdeps) in enumerate(var_vardeps) - for vdepidx in vdeps - add_edge!(dg, vidx, vdepidx) - end -end -dg4 = varvar_dependencies(depsbg, deps2) -@test dg == dg4 - -# testing when ignoring VariableRateJumps -let +@testset "JumpSystem" begin @parameters k1 k2 @variables S(t) I(t) R(t) j₁ = MassActionJump(k1, [0 => 1], [S => 1]) @@ -82,109 +15,158 @@ let j₄ = MassActionJump(k2, [S => 2, R => 1], [R => -1]) j₅ = ConstantRateJump(k1 * I, [R ~ R + 1]) j₆ = VariableRateJump(k1 * k2 / (1 + t) * S, [S ~ S - 1, R ~ R + 1]) - eqs = [j₁, j₂, j₃, j₄, j₅, j₆] - @named js = JumpSystem(eqs, t, [S, I, R], [k1, k2]) + alleqs = [j₁, j₂, j₃, j₄, j₅, j₆] + @named js = JumpSystem(alleqs, t, [S, I, R], [k1, k2]) S = value(S) I = value(I) R = value(R) k1 = value(k1) k2 = value(k2) - # eq to vars they depend on - eq_sdeps = [Variable[], [S], [S, I], [S, R], [I]] - eq_sidepsf = [Int[], [1], [1, 2], [1, 3], [2]] - eq_sidepsb = [[2, 3, 4], [3, 5], [4]] - - # filter out vrjs in making graphs - eqs = ArrayPartition(equations(js).x[1], equations(js).x[2]) - deps = equation_dependencies(js; eqs) - @test length(deps) == length(eq_sdeps) - @test all(i -> isequal(Set(eq_sdeps[i]), Set(deps[i])), 1:length(eqs)) - depsbg = asgraph(js; eqs) - @test depsbg.fadjlist == eq_sidepsf - @test depsbg.badjlist == eq_sidepsb - # eq to params they depend on - eq_pdeps = [[k1], [k1], [k2], [k2], [k1]] - eq_pidepsf = [[1], [1], [2], [2], [1]] - eq_pidepsb = [[1, 2, 5], [3, 4]] - deps = equation_dependencies(js; variables = parameters(js), eqs) - @test length(deps) == length(eq_pdeps) - @test all(i -> isequal(Set(eq_pdeps[i]), Set(deps[i])), 1:length(eqs)) - depsbg2 = asgraph(js; variables = parameters(js), eqs) - @test depsbg2.fadjlist == eq_pidepsf - @test depsbg2.badjlist == eq_pidepsb - - # var to eqs that modify them - s_eqdepsf = [[1, 2, 3], [3], [4, 5]] - s_eqdepsb = [[1], [1], [1, 2], [3], [3]] - ne = 6 - bg = BipartiteGraph(ne, s_eqdepsf, s_eqdepsb) - deps2 = variable_dependencies(js; eqs) - @test isequal(bg, deps2) - - # eq to eqs that depend on them - eq_eqdeps = [[2, 3, 4], [2, 3, 4], [2, 3, 4, 5], [4], [4], [2, 3, 4]] - dg = SimpleDiGraph(5) - for (eqidx, eqdeps) in enumerate(eq_eqdeps) - for eqdepidx in eqdeps - add_edge!(dg, eqidx, eqdepidx) + test_case_1 = (; + eqs = jumps(js), + # eq to vars they depend on + eq_sdeps = [Variable[], [S], [S, I], [S, R], [I], [S]], + eq_sidepsf = [Int[], [1], [1, 2], [1, 3], [2], [1]], + eq_sidepsb = [[2, 3, 4, 6], [3, 5], [4]], + # eq to params they depend on + eq_pdeps = [[k1], [k1], [k2], [k2], [k1], [k1, k2]], + eq_pidepsf = [[1], [1], [2], [2], [1], [1, 2]], + eq_pidepsb = [[1, 2, 5, 6], [3, 4, 6]], + # var to eqs that modify them + s_eqdepsf = [[1, 2, 3, 6], [3], [4, 5, 6]], + s_eqdepsb = [[1], [1], [1, 2], [3], [3], [1, 3]], + var_eq_ne = 8, + # eq to eqs that depend on them + eq_eqdeps = [[2, 3, 4, 6], [2, 3, 4, 6], [2, 3, 4, 5, 6], [4], [4], [2, 3, 4, 6]], + eq_eq_ne = 6, + # var to vars that depend on them + var_vardeps = [[1, 2, 3], [1, 2, 3], [3]], + var_var_ne = 3 + ) + # testing when ignoring VariableRateJumps + test_case_2 = (; + # filter out vrjs in making graphs + eqs = filter(x -> !(x isa VariableRateJump), jumps(js)), + # eq to vars they depend on + eq_sdeps = [Variable[], [S], [S, I], [S, R], [I]], + eq_sidepsf = [Int[], [1], [1, 2], [1, 3], [2]], + eq_sidepsb = [[2, 3, 4], [3, 5], [4]], + # eq to params they depend on + eq_pdeps = [[k1], [k1], [k2], [k2], [k1]], + eq_pidepsf = [[1], [1], [2], [2], [1]], + eq_pidepsb = [[1, 2, 5], [3, 4]], + # var to eqs that modify them + s_eqdepsf = [[1, 2, 3], [3], [4, 5]], + s_eqdepsb = [[1], [1], [1, 2], [3], [3]], + var_eq_ne = 6, + # eq to eqs that depend on them + eq_eqdeps = [[2, 3, 4], [2, 3, 4], [2, 3, 4, 5], [4], [4], [2, 3, 4]], + eq_eq_ne = 5, + # var to vars that depend on them + var_vardeps = [[1, 2, 3], [1, 2, 3], [3]], + var_var_ne = 3 + ) + + @testset "Case $i" for (i, test_case) in enumerate([test_case_1, test_case_2]) + (; # filter out vrjs in making graphs + eqs, # eq to vars they depend on + eq_sdeps, + eq_sidepsf, + eq_sidepsb, # eq to params they depend on + eq_pdeps, + eq_pidepsf, + eq_pidepsb, # var to eqs that modify them + s_eqdepsf, + s_eqdepsb, + var_eq_ne, # eq to eqs that depend on them + eq_eqdeps, + eq_eq_ne, # var to vars that depend on them + var_vardeps, + var_var_ne +) = test_case + deps = equation_dependencies(js; eqs) + @test length(deps) == length(eq_sdeps) + @test all([issetequal(a, b) for (a, b) in zip(eq_sdeps, deps)]) + # @test all(i -> ) + # @test all(i -> isequal(Set(eq_sdeps[i]), Set(deps[i])), 1:length(alleqs)) + depsbg = asgraph(js; eqs) + @test depsbg.fadjlist == eq_sidepsf + @test depsbg.badjlist == eq_sidepsb + + deps = equation_dependencies(js; variables = parameters(js), eqs) + @test length(deps) == length(eq_pdeps) + @test all([issetequal(a, b) for (a, b) in zip(eq_pdeps, deps)]) + depsbg2 = asgraph(js; variables = parameters(js), eqs) + @test depsbg2.fadjlist == eq_pidepsf + @test depsbg2.badjlist == eq_pidepsb + + bg = BipartiteGraph(var_eq_ne, s_eqdepsf, s_eqdepsb) + deps2 = variable_dependencies(js; eqs) + @test isequal(bg, deps2) + + dg = SimpleDiGraph(eq_eq_ne) + for (eqidx, eqdeps) in enumerate(eq_eqdeps) + for eqdepidx in eqdeps + add_edge!(dg, eqidx, eqdepidx) + end end - end - dg3 = eqeq_dependencies(depsbg, deps2) - @test dg == dg3 - - # var to vars that depend on them - var_vardeps = [[1, 2, 3], [1, 2, 3], [3]] - ne = 7 - dg = SimpleDiGraph(3) - for (vidx, vdeps) in enumerate(var_vardeps) - for vdepidx in vdeps - add_edge!(dg, vidx, vdepidx) + dg3 = eqeq_dependencies(depsbg, deps2) + @test dg == dg3 + + dg = SimpleDiGraph(var_var_ne) + for (vidx, vdeps) in enumerate(var_vardeps) + for vdepidx in vdeps + add_edge!(dg, vidx, vdepidx) + end end + dg4 = varvar_dependencies(depsbg, deps2) + @test dg == dg4 end - dg4 = varvar_dependencies(depsbg, deps2) - @test dg == dg4 end ##################################### # testing for ODE/SDEs ##################################### -@parameters k1 k2 -@variables S(t) I(t) R(t) -eqs = [D(S) ~ k1 - k1 * S - k2 * S * I - k1 * k2 / (1 + t) * S, - D(I) ~ k2 * S * I, - D(R) ~ -k2 * S^2 * R / 2 + k1 * I + k1 * k2 * S / (1 + t)] -noiseeqs = [S, I, R] -@named os = System(eqs, t, [S, I, R], [k1, k2]) -deps = equation_dependencies(os) -S = value(S); -I = value(I); -R = value(R); -k1 = value(k1); -k2 = value(k2); -eq_sdeps = [[S, I], [S, I], [S, I, R]] -@test all(i -> isequal(Set(eq_sdeps[i]), Set(deps[i])), 1:length(deps)) -@parameters k1 k2 -@variables S(t) I(t) R(t) -@named sdes = SDESystem(eqs, noiseeqs, t, [S, I, R], [k1, k2]) -deps = equation_dependencies(sdes) -@test all(i -> isequal(Set(eq_sdeps[i]), Set(deps[i])), 1:length(deps)) +@testset "ODEs, SDEs" begin + @parameters k1 k2 + @variables S(t) I(t) R(t) + eqs = [D(S) ~ k1 - k1 * S - k2 * S * I - k1 * k2 / (1 + t) * S + D(I) ~ k2 * S * I + D(R) ~ -k2 * S^2 * R / 2 + k1 * I + k1 * k2 * S / (1 + t)] + @named os = System(eqs, t, [S, I, R], [k1, k2]) + deps = equation_dependencies(os) + S = value(S) + I = value(I) + R = value(R) + k1 = value(k1) + k2 = value(k2) + eq_sdeps = [[S, I], [S, I], [S, I, R]] + @test all(i -> isequal(Set(eq_sdeps[i]), Set(deps[i])), 1:length(deps)) + + noiseeqs = [S, I, R] + @named sdes = SDESystem(eqs, noiseeqs, t, [S, I, R], [k1, k2]) + deps = equation_dependencies(sdes) + @test all(i -> isequal(Set(eq_sdeps[i]), Set(deps[i])), 1:length(deps)) -deps = variable_dependencies(os) -s_eqdeps = [[1], [2], [3]] -@test deps.fadjlist == s_eqdeps + deps = variable_dependencies(os) + s_eqdeps = [[1], [2], [3]] + @test deps.fadjlist == s_eqdeps +end ##################################### # testing for nonlin sys ##################################### -@variables x y z -@parameters σ ρ β - -eqs = [0 ~ σ * (y - x), - 0 ~ ρ - y, - 0 ~ y - β * z] -@named ns = System(eqs, [x, y, z], [σ, ρ, β]) -deps = equation_dependencies(ns) -eq_sdeps = [[x, y], [y], [y, z]] -@test all(i -> isequal(Set(deps[i]), Set(value.(eq_sdeps[i]))), 1:length(deps)) +@testset "Nonlinear" begin + @variables x y z + @parameters σ ρ β + + eqs = [0 ~ σ * (y - x), + 0 ~ ρ - y, + 0 ~ y - β * z] + @named ns = System(eqs, [x, y, z], [σ, ρ, β]) + deps = equation_dependencies(ns) + eq_sdeps = [[x, y], [y], [y, z]] + @test all(i -> isequal(Set(deps[i]), Set(value.(eq_sdeps[i]))), 1:length(deps)) +end From f82ff93c0ad66387cdb47cd263894b29b91aedff Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Tue, 29 Apr 2025 21:22:29 +0530 Subject: [PATCH 152/235] test: fix usage of `ODEProblemExpr` in lowering test --- test/lowering_solving.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/lowering_solving.jl b/test/lowering_solving.jl index 8c4db26287..280848f6ad 100644 --- a/test/lowering_solving.jl +++ b/test/lowering_solving.jl @@ -33,7 +33,7 @@ tspan = (0.0, 100.0) sys = complete(sys) prob = ODEProblem(sys, u0, tspan, p, jac = true) -probexpr = ODEProblemExpr(sys, u0, tspan, p, jac = true) +probexpr = ODEProblem(sys, u0, tspan, p; jac = true, expression = Val{true}) sol = solve(prob, Tsit5()) solexpr = solve(eval(prob), Tsit5()) @test all(x -> x == 0, Array(sol - solexpr)) From b55e28f5b645d93e9c89c7bbeffdc29c44788ae3 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Tue, 29 Apr 2025 21:22:44 +0530 Subject: [PATCH 153/235] test: remove test for specifying type of system in `@mtkmodel` --- test/model_parsing.jl | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/test/model_parsing.jl b/test/model_parsing.jl index 7271d9bd1d..705ec79816 100644 --- a/test/model_parsing.jl +++ b/test/model_parsing.jl @@ -1012,21 +1012,6 @@ end end end -@testset "Specify the type of system" begin - @mtkmodel Float2Bool::DiscreteSystem begin - @variables begin - u(t)::Float64 - y(t)::Bool - end - @equations begin - y ~ u != 0 - end - end - - @named sys = Float2Bool() - @test typeof(sys) == DiscreteSystem -end - @testset "Constraints, costs, consolidate" begin @mtkmodel Example begin @variables begin From d5fd328c3b14caba394beb24a7aeb13f95b03999 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Tue, 29 Apr 2025 21:23:05 +0530 Subject: [PATCH 154/235] test: fix parameter dependencies test --- test/parameter_dependencies.jl | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/test/parameter_dependencies.jl b/test/parameter_dependencies.jl index 71ca78a101..624dbe8b6b 100644 --- a/test/parameter_dependencies.jl +++ b/test/parameter_dependencies.jl @@ -78,14 +78,14 @@ end t ) @named sys2 = System( - [], + Equation[], t; parameter_dependencies = [p2 => 2p1] ) sys = extend(sys2, sys1) @test !(p2 in Set(parameters(sys))) @test p2 in Set(full_parameters(sys)) - prob = ODEProblem(complete(sys)) + prob = ODEProblem(complete(sys), nothing, (0.0, 1.0)) get_dep = getu(prob, 2p2) @test get_dep(prob) == 4 end @@ -99,7 +99,7 @@ end t; parameter_dependencies = [p2 => 2p1] ) - prob = ODEProblem(complete(sys)) + prob = ODEProblem(complete(sys), nothing, (0.0, 1.0)) get_dep = getu(prob, 2p2) @test get_dep(prob) == 4 end @@ -131,9 +131,9 @@ end t; parameter_dependencies = [p2 => 2p1] ) - sys = complete(System([], t, systems = [sys1, sys2], name = :sys)) + sys = complete(System(Equation[], t, systems = [sys1, sys2], name = :sys)) - prob = ODEProblem(sys) + prob = ODEProblem(sys, [], (0.0, 1.0)) v1 = sys.sys2.p2 v2 = 2 * v1 @test is_observed(prob, v1) From ce50a4d3f3b8d25617450a5585b5b377c4937e28 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Tue, 29 Apr 2025 21:23:14 +0530 Subject: [PATCH 155/235] test: fix symbolic events test --- test/symbolic_events.jl | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/test/symbolic_events.jl b/test/symbolic_events.jl index 35a5369869..1d611360a3 100644 --- a/test/symbolic_events.jl +++ b/test/symbolic_events.jl @@ -12,6 +12,10 @@ using SymbolicIndexingInterface using Setfield rng = StableRNG(12345) +function get_callback(prob) + prob.kwargs[:callback] +end + @variables x(t) = 0 eqs = [D(x) ~ 1] @@ -555,8 +559,7 @@ end 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...) + jprob = JumpProblem(jsys, u0, tspan, p; aggregator = 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.0, 1.0]) @@ -1225,13 +1228,13 @@ end @named wd1 = weird1(0.021) @named wd2 = weird2(0.021) - sys1 = structural_simplify(System([], t; name = :parent, + sys1 = structural_simplify(System(Equation[], 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(System([], t; name = :parent, + sys2 = structural_simplify(System(Equation[], 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 From 9788f3ed97f240cf9c75ab78561286e9c8c54f2e Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Tue, 29 Apr 2025 22:44:18 +0530 Subject: [PATCH 156/235] test: fix modelingtoolkitize test --- test/modelingtoolkitize.jl | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/test/modelingtoolkitize.jl b/test/modelingtoolkitize.jl index 04e6263c7c..0157a50acb 100644 --- a/test/modelingtoolkitize.jl +++ b/test/modelingtoolkitize.jl @@ -253,7 +253,6 @@ prob = ODEProblem(ode_prob_dict, u0, (0.0, 1.0), params) sys = modelingtoolkitize(prob) @test [ModelingToolkit.defaults(sys)[s] for s in unknowns(sys)] == u0 @test [ModelingToolkit.defaults(sys)[s] for s in parameters(sys)] == [10, 20] -@test ModelingToolkit.has_tspan(sys) @parameters sig=10 rho=28.0 beta=8 / 3 @variables x(t)=100 y(t)=1.0 z(t)=1 @@ -266,10 +265,9 @@ noiseeqs = [0.1 * x, 0.1 * y, 0.1 * z] -@named sys = SDESystem(eqs, noiseeqs, t, [x, y, z], [sig, rho, beta]; tspan = (0, 1000.0)) -prob = SDEProblem(complete(sys)) +@named sys = SDESystem(eqs, noiseeqs, t, [x, y, z], [sig, rho, beta]) +prob = SDEProblem(complete(sys), nothing, (0.0, 1.0)) sys = modelingtoolkitize(prob) -@test ModelingToolkit.has_tspan(sys) @testset "Explicit variable names" begin function fn(du, u, p::NamedTuple, t) From e2a94aa7c94f422e193d73671b946b13cfe40b5a Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Thu, 1 May 2025 13:56:50 +0530 Subject: [PATCH 157/235] test: remove outdated test We allow time-dependent parameter derivatives now --- test/odesystem.jl | 8 -------- 1 file changed, 8 deletions(-) diff --git a/test/odesystem.jl b/test/odesystem.jl index defef0bbaa..8b24691fea 100644 --- a/test/odesystem.jl +++ b/test/odesystem.jl @@ -851,14 +851,6 @@ let @test string.(independent_variables(prob.f.sys)) == ["t"] end -let - @parameters P(t) Q(t) - ∂t = D - eqs = [∂t(Q) ~ 0.2P - ∂t(P) ~ -80.0sin(Q)] - @test_throws ArgumentError @named sys = System(eqs, t) -end - @parameters C L R @variables q(t) p(t) F(t) From 63feb394d134a2c8306a5e29dd9a5feb3935d801 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Thu, 1 May 2025 15:55:30 +0530 Subject: [PATCH 158/235] refactor: remove old `modelingtoolkitize(::OptimizationProblem)` --- .../optimization/modelingtoolkitize.jl | 148 ------------------ 1 file changed, 148 deletions(-) delete mode 100644 src/systems/optimization/modelingtoolkitize.jl diff --git a/src/systems/optimization/modelingtoolkitize.jl b/src/systems/optimization/modelingtoolkitize.jl deleted file mode 100644 index 27dccc251a..0000000000 --- a/src/systems/optimization/modelingtoolkitize.jl +++ /dev/null @@ -1,148 +0,0 @@ -""" -$(TYPEDSIGNATURES) - -Generate `OptimizationSystem`, dependent variables, and parameters from an `OptimizationProblem`. -""" -function modelingtoolkitize(prob::DiffEqBase.OptimizationProblem; - u_names = nothing, p_names = nothing, kwargs...) - num_cons = isnothing(prob.lcons) ? 0 : length(prob.lcons) - if prob.p isa Tuple || prob.p isa NamedTuple - p = [x for x in prob.p] - else - p = prob.p - end - has_p = !(p isa Union{DiffEqBase.NullParameters, Nothing}) - if u_names !== nothing - varnames_length_check(prob.u0, u_names; is_unknowns = true) - _vars = [variable(name) for name in u_names] - elseif SciMLBase.has_sys(prob.f) - varnames = getname.(variable_symbols(prob.f.sys)) - varidxs = variable_index.((prob.f.sys,), varnames) - invpermute!(varnames, varidxs) - _vars = [variable(name) for name in varnames] - if prob.f.sys isa OptimizationSystem - for (i, sym) in enumerate(variable_symbols(prob.f.sys)) - if hasbounds(sym) - _vars[i] = Symbolics.setmetadata( - _vars[i], VariableBounds, getbounds(sym)) - end - end - end - else - _vars = [variable(:x, i) for i in eachindex(prob.u0)] - end - _vars = reshape(_vars, size(prob.u0)) - vars = ArrayInterface.restructure(prob.u0, _vars) - if prob.ub !== nothing # lb is also !== nothing - vars = map(vars, prob.lb, prob.ub) do sym, lb, ub - if iszero(lb) && iszero(ub) || isinf(lb) && lb < 0 && isinf(ub) && ub > 0 - sym - else - Symbolics.setmetadata(sym, VariableBounds, (lb, ub)) - end - end - end - params = if has_p - if p_names === nothing && SciMLBase.has_sys(prob.f) - p_names = Dict(parameter_index(prob.f.sys, sym) => sym - for sym in parameter_symbols(prob.f.sys)) - end - if p isa MTKParameters - old_to_new = Dict() - for sym in parameter_symbols(prob) - idx = parameter_index(prob, sym) - old_to_new[unwrap(sym)] = unwrap(p_names[idx]) - end - order = reorder_parameters(prob.f.sys) - for arr in order - for i in eachindex(arr) - arr[i] = old_to_new[arr[i]] - end - end - _params = order - else - _params = define_params(p, p_names) - end - p isa Number ? _params[1] : - (p isa Tuple || p isa NamedTuple || p isa AbstractDict || p isa MTKParameters ? - _params : - ArrayInterface.restructure(p, _params)) - else - [] - end - - if p isa MTKParameters - eqs = prob.f(vars, params...) - else - eqs = prob.f(vars, params) - end - - if DiffEqBase.isinplace(prob) && !isnothing(prob.f.cons) - lhs = Array{Num}(undef, num_cons) - if p isa MTKParameters - prob.f.cons(lhs, vars, params...) - else - prob.f.cons(lhs, vars, params) - end - cons = Union{Equation, Inequality}[] - - if !isnothing(prob.lcons) - for i in 1:num_cons - if !isinf(prob.lcons[i]) - if prob.lcons[i] != prob.ucons[i] - push!(cons, prob.lcons[i] ≲ lhs[i]) - else - push!(cons, lhs[i] ~ prob.ucons[i]) - end - end - end - end - - if !isnothing(prob.ucons) - for i in 1:num_cons - if !isinf(prob.ucons[i]) && prob.lcons[i] != prob.ucons[i] - push!(cons, lhs[i] ≲ prob.ucons[i]) - end - end - end - if (isnothing(prob.lcons) || all(isinf, prob.lcons)) && - (isnothing(prob.ucons) || all(isinf, prob.ucons)) - throw(ArgumentError("Constraints passed have no proper bounds defined. - Ensure you pass equal bounds (the scalar that the constraint should evaluate to) for equality constraints - or pass the lower and upper bounds for inequality constraints.")) - end - elseif !isnothing(prob.f.cons) - cons = p isa MTKParameters ? prob.f.cons(vars, params...) : - prob.f.cons(vars, params) - else - cons = [] - end - params = values(params) - params = if params isa Number || (params isa Array && ndims(params) == 0) - [params[1]] - elseif p isa MTKParameters - reduce(vcat, params) - else - vec(collect(params)) - end - - sts = vec(collect(vars)) - default_u0 = Dict(sts .=> vec(collect(prob.u0))) - default_p = if has_p - if prob.p isa AbstractDict - Dict(v => prob.p[k] for (k, v) in pairs(_params)) - elseif prob.p isa MTKParameters - Dict(params .=> reduce(vcat, prob.p)) - else - Dict(params .=> vec(collect(prob.p))) - end - else - Dict() - end - de = OptimizationSystem(eqs, sts, params; - name = gensym(:MTKizedOpt), - constraints = cons, - defaults = merge(default_u0, default_p), - kwargs...) - de -end From a70615038069b76a893e72161df03ea263cec260 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Thu, 1 May 2025 15:55:40 +0530 Subject: [PATCH 159/235] feat: add `modelingtoolkitize(::OptimizationProblem)` --- src/ModelingToolkit.jl | 3 +- src/modelingtoolkitize/optimizationproblem.jl | 95 +++++++++++++++++++ 2 files changed, 96 insertions(+), 2 deletions(-) create mode 100644 src/modelingtoolkitize/optimizationproblem.jl diff --git a/src/ModelingToolkit.jl b/src/ModelingToolkit.jl index c22d78f30f..c4909524c4 100644 --- a/src/ModelingToolkit.jl +++ b/src/ModelingToolkit.jl @@ -185,8 +185,7 @@ include("problems/bvproblem.jl") include("modelingtoolkitize/common.jl") include("modelingtoolkitize/odeproblem.jl") include("modelingtoolkitize/sdeproblem.jl") - -include("systems/optimization/modelingtoolkitize.jl") +include("modelingtoolkitize/optimizationproblem.jl") include("systems/nonlinear/homotopy_continuation.jl") include("systems/nonlinear/modelingtoolkitize.jl") diff --git a/src/modelingtoolkitize/optimizationproblem.jl b/src/modelingtoolkitize/optimizationproblem.jl new file mode 100644 index 0000000000..26d557152a --- /dev/null +++ b/src/modelingtoolkitize/optimizationproblem.jl @@ -0,0 +1,95 @@ +""" + $(TYPEDSIGNATURES) + +Convert an `OptimizationProblem` to a `ModelingToolkit.System`. + +# Keyword arguments + +- `u_names`: An array of names of the same size as `prob.u0` to use as the names of the + unknowns of the system. The names should be given as `Symbol`s. +- `p_names`: A collection of names to use for parameters of the system. The collection + should have keys corresponding to indexes of `prob.p`. For example, if `prob.p` is an + associative container like `NamedTuple`, then `p_names` should map keys of `prob.p` to + the name that the corresponding parameter should have in the returned system. The names + should be given as `Symbol`s. + +All other keyword arguments are forwarded to the created `System`. +""" +function modelingtoolkitize( + prob::OptimizationProblem; u_names = nothing, p_names = nothing, + kwargs...) + num_cons = isnothing(prob.lcons) ? 0 : length(prob.lcons) + p = prob.p + has_p = !(p isa Union{DiffEqBase.NullParameters, Nothing}) + + vars = construct_vars(prob, nothing, u_names) + if prob.ub !== nothing # lb is also !== nothing + vars = map(vars, prob.lb, prob.ub) do sym, lb, ub + if iszero(lb) && iszero(ub) || isinf(lb) && lb < 0 && isinf(ub) && ub > 0 + sym + else + Symbolics.setmetadata(sym, VariableBounds, (lb, ub)) + end + end + end + params = construct_params(prob, nothing, p_names) + + objective = prob.f(vars, params) + + if prob.f.cons === nothing + cons = [] + else + if DiffEqBase.isinplace(prob.f) + lhs = Array{Num}(undef, num_cons) + prob.f.cons(lhs, vars, params) + else + lhs = prob.f.cons(vars, params) + end + cons = Union{Equation, Inequality}[] + + if !isnothing(prob.lcons) + for i in 1:num_cons + if !isinf(prob.lcons[i]) + if prob.lcons[i] != prob.ucons[i] + push!(cons, prob.lcons[i] ≲ lhs[i]) + else + push!(cons, lhs[i] ~ prob.ucons[i]) + end + end + end + end + + if !isnothing(prob.ucons) + for i in 1:num_cons + if !isinf(prob.ucons[i]) && prob.lcons[i] != prob.ucons[i] + push!(cons, lhs[i] ≲ prob.ucons[i]) + end + end + end + + if (isnothing(prob.lcons) || all(isinf, prob.lcons)) && + (isnothing(prob.ucons) || all(isinf, prob.ucons)) + throw(ArgumentError("Constraints passed have no proper bounds defined. + Ensure you pass equal bounds (the scalar that the constraint should evaluate to) for equality constraints + or pass the lower and upper bounds for inequality constraints.")) + end + end + + # turn `params` into a list of symbolic variables as opposed to + # a parameter object containing symbolic variables. + _params = params + params = to_paramvec(params) + + defaults = defaults_from_u0_p(prob, vars, _params, params) + # In case initials crept in, specifically from when we constructed parameters + # using prob.f.sys + filter!(x -> !iscall(x) || !(operation(x) isa Initial), params) + filter!(x -> !iscall(x[1]) || !(operation(x[1]) isa Initial), defaults) + + sts = vec(collect(vars)) + sys = OptimizationSystem(objective, sts, params; + defaults, + constraints = cons, + name = gensym(:MTKizedOpt), + kwargs...) +end From 72fd93090e6f0a2e586bb1a0c47e2bb41c281d5e Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Thu, 1 May 2025 16:10:57 +0530 Subject: [PATCH 160/235] refactor: remove old `modelingtoolkitize(::NonlinearProblem)` --- src/ModelingToolkit.jl | 1 - src/systems/nonlinear/modelingtoolkitize.jl | 85 --------------------- 2 files changed, 86 deletions(-) delete mode 100644 src/systems/nonlinear/modelingtoolkitize.jl diff --git a/src/ModelingToolkit.jl b/src/ModelingToolkit.jl index c4909524c4..cd79a0a553 100644 --- a/src/ModelingToolkit.jl +++ b/src/ModelingToolkit.jl @@ -188,7 +188,6 @@ include("modelingtoolkitize/sdeproblem.jl") include("modelingtoolkitize/optimizationproblem.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/basic_transformations.jl") diff --git a/src/systems/nonlinear/modelingtoolkitize.jl b/src/systems/nonlinear/modelingtoolkitize.jl deleted file mode 100644 index 4ce3769ef7..0000000000 --- a/src/systems/nonlinear/modelingtoolkitize.jl +++ /dev/null @@ -1,85 +0,0 @@ -""" -$(TYPEDSIGNATURES) - -Generate `System`, dependent variables, and parameters from an `NonlinearProblem`. -""" -function modelingtoolkitize( - prob::Union{NonlinearProblem, NonlinearLeastSquaresProblem}; - u_names = nothing, p_names = nothing, kwargs...) - p = prob.p - has_p = !(p isa Union{DiffEqBase.NullParameters, Nothing}) - - if u_names !== nothing - varnames_length_check(prob.u0, u_names; is_unknowns = true) - _vars = [variable(name) for name in u_names] - elseif SciMLBase.has_sys(prob.f) - varnames = getname.(variable_symbols(prob.f.sys)) - varidxs = variable_index.((prob.f.sys,), varnames) - invpermute!(varnames, varidxs) - _vars = [variable(name) for name in varnames] - else - _vars = [variable(:x, i) for i in eachindex(prob.u0)] - end - _vars = reshape(_vars, size(prob.u0)) - - vars = prob.u0 isa Number ? _vars : ArrayInterface.restructure(prob.u0, _vars) - params = if has_p - if p_names === nothing && SciMLBase.has_sys(prob.f) - p_names = Dict(parameter_index(prob.f.sys, sym) => sym - for sym in parameter_symbols(prob.f.sys)) - end - _params = define_params(p, p_names) - p isa Number ? _params[1] : - (p isa Tuple || p isa NamedTuple || p isa AbstractDict || p isa MTKParameters ? - _params : - ArrayInterface.restructure(p, _params)) - else - [] - end - - if DiffEqBase.isinplace(prob) - if prob isa NonlinearLeastSquaresProblem - rhs = ArrayInterface.restructure( - prob.f.resid_prototype, similar(prob.f.resid_prototype, Num)) - prob.f(rhs, vars, params) - eqs = vcat([0.0 ~ rhs[i] for i in 1:length(prob.f.resid_prototype)]...) - else - rhs = ArrayInterface.restructure(prob.u0, similar(vars, Num)) - prob.f(rhs, vars, params) - eqs = vcat([0.0 ~ rhs[i] for i in 1:length(rhs)]...) - end - - else - rhs = prob.f(vars, params) - out_def = prob.f(prob.u0, prob.p) - eqs = vcat([0.0 ~ rhs[i] for i in 1:length(out_def)]...) - end - - sts = vec(collect(vars)) - _params = params - params = values(params) - params = if params isa Number || (params isa Array && ndims(params) == 0) - [params[1]] - else - vec(collect(params)) - end - default_u0 = Dict(sts .=> vec(collect(prob.u0))) - default_p = if has_p - if prob.p isa AbstractDict - Dict(v => prob.p[k] for (k, v) in pairs(_params)) - elseif prob.p isa MTKParameters - Dict(params .=> reduce(vcat, prob.p)) - else - Dict(params .=> vec(collect(prob.p))) - end - else - Dict() - end - - de = System(eqs, sts, params, - defaults = merge(default_u0, default_p); - name = gensym(:MTKizedNonlinProb), - kwargs...) - - de -end From e7a96b60924cef3a3e01d458255c085d5e25b289 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Thu, 1 May 2025 16:11:07 +0530 Subject: [PATCH 161/235] feat: add `modelingtoolkitize(::NonlinearProblem)` --- src/ModelingToolkit.jl | 1 + src/modelingtoolkitize/common.jl | 8 +++- src/modelingtoolkitize/nonlinearproblem.jl | 48 ++++++++++++++++++++++ 3 files changed, 55 insertions(+), 2 deletions(-) create mode 100644 src/modelingtoolkitize/nonlinearproblem.jl diff --git a/src/ModelingToolkit.jl b/src/ModelingToolkit.jl index cd79a0a553..7f228eabbc 100644 --- a/src/ModelingToolkit.jl +++ b/src/ModelingToolkit.jl @@ -186,6 +186,7 @@ include("modelingtoolkitize/common.jl") include("modelingtoolkitize/odeproblem.jl") include("modelingtoolkitize/sdeproblem.jl") include("modelingtoolkitize/optimizationproblem.jl") +include("modelingtoolkitize/nonlinearproblem.jl") include("systems/nonlinear/homotopy_continuation.jl") include("systems/nonlinear/initializesystem.jl") diff --git a/src/modelingtoolkitize/common.jl b/src/modelingtoolkitize/common.jl index a3d059a66a..8291fd5710 100644 --- a/src/modelingtoolkitize/common.jl +++ b/src/modelingtoolkitize/common.jl @@ -346,14 +346,18 @@ end Given a problem `prob`, the symbolic unknowns and params and the independent variable, trace through `prob.f` and return the resultant expression. """ -function trace_rhs(prob, vars, params, t) +function trace_rhs(prob, vars, params, t; prototype = nothing) args = (vars, params) if t !== nothing args = (args..., t) end # trace prob.f to get equation RHS if SciMLBase.isinplace(prob.f) - rhs = ArrayInterface.restructure(prob.u0, similar(vars, Num)) + if prototype === nothing + rhs = ArrayInterface.restructure(prob.u0, similar(vars, Num)) + else + rhs = similar(prototype, Num) + end fill!(rhs, 0) if prob.f isa SciMLBase.AbstractSciMLFunction && prob.f.f isa FunctionWrappersWrappers.FunctionWrappersWrapper diff --git a/src/modelingtoolkitize/nonlinearproblem.jl b/src/modelingtoolkitize/nonlinearproblem.jl new file mode 100644 index 0000000000..92425be373 --- /dev/null +++ b/src/modelingtoolkitize/nonlinearproblem.jl @@ -0,0 +1,48 @@ +""" + $(TYPEDSIGNATURES) + +Convert a `NonlinearProblem` or `NonlinearLeastSquaresProblem` to a +`ModelingToolkit.System`. + +# Keyword arguments + +- `u_names`: An array of names of the same size as `prob.u0` to use as the names of the + unknowns of the system. The names should be given as `Symbol`s. +- `p_names`: A collection of names to use for parameters of the system. The collection + should have keys corresponding to indexes of `prob.p`. For example, if `prob.p` is an + associative container like `NamedTuple`, then `p_names` should map keys of `prob.p` to + the name that the corresponding parameter should have in the returned system. The names + should be given as `Symbol`s. + +All other keyword arguments are forwarded to the created `System`. +""" +function modelingtoolkitize( + prob::Union{NonlinearProblem, NonlinearLeastSquaresProblem}; + u_names = nothing, p_names = nothing, kwargs...) + p = prob.p + has_p = !(p isa Union{DiffEqBase.NullParameters, Nothing}) + + vars = construct_vars(prob, nothing, u_names) + params = construct_params(prob, nothing, p_names) + + rhs = trace_rhs(prob, vars, params, nothing; prototype = prob.f.resid_prototype) + eqs = vcat([0 ~ rhs[i] for i in eachindex(rhs)]...) + + sts = vec(collect(vars)) + + # turn `params` into a list of symbolic variables as opposed to + # a parameter object containing symbolic variables. + _params = params + params = to_paramvec(params) + + defaults = defaults_from_u0_p(prob, vars, _params, params) + # In case initials crept in, specifically from when we constructed parameters + # using prob.f.sys + filter!(x -> !iscall(x) || !(operation(x) isa Initial), params) + filter!(x -> !iscall(x[1]) || !(operation(x[1]) isa Initial), defaults) + + return System(eqs, sts, params; + defaults, + name = gensym(:MTKizedNonlin), + kwargs...) +end From 397455066a950dbcf476a3aaf1933d5139c4fa4d Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Thu, 1 May 2025 16:12:05 +0530 Subject: [PATCH 162/235] refactor: remove old `modelingtoolkitize(::ODEProblem)` and `::SDEProblem` --- src/systems/diffeqs/modelingtoolkitize.jl | 303 ---------------------- 1 file changed, 303 deletions(-) delete mode 100644 src/systems/diffeqs/modelingtoolkitize.jl diff --git a/src/systems/diffeqs/modelingtoolkitize.jl b/src/systems/diffeqs/modelingtoolkitize.jl deleted file mode 100644 index cfa3ac43dd..0000000000 --- a/src/systems/diffeqs/modelingtoolkitize.jl +++ /dev/null @@ -1,303 +0,0 @@ -""" -$(TYPEDSIGNATURES) - -Generate `System`, dependent variables, and parameters from an `ODEProblem`. -""" -function modelingtoolkitize( - prob::DiffEqBase.ODEProblem; u_names = nothing, p_names = nothing, kwargs...) - prob.f isa DiffEqBase.AbstractParameterizedFunction && - return prob.f.sys - t = t_nounits - p = prob.p - has_p = !(p isa Union{DiffEqBase.NullParameters, Nothing}) - - if u_names !== nothing - varnames_length_check(prob.u0, u_names; is_unknowns = true) - _vars = [_defvar(name)(t) for name in u_names] - elseif SciMLBase.has_sys(prob.f) - varnames = getname.(variable_symbols(prob.f.sys)) - varidxs = variable_index.((prob.f.sys,), varnames) - invpermute!(varnames, varidxs) - _vars = [_defvar(name)(t) for name in varnames] - else - _vars = define_vars(prob.u0, t) - end - - vars = prob.u0 isa Number ? _vars : ArrayInterface.restructure(prob.u0, _vars) - params = if has_p - if p_names === nothing && SciMLBase.has_sys(prob.f) - p_names = Dict(parameter_index(prob.f.sys, sym) => sym - for sym in parameter_symbols(prob.f.sys)) - end - _params = define_params(p, p_names) - p isa Number ? _params[1] : - (p isa Tuple || p isa NamedTuple || p isa AbstractDict || p isa MTKParameters ? - _params : - ArrayInterface.restructure(p, _params)) - else - [] - end - - var_set = Set(vars) - - D = D_nounits - mm = prob.f.mass_matrix - - if mm === I - lhs = map(v -> D(v), vars) - else - lhs = map(mm * vars) do v - if iszero(v) - 0 - elseif v in var_set - D(v) - else - error("Non-permutation mass matrix is not supported.") - end - end - end - - if DiffEqBase.isinplace(prob) - rhs = ArrayInterface.restructure(prob.u0, similar(vars, Num)) - fill!(rhs, 0) - if prob.f isa ODEFunction && - prob.f.f isa FunctionWrappersWrappers.FunctionWrappersWrapper - prob.f.f.fw[1].obj[](rhs, vars, params, t) - else - prob.f(rhs, vars, params, t) - end - else - rhs = prob.f(vars, params, t) - end - - eqs = vcat([lhs[i] ~ rhs[i] for i in eachindex(prob.u0)]...) - - sts = vec(collect(vars)) - - _params = params - params = values(params) - params = if params isa Number || (params isa Array && ndims(params) == 0) - [params[1]] - else - vec(collect(params)) - end - default_u0 = Dict(sts .=> vec(collect(prob.u0))) - default_p = if has_p - if prob.p isa AbstractDict - Dict(v => prob.p[k] for (k, v) in pairs(_params)) - elseif prob.p isa MTKParameters - Dict(params .=> reduce(vcat, prob.p)) - else - Dict(params .=> vec(collect(prob.p))) - end - else - Dict() - end - filter!(x -> !iscall(x) || !(operation(x) isa Initial), params) - filter!(x -> !iscall(x[1]) || !(operation(x[1]) isa Initial), default_p) - de = System(eqs, t, sts, params, - defaults = merge(default_u0, default_p); - name = gensym(:MTKizedODE), - tspan = prob.tspan, - kwargs...) - - de -end - -_defvaridx(x, i) = variable(x, i, T = SymbolicUtils.FnType{Tuple, Real}) -_defvar(x) = variable(x, T = SymbolicUtils.FnType{Tuple, Real}) - -function define_vars(u, t) - [_defvaridx(:x, i)(t) for i in eachindex(u)] -end - -function define_vars(u::NTuple{<:Number}, t) - tuple((_defvaridx(:x, i)(ModelingToolkit.value(t)) for i in eachindex(u))...) -end - -function define_vars(u::NamedTuple, t) - NamedTuple(x => _defvar(x)(ModelingToolkit.value(t)) for x in keys(u)) -end - -const PARAMETERS_NOT_SUPPORTED_MESSAGE = """ - The chosen parameter type is currently not supported by `modelingtoolkitize`. The - current supported types are: - - - AbstractArrays - - AbstractDicts - - LabelledArrays (SLArray, LArray) - - Flat tuples (tuples of numbers) - - Flat named tuples (namedtuples of numbers) - """ - -struct ModelingtoolkitizeParametersNotSupportedError <: Exception - type::Any -end - -function Base.showerror(io::IO, e::ModelingtoolkitizeParametersNotSupportedError) - println(io, PARAMETERS_NOT_SUPPORTED_MESSAGE) - print(io, "Parameter type: ") - println(io, e.type) -end - -function varnames_length_check(vars, names; is_unknowns = false) - if length(names) != length(vars) - throw(ArgumentError(""" - Number of $(is_unknowns ? "unknowns" : "parameters") ($(length(vars))) \ - does not match number of names ($(length(names))). - """)) - end -end - -function define_params(p, _ = nothing) - throw(ModelingtoolkitizeParametersNotSupportedError(typeof(p))) -end - -function define_params(p::AbstractArray, names = nothing) - if names === nothing - [toparam(variable(:α, i)) for i in eachindex(p)] - else - varnames_length_check(p, names) - [toparam(variable(names[i])) for i in eachindex(p)] - end -end - -function define_params(p::Number, names = nothing) - if names === nothing - [toparam(variable(:α))] - elseif names isa Union{AbstractArray, AbstractDict} - varnames_length_check(p, names) - [toparam(variable(names[i])) for i in eachindex(p)] - else - [toparam(variable(names))] - end -end - -function define_params(p::AbstractDict, names = nothing) - if names === nothing - OrderedDict(k => toparam(variable(:α, i)) for (i, k) in zip(1:length(p), keys(p))) - else - varnames_length_check(p, names) - OrderedDict(k => toparam(variable(names[k])) for k in keys(p)) - end -end - -function define_params(p::Tuple, names = nothing) - if names === nothing - tuple((toparam(variable(:α, i)) for i in eachindex(p))...) - else - varnames_length_check(p, names) - tuple((toparam(variable(names[i])) for i in eachindex(p))...) - end -end - -function define_params(p::NamedTuple, names = nothing) - if names === nothing - NamedTuple(x => toparam(variable(x)) for x in keys(p)) - else - varnames_length_check(p, names) - NamedTuple(x => toparam(variable(names[x])) for x in keys(p)) - end -end - -function define_params(p::MTKParameters, names = nothing) - if names === nothing - bufs = (p...,) - i = 1 - ps = [] - for buf in bufs - for _ in buf - push!( - ps, - toparam(variable(:α, i)) - ) - end - end - return identity.(ps) - else - new_p = as_any_buffer(p) - for (k, v) in names - new_p[k] = v - end - return reduce(vcat, new_p; init = []) - end -end - -""" -$(TYPEDSIGNATURES) - -Generate `System`, dependent variables, and parameters from an `SDEProblem`. -""" -function modelingtoolkitize(prob::DiffEqBase.SDEProblem; kwargs...) - prob.f isa DiffEqBase.AbstractParameterizedFunction && - return (prob.f.sys, prob.f.sys.unknowns, prob.f.sys.ps) - @independent_variables t - p = prob.p - has_p = !(p isa Union{DiffEqBase.NullParameters, Nothing}) - - _vars = define_vars(prob.u0, t) - - vars = prob.u0 isa Number ? _vars : ArrayInterface.restructure(prob.u0, _vars) - params = if has_p - _params = define_params(p) - p isa MTKParameters ? _params : - p isa Number ? _params[1] : - (p isa Tuple || p isa NamedTuple ? _params : - ArrayInterface.restructure(p, _params)) - else - [] - end - - D = Differential(t) - - rhs = [D(var) for var in vars] - - if DiffEqBase.isinplace(prob) - lhs = similar(vars, Any) - - prob.f(lhs, vars, params, t) - - if DiffEqBase.is_diagonal_noise(prob) - neqs = similar(vars, Any) - prob.g(neqs, vars, params, t) - else - neqs = similar(vars, Any, size(prob.noise_rate_prototype)) - prob.g(neqs, vars, params, t) - end - else - lhs = prob.f(vars, params, t) - if DiffEqBase.is_diagonal_noise(prob) - neqs = prob.g(vars, params, t) - else - neqs = prob.g(vars, params, t) - end - end - deqs = vcat([rhs[i] ~ lhs[i] for i in eachindex(prob.u0)]...) - - params = if ndims(params) == 0 - [params[1]] - else - Vector(vec(params)) - end - sts = Vector(vec(vars)) - default_u0 = Dict(sts .=> vec(collect(prob.u0))) - default_p = if has_p - if prob.p isa AbstractDict - Dict(v => prob.p[k] for (k, v) in pairs(_params)) - elseif prob.p isa MTKParameters - Dict(params .=> reduce(vcat, prob.p)) - else - Dict(params .=> vec(collect(prob.p))) - end - else - Dict() - end - - de = System(deqs, t, sts, params; noise_eqs = neqs, - name = gensym(:MTKizedSDE), - tspan = prob.tspan, - defaults = merge(default_u0, default_p), - kwargs...) - - de -end From 153aa736becc1edbffbc0b2f24dde8509cc10b9d Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Fri, 2 May 2025 17:30:46 +0530 Subject: [PATCH 163/235] feat: add `structural_simplify` for optimization systems --- src/systems/systems.jl | 46 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/src/systems/systems.jl b/src/systems/systems.jl index 44a31ab921..8d61614a29 100644 --- a/src/systems/systems.jl +++ b/src/systems/systems.jl @@ -60,6 +60,9 @@ function __structural_simplify(sys::AbstractSystem; simplify = false, if has_noise_eqs(sys) && get_noise_eqs(sys) !== nothing sys = noise_to_brownians(sys; names = :αₘₜₖ) end + if isempty(equations(sys)) && !is_time_dependent(sys) && !_iszero(cost(sys)) + return simplify_optimization_system(sys; kwargs..., sort_eqs, simplify) + end sys = expand_connections(sys) state = TearingState(sys; sort_eqs) @@ -152,6 +155,49 @@ function __structural_simplify(sys::AbstractSystem; simplify = false, end end +function simplify_optimization_system(sys::System; split = true, kwargs...) + sys = flatten(sys) + cons = constraints(sys) + econs = Equation[] + icons = similar(cons, 0) + for e in cons + if e isa Equation + push!(econs, e) + else + push!(icons, e) + end + end + irreducible_subs = Dict() + dvs = mapreduce(Symbolics.scalarize, vcat, unknowns(sys)) + if !(dvs isa Array) + dvs = [dvs] + end + for i in eachindex(dvs) + var = dvs[i] + if hasbounds(var) + irreducible_subs[var] = irrvar = setirreducible(var, true) + dvs[i] = irrvar + end + end + econs = fast_substitute.(econs, (irreducible_subs,)) + nlsys = System(econs, dvs, parameters(sys); name = :___tmp_nlsystem) + snlsys = structural_simplify(nlsys; kwargs..., fully_determined = false) + obs = observed(snlsys) + subs = Dict(eq.lhs => eq.rhs for eq in observed(snlsys)) + seqs = equations(snlsys) + cons_simplified = similar(cons, length(icons) + length(seqs)) + for (i, eq) in enumerate(Iterators.flatten((seqs, icons))) + cons_simplified[i] = fixpoint_sub(eq, subs) + end + newsts = setdiff(dvs, keys(subs)) + @set! sys.constraints = cons_simplified + @set! sys.observed = [observed(sys); obs] + newcost = fixpoint_sub.(get_costs(sys), (subs,)) + @set! sys.costs = newcost + @set! sys.unknowns = newsts + return sys +end + function __num_isdiag_noise(mat) for i in axes(mat, 1) nnz = 0 From 9602a8196c93002541fd638b6160f5f549d63647 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Wed, 7 May 2025 13:41:04 +0530 Subject: [PATCH 164/235] fix: change default `consolidate` to `default_consolidate` in `@mtkmodel` --- src/systems/model_parsing.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/systems/model_parsing.jl b/src/systems/model_parsing.jl index fdb26ba7ec..8fe07f7f99 100644 --- a/src/systems/model_parsing.jl +++ b/src/systems/model_parsing.jl @@ -121,7 +121,7 @@ function _model_macro(mod, fullname::Union{Expr, Symbol}, expr, isconnector) gui_metadata = isassigned(icon) > 0 ? GUIMetadata(GlobalRef(mod, name), icon[]) : GUIMetadata(GlobalRef(mod, name)) - consolidate = get(dict, :consolidate, nothing) + consolidate = get(dict, :consolidate, default_consolidate) description = get(dict, :description, "") @inline pop_structure_dict!.( From 9d8df2a3759f884b7e7a17e7871685dc224f72be Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Thu, 8 May 2025 18:23:59 +0530 Subject: [PATCH 165/235] fix: handle `Shift`s in `is_diff_equation` --- src/systems/abstractsystem.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/systems/abstractsystem.jl b/src/systems/abstractsystem.jl index 23b4859484..04d520838b 100644 --- a/src/systems/abstractsystem.jl +++ b/src/systems/abstractsystem.jl @@ -3047,9 +3047,9 @@ is_diff_equation(eq2) # false """ function is_diff_equation(eq) (eq isa Equation) || (return false) - isdefined(eq, :lhs) && hasnode(is_derivative, wrap(eq.lhs)) && + isdefined(eq, :lhs) && recursive_hasoperator(Union{Differential, Shift}, eq.lhs) && (return true) - isdefined(eq, :rhs) && hasnode(is_derivative, wrap(eq.rhs)) && + isdefined(eq, :rhs) && recursive_hasoperator(Union{Differential, Shift}, eq.rhs) && (return true) return false end From f19daed361a490e118ac9377a29034aa5328ae65 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Thu, 8 May 2025 18:25:39 +0530 Subject: [PATCH 166/235] feat: implement `calculate_hessian` and `hessian_sparsity` for `System` --- src/systems/codegen.jl | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/systems/codegen.jl b/src/systems/codegen.jl index 1ca879322a..6f035670e6 100644 --- a/src/systems/codegen.jl +++ b/src/systems/codegen.jl @@ -201,6 +201,25 @@ function generate_tgrad( expression, wrap_gfw, (2, 3, is_split(sys)), res; eval_expression, eval_module) end +function calculate_hessian(sys::System; simplify = false, sparse = false) + rhs = [eq.rhs - eq.lhs for eq in full_equations(sys)] + dvs = unknowns(sys) + if sparse + hess = map(rhs) do expr + Symbolics.sparsehessian(expr, dvs; simplify)::AbstractSparseArray + end + else + hess = [Symbolics.hessian(expr, dvs; simplify) for expr in rhs] + end + + return hess +end + +function Symbolics.hessian_sparsity(sys::System) + hess = calculate_hessian(sys; sparse = true) + return similar.(hess, Float64) +end + const W_GAMMA = only(@variables ˍ₋gamma) function generate_W(sys::System, γ = 1.0, dvs = unknowns(sys), From c656c33edb226f3fc560c5af3613aa818dd58e95 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Thu, 8 May 2025 18:26:11 +0530 Subject: [PATCH 167/235] feat: make delay processing more modular in `build_function_wrapper` --- src/systems/codegen_utils.jl | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/systems/codegen_utils.jl b/src/systems/codegen_utils.jl index e7ba2659d7..9ee8a7fd81 100644 --- a/src/systems/codegen_utils.jl +++ b/src/systems/codegen_utils.jl @@ -129,6 +129,7 @@ end The argument of generated functions corresponding to the history function. """ const DDE_HISTORY_FUN = Sym{Symbolics.FnType{Tuple{Any, <:Real}, Vector{Real}}}(:___history___) +const BVP_SOLUTION = Sym{Symbolics.FnType{Tuple{<:Real}, Vector{Real}}}(:__sol__) """ $(TYPEDSIGNATURES) @@ -143,14 +144,15 @@ Turn delayed unknowns in `eqs` into calls to `DDE_HISTORY_FUNCTION`. # Keyword Arguments - `param_arg`: The name of the variable containing the parameter object. +- `histfn`: The history function to use for codegen, called as `histfn(p, t)` """ function delay_to_function( - sys::AbstractSystem, eqs = full_equations(sys); param_arg = MTKPARAMETERS_ARG) + sys::AbstractSystem, eqs = full_equations(sys); param_arg = MTKPARAMETERS_ARG, histfn = DDE_HISTORY_FUN) delay_to_function(eqs, get_iv(sys), Dict{Any, Int}(operation(s) => i for (i, s) in enumerate(unknowns(sys))), parameters(sys), - DDE_HISTORY_FUN; param_arg) + histfn; param_arg) end function delay_to_function(eqs::Vector, iv, sts, ps, h; param_arg = MTKPARAMETERS_ARG) delay_to_function.(eqs, (iv,), (sts,), (ps,), (h,); param_arg) @@ -191,6 +193,10 @@ generated functions, and `args` are the arguments. - `wrap_delays`: Whether to transform delayed unknowns of `sys` present in `expr` into calls to a history function. The history function is added to the list of arguments right before parameters, at the index `p_start`. +- `histfn`: The history function to use for transforming delayed terms. For any delayed + term `x(expr)`, this is called as `histfn(p, expr)` where `p` is the parameter object. +- `histfn_symbolic`: The symbolic history function variable to add as an argument to the + generated function. - `wrap_code`: Forwarded to `build_function`. - `add_observed`: Whether to add assignment statements for observed equations in the generated code. @@ -218,7 +224,7 @@ All other keyword arguments are forwarded to `build_function`. """ function build_function_wrapper(sys::AbstractSystem, expr, args...; p_start = 2, p_end = is_time_dependent(sys) ? length(args) - 1 : length(args), - wrap_delays = is_dde(sys), wrap_code = identity, + wrap_delays = is_dde(sys), histfn = DDE_HISTORY_FUN, histfn_symbolic = histfn, wrap_code = identity, add_observed = true, filter_observed = Returns(true), create_bindings = false, output_type = nothing, mkarray = nothing, wrap_mtkparameters = true, extra_assignments = Assignment[], cse = true, kwargs...) @@ -229,11 +235,11 @@ function build_function_wrapper(sys::AbstractSystem, expr, args...; p_start = 2, if wrap_delays param_arg = is_split(sys) ? MTKPARAMETERS_ARG : generated_argument_name(p_start) obs = map(obs) do eq - delay_to_function(sys, eq; param_arg) + delay_to_function(sys, eq; param_arg, histfn) end - expr = delay_to_function(sys, expr; param_arg) + expr = delay_to_function(sys, expr; param_arg, histfn) # add extra argument - args = (args[1:(p_start - 1)]..., DDE_HISTORY_FUN, args[p_start:end]...) + args = (args[1:(p_start - 1)]..., histfn_symbolic, args[p_start:end]...) p_start += 1 p_end += 1 end From 7102e48a9250d3d3c7ad60b0a05e423082a6a716 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Thu, 8 May 2025 18:26:50 +0530 Subject: [PATCH 168/235] fix: fix `generate_cost` for time-dependent (BV) systems --- src/systems/codegen.jl | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/src/systems/codegen.jl b/src/systems/codegen.jl index 6f035670e6..064be6fdfb 100644 --- a/src/systems/codegen.jl +++ b/src/systems/codegen.jl @@ -398,13 +398,29 @@ function generate_cost(sys::System; expression = Val{true}, wrap_gfw = Val{false obj = cost(sys) dvs = unknowns(sys) ps = reorder_parameters(sys) - res = build_function_wrapper(sys, obj, dvs, ps...; expression = Val{true}, kwargs...) + + if is_time_dependent(sys) + wrap_delays = true + p_start = 1 + p_end = length(ps) + args = (ps..., get_iv(sys)) + nargs = 3 + else + wrap_delays = false + p_start = 2 + p_end = length(ps) + 1 + args = (dvs, ps...) + nargs = 2 + end + res = build_function_wrapper( + sys, obj, args...; expression = Val{true}, p_start, p_end, wrap_delays, + histfn = (p, t) -> BVP_SOLUTION(t), histfn_symbolic = BVP_SOLUTION, kwargs...) if expression == Val{true} return res end f_oop = eval_or_rgf(res; eval_expression, eval_module) return maybe_compile_function( - expression, wrap_gfw, (2, 2, is_split(sys)), res; eval_expression, eval_module) + expression, wrap_gfw, (2, nargs, is_split(sys)), res; eval_expression, eval_module) end function calculate_cost_gradient(sys::System; simplify = false) From a654f5e988a99c3fdf3e49feff8694c41ceb59ee Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Thu, 8 May 2025 18:30:48 +0530 Subject: [PATCH 169/235] fix: default `build_initializeprob` to `supports_initialization(sys)` --- src/systems/problem_utils.jl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/systems/problem_utils.jl b/src/systems/problem_utils.jl index df59dc201c..912496fc69 100644 --- a/src/systems/problem_utils.jl +++ b/src/systems/problem_utils.jl @@ -1220,7 +1220,8 @@ Keyword arguments: All other keyword arguments are passed as-is to `constructor`. """ function process_SciMLProblem( - constructor, sys::AbstractSystem, u0map, pmap; build_initializeprob = true, + constructor, sys::AbstractSystem, u0map, pmap; + build_initializeprob = supports_initialization(sys), implicit_dae = false, t = nothing, guesses = AnyDict(), warn_initialize_determined = true, initialization_eqs = [], eval_expression = false, eval_module = @__MODULE__, fully_determined = nothing, From 7cad5fef22651be84a09c45b81e8d3cb9c6a0d72 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Thu, 8 May 2025 18:32:44 +0530 Subject: [PATCH 170/235] fix: disallow simplification of jump systems --- src/systems/systems.jl | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/systems/systems.jl b/src/systems/systems.jl index 8d61614a29..70b3ddcfa7 100644 --- a/src/systems/systems.jl +++ b/src/systems/systems.jl @@ -60,6 +60,9 @@ function __structural_simplify(sys::AbstractSystem; simplify = false, if has_noise_eqs(sys) && get_noise_eqs(sys) !== nothing sys = noise_to_brownians(sys; names = :αₘₜₖ) end + if !isempty(jumps(sys)) + return sys + end if isempty(equations(sys)) && !is_time_dependent(sys) && !_iszero(cost(sys)) return simplify_optimization_system(sys; kwargs..., sort_eqs, simplify) end From 7f6456a9118dbee3ddd1af18da042b42a0fe032a Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Thu, 8 May 2025 18:33:18 +0530 Subject: [PATCH 171/235] fix: remove usage of `is_scalar_noise` kwarg --- src/systems/systems.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/systems/systems.jl b/src/systems/systems.jl index 70b3ddcfa7..eb9e27d409 100644 --- a/src/systems/systems.jl +++ b/src/systems/systems.jl @@ -150,7 +150,7 @@ function __structural_simplify(sys::AbstractSystem; simplify = false, noise_eqs = StructuralTransformations.tearing_substitute_expr(ode_sys, noise_eqs) ssys = System(Vector{Equation}(full_equations(ode_sys)), get_iv(ode_sys), unknowns(ode_sys), parameters(ode_sys); noise_eqs, - name = nameof(ode_sys), is_scalar_noise, observed = observed(ode_sys), defaults = defaults(sys), + name = nameof(ode_sys), 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), From be1d04b57114cd2481515198a1c99b925777cb6a Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Thu, 8 May 2025 18:34:37 +0530 Subject: [PATCH 172/235] feat: inspect jumps in `collect_scoped_vars!` --- src/utils.jl | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/utils.jl b/src/utils.jl index b3df0b957f..efa6196af8 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -546,6 +546,12 @@ function collect_scoped_vars!(unknowns, parameters, sys, iv; depth = 1, op = Dif collect_vars!(unknowns, parameters, eq, iv; depth, op) end end + if has_jumps(sys) + for eq in jumps(sys) + eqtype_supports_collect_vars(eq) || continue + collect_vars!(unknowns, parameters, eq, iv; depth, op) + end + end if has_parameter_dependencies(sys) for eq in parameter_dependencies(sys) if eq isa Pair From 854a3400eda0870f8e14cc277822acc1d63f048d Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Thu, 8 May 2025 18:35:00 +0530 Subject: [PATCH 173/235] test: comment out jac/tgrad caching test --- test/structural_transformation/tearing.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/structural_transformation/tearing.jl b/test/structural_transformation/tearing.jl index 0cbabbfa3b..e91f3fa988 100644 --- a/test/structural_transformation/tearing.jl +++ b/test/structural_transformation/tearing.jl @@ -197,8 +197,8 @@ u0 = [mass.s => 0.0 mass.v => 1.0] sys = structural_simplify(ms_model) -@test ModelingToolkit.get_jac(sys)[] === ModelingToolkit.EMPTY_JAC -@test ModelingToolkit.get_tgrad(sys)[] === ModelingToolkit.EMPTY_TGRAD +# @test ModelingToolkit.get_jac(sys)[] === ModelingToolkit.EMPTY_JAC +# @test ModelingToolkit.get_tgrad(sys)[] === ModelingToolkit.EMPTY_TGRAD prob_complex = ODEProblem(sys, u0, (0, 1.0)) sol = solve(prob_complex, Tsit5()) @test all(sol[mass.v] .== 1) From de3b7e8154828b4a3c7d2c3ce52f8a0a94254fda Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Thu, 8 May 2025 18:38:02 +0530 Subject: [PATCH 174/235] test: update BVProblem tests to new semantics --- test/bvproblem.jl | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/test/bvproblem.jl b/test/bvproblem.jl index dc5f1ef658..0931a0ab6d 100644 --- a/test/bvproblem.jl +++ b/test/bvproblem.jl @@ -285,26 +285,28 @@ end u0map = [x(t) => 4.0, y(t) => 2.0] parammap = [α => 7.5, β => 4, γ => 8.0, δ => 5.0] costs = [x(0.6), x(0.3)^2] - consolidate(u) = (u[1] + 3)^2 + u[2] + consolidate(u, sub) = (u[1] + 3)^2 + u[2] + sum(sub; init = 0) @mtkbuild lksys = System(eqs, t; costs, consolidate) - @test_throws ErrorException @mtkbuild lksys2 = System(eqs, t; costs) - @test_throws ErrorException ODEProblem(lksys, u0map, tspan, parammap) - prob = ODEProblem(lksys, u0map, tspan, parammap; allow_cost = true) + @test_throws ModelingToolkit.SystemCompatibilityError ODEProblem( + lksys, u0map, tspan, parammap) + prob = ODEProblem(lksys, u0map, tspan, parammap; check_compatibility = false) sol = solve(prob, Tsit5()) - costfn = ModelingToolkit.generate_cost_function(lksys) + costfn = ModelingToolkit.generate_cost( + lksys; expression = Val{false}, wrap_gfw = Val{true}) _t = tspan[2] @test costfn(sol, prob.p, _t) ≈ (sol(0.6)[1] + 3)^2 + sol(0.3)[1]^2 ### With a parameter @parameters t_c costs = [y(t_c) + x(0.0), x(0.4)^2] - consolidate(u) = log(u[1]) - u[2] + consolidate(u, sub) = log(u[1]) - u[2] + sum(sub; init = 0) @mtkbuild lksys = System(eqs, t; costs, consolidate) @test t_c ∈ Set(parameters(lksys)) push!(parammap, t_c => 0.56) - prob = ODEProblem(lksys, u0map, tspan, parammap; allow_cost = true) + prob = ODEProblem(lksys, u0map, tspan, parammap; check_compatibility = false) sol = solve(prob, Tsit5()) - costfn = ModelingToolkit.generate_cost_function(lksys) + costfn = ModelingToolkit.generate_cost( + lksys; expression = Val{false}, wrap_gfw = Val{true}) @test costfn(sol, prob.p, _t) ≈ log(sol(0.56)[2] + sol(0.0)[1]) - sol(0.4)[1]^2 end From 45b3d5924da84e0afe55539ff100fe7735281c1a Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Thu, 8 May 2025 18:38:12 +0530 Subject: [PATCH 175/235] test: update DDE tests to new semantics --- test/dde.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/dde.jl b/test/dde.jl index c7561e6c24..a5a0f6ee0d 100644 --- a/test/dde.jl +++ b/test/dde.jl @@ -86,7 +86,7 @@ eqs = [D(x(t)) ~ a * x(t) + b * x(t - τ) + c + (α * x(t) + γ) * η, delx ~ x( @test ModelingToolkit.is_dde(sys) @test !is_markovian(sys) @test equations(sys) == [D(x(t)) ~ a * x(t) + b * x(t - τ) + c] -@test isequal(ModelingToolkit.get_noiseeqs(sys), [α * x(t) + γ]) +@test isequal(ModelingToolkit.get_noise_eqs(sys), [α * x(t) + γ;;]) prob_mtk = SDDEProblem(sys, [x(t) => 1.0 + t], tspan; constant_lags = (τ,)); @test_nowarn sol_mtk = solve(prob_mtk, RKMil(), seed = 100) From f5fffbb865c1d7e4437c56a328940548449012af Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Thu, 8 May 2025 18:38:52 +0530 Subject: [PATCH 176/235] test: make debugging tests more reproducible --- test/debugging.jl | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/test/debugging.jl b/test/debugging.jl index de4420d08c..615b452b38 100644 --- a/test/debugging.jl +++ b/test/debugging.jl @@ -5,15 +5,17 @@ using ModelingToolkit: t_nounits as t, D_nounits as D, ASSERTION_LOG_VARIABLE @variables x(t) @brownian a @named inner_ode = System(D(x) ~ -sqrt(x), t; assertions = [(x > 0) => "ohno"]) -@named inner_sde = System([D(x) ~ -sqrt(x) + a], t; assertions = [(x > 0) => "ohno"]) +@named inner_sde = System([D(x) ~ -10sqrt(x) + 0.01a], t; assertions = [(x > 0) => "ohno"]) sys_ode = structural_simplify(inner_ode) sys_sde = structural_simplify(inner_sde) +SEED = 42 @testset "assertions are present in generated `f`" begin - @testset "$(typeof(sys))" for (Problem, sys, alg) in [ + @testset "$(Problem)" for (Problem, sys, alg) in [ (ODEProblem, sys_ode, Tsit5()), (SDEProblem, sys_sde, ImplicitEM())] + kwargs = Problem == SDEProblem ? (; seed = SEED) : (;) @test !is_parameter(sys, ASSERTION_LOG_VARIABLE) - prob = Problem(sys, [x => 0.1], (0.0, 5.0)) + prob = Problem(sys, [x => 0.1], (0.0, 5.0); kwargs...) sol = solve(prob, alg) @test !SciMLBase.successful_retcode(sol) @test isnan(prob.f.f([0.0], prob.p, sol.t[end])[1]) @@ -21,11 +23,12 @@ sys_sde = structural_simplify(inner_sde) end @testset "`debug_system` adds logging" begin - @testset "$(typeof(sys))" for (Problem, sys, alg) in [ + @testset "$(Problem)" for (Problem, sys, alg) in [ (ODEProblem, sys_ode, Tsit5()), (SDEProblem, sys_sde, ImplicitEM())] + kwargs = Problem == SDEProblem ? (; seed = SEED) : (;) dsys = debug_system(sys; functions = []) @test is_parameter(dsys, ASSERTION_LOG_VARIABLE) - prob = Problem(dsys, [x => 0.1], (0.0, 5.0)) + prob = Problem(dsys, [x => 0.1], (0.0, 5.0); kwargs...) sol = @test_logs (:error, r"ohno") match_mode=:any solve(prob, alg) @test !SciMLBase.successful_retcode(sol) prob.ps[ASSERTION_LOG_VARIABLE] = false @@ -35,13 +38,14 @@ end end @testset "Hierarchical system" begin - @testset "$(typeof(inner))" for (ctor, Problem, inner, alg) in [ + @testset "$(Problem)" for (ctor, Problem, inner, alg) in [ (System, ODEProblem, inner_ode, Tsit5()), (System, SDEProblem, inner_sde, ImplicitEM())] + kwargs = Problem == SDEProblem ? (; seed = SEED) : (;) @mtkbuild outer = ctor(Equation[], t; systems = [inner]) dsys = debug_system(outer; functions = []) @test is_parameter(dsys, ASSERTION_LOG_VARIABLE) - prob = Problem(dsys, [inner.x => 0.1], (0.0, 5.0)) + prob = Problem(dsys, [inner.x => 0.1], (0.0, 5.0); kwargs...) sol = @test_logs (:error, r"ohno") match_mode=:any solve(prob, alg) @test !SciMLBase.successful_retcode(sol) prob.ps[ASSERTION_LOG_VARIABLE] = false From a77d685d25fb25c913b25e468600fea33db798ba Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Thu, 8 May 2025 18:39:14 +0530 Subject: [PATCH 177/235] test: update discrete system tests to new semantics --- test/discrete_system.jl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/discrete_system.jl b/test/discrete_system.jl index 0c5412977f..1ed6438d76 100644 --- a/test/discrete_system.jl +++ b/test/discrete_system.jl @@ -37,7 +37,8 @@ syss = structural_simplify(sys) df = DiscreteFunction(syss) # iip du = zeros(3) -u = ModelingToolkit.better_varmap_to_vars(Dict([S => 1, I => 2, R => 3]), unknowns(syss)) +u = ModelingToolkit.better_varmap_to_vars( + Dict([S(k - 1) => 1, I(k - 1) => 2, R(k - 1) => 3]), unknowns(syss)) p = MTKParameters(syss, [c, nsteps, δt, β, γ] .=> collect(1:5)) df.f(du, u, p, 0) reorderer = getu(syss, [S, I, R]) From f02179123e65fc4caee91d613bd5c5b2b1ee00e7 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Thu, 8 May 2025 18:39:25 +0530 Subject: [PATCH 178/235] test: update implicit discrete system tests to new semantics --- test/implicit_discrete_system.jl | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/test/implicit_discrete_system.jl b/test/implicit_discrete_system.jl index 45c89f969e..49ab2a5921 100644 --- a/test/implicit_discrete_system.jl +++ b/test/implicit_discrete_system.jl @@ -28,7 +28,8 @@ rng = StableRNG(22525) @test prob.u0 == [1.0, 1.0] @variables x(t) @mtkbuild sys = System([x(k) ~ x(k) * x(k - 1) - 3], t) - @test_throws ErrorException prob=ImplicitDiscreteProblem(sys, [], tspan) + @test_throws ModelingToolkit.MissingVariablesError prob=ImplicitDiscreteProblem( + sys, [], tspan) end @testset "System with algebraic equations" begin @@ -64,12 +65,14 @@ end x + y + z ~ 2] @mtkbuild sys = System(eqs, t) @test length(unknowns(sys)) == length(equations(sys)) == 3 - @test occursin("var\"y(t)\"", string(ImplicitDiscreteFunctionExpr(sys))) + @test occursin( + "var\"y(t)\"", string(ImplicitDiscreteFunction(sys; expression = Val{true}))) # Shifted observable that appears in algebraic equation is properly handled. eqs = [z(k) ~ x(k) + sin(x(k)), y(k) ~ x(k - 1) + x(k - 2), z(k) * x(k) ~ 3] @mtkbuild sys = System(eqs, t) - @test occursin("var\"Shift(t, 1)(z(t))\"", string(ImplicitDiscreteFunctionExpr(sys))) + @test occursin("var\"Shift(t, 1)(z(t))\"", + string(ImplicitDiscreteFunction(sys; expression = Val{true}))) end From efbe0c87b89b37bcb768e4cfaa7473f12764ece5 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Thu, 8 May 2025 18:39:35 +0530 Subject: [PATCH 179/235] test: remove redundant initial values tests --- test/initial_values.jl | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/test/initial_values.jl b/test/initial_values.jl index aca533acbd..cc75c2e41e 100644 --- a/test/initial_values.jl +++ b/test/initial_values.jl @@ -100,18 +100,8 @@ prob = ODEProblem(sys, [], (0.0, 1.0), [A1 => 0.3]) @testset "default=nothing is skipped" begin @parameters p = nothing @variables x(t)=nothing y(t) - for sys in [ - System(Equation[], t, [x, y], [p]; defaults = [y => nothing], name = :osys), - SDESystem(Equation[], [], t, [x, y], [p]; defaults = [y => nothing], name = :ssys), - JumpSystem(Equation[], t, [x, y], [p]; defaults = [y => nothing], name = :jsys), - System(Equation[], [x, y], [p]; defaults = [y => nothing], name = :nsys), - OptimizationSystem( - Equation[], [x, y], [p]; defaults = [y => nothing], name = :optsys), - ConstraintsSystem( - Equation[], [x, y], [p]; defaults = [y => nothing], name = :conssys) - ] - @test isempty(ModelingToolkit.defaults(sys)) - end + @named sys = System(Equation[], t, [x, y], [p]; defaults = [y => nothing]) + @test isempty(ModelingToolkit.defaults(sys)) end # Using indepvar in initialization From 33da82f96647abb0a019ca2f4f135e6fb2425e94 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Thu, 8 May 2025 18:39:53 +0530 Subject: [PATCH 180/235] test: update initialization system tests to new semantics --- test/initializationsystem.jl | 47 +++++++++++++++--------------------- 1 file changed, 20 insertions(+), 27 deletions(-) diff --git a/test/initializationsystem.jl b/test/initializationsystem.jl index 93a0845bdb..eda381b5a3 100644 --- a/test/initializationsystem.jl +++ b/test/initializationsystem.jl @@ -600,13 +600,12 @@ end # the correct solver. # `rhss` allows adding terms to the end of equations (only 2 equations allowed) to influence # the system type (brownian vars to turn it into an SDE). - @testset "$Problem with $(SciMLBase.parameterless_type(alg)) and $ctor ctor" for ((System, Problem, alg, rhss), (ctor, expectedT)) in Iterators.product( + @testset "$Problem with $(SciMLBase.parameterless_type(alg)) and $ctor ctor" for ((Problem, alg, rhss), (ctor, expectedT)) in Iterators.product( [ - (ModelingToolkit.System, ODEProblem, Tsit5(), zeros(2)), - (ModelingToolkit.System, SDEProblem, ImplicitEM(), [a, b]), - (ModelingToolkit.System, DDEProblem, - MethodOfSteps(Tsit5()), [_x(t - 0.1), 0.0]), - (ModelingToolkit.System, SDDEProblem, ImplicitEM(), [_x(t - 0.1) + a, b]) + (ODEProblem, Tsit5(), zeros(2)), + (SDEProblem, ImplicitEM(), [a, b]), + (DDEProblem, MethodOfSteps(Tsit5()), [_x(t - 0.1), 0.0]), + (SDDEProblem, ImplicitEM(), [_x(t - 0.1) + a, b]) ], [(identity, Any), (sarray_ctor, SVector)]) u0_constructor = p_constructor = ctor @@ -626,11 +625,10 @@ end @test parameter_values(initprob).tunable isa expectedT @test solve(prob, alg).ps[sym] ≈ val end - function test_initializesystem(sys, u0map, pmap, p, equation) - isys = ModelingToolkit.generate_initializesystem( - sys; u0map, pmap, guesses = ModelingToolkit.guesses(sys)) - @test is_variable(isys, p) - @test equation in equations(isys) || (0 ~ -equation.rhs) in equations(isys) + function test_initializesystem(prob, p, equation) + isys = prob.f.initialization_data.initializeprob.f.sys + @test is_variable(isys, p) || ModelingToolkit.has_observed_with_lhs(isys, p) + @test equation in [equations(isys); observed(isys)] end u0map = Dict(x => 1.0, y => 1.0) @@ -650,7 +648,7 @@ end [D(x) ~ x + rhss[1], p ~ x + y + rhss[2]], t; defaults = [p => missing], guesses = [p => 0.0]) prob = Problem(sys, u0map, (0.0, 1.0); u0_constructor, p_constructor) test_parameter(prob, p, 2.0) - test_initializesystem(sys, u0map, pmap, p, 0 ~ p - x - y) + test_initializesystem(prob, p, p ~ x + y) prob2 = remake(prob; u0 = u0map) prob2 = remake(prob2; p = setp_oop(prob2, p)(prob2, 0.0)) test_parameter(prob2, p, 2.0) @@ -661,7 +659,7 @@ end pmap[p] = missing prob = Problem(sys, u0map, (0.0, 1.0), pmap; u0_constructor, p_constructor) test_parameter(prob, p, 2.0) - test_initializesystem(sys, u0map, pmap, p, 0 ~ 2q - p) + test_initializesystem(prob, p, p ~ 2q) prob2 = remake(prob; u0 = u0map, p = pmap) prob2 = remake(prob2; p = setp_oop(prob2, p)(prob2, 0.0)) test_parameter(prob2, p, 2.0) @@ -670,7 +668,7 @@ end [D(x) ~ x + rhss[1], p ~ x + y + rhss[2]], t; guesses = [p => 0.0]) prob = Problem(sys, u0map, (0.0, 1.0), pmap; u0_constructor, p_constructor) test_parameter(prob, p, 2.0) - test_initializesystem(sys, u0map, pmap, p, 0 ~ x + y - p) + test_initializesystem(prob, p, p ~ x + y) prob2 = remake(prob; u0 = u0map, p = pmap) prob2 = remake(prob2; p = setp_oop(prob2, p)(prob2, 0.0)) test_parameter(prob2, p, 2.0) @@ -681,7 +679,7 @@ end delete!(pmap, p) prob = Problem(sys, u0map, (0.0, 1.0), pmap; u0_constructor, p_constructor) test_parameter(prob, p, 2.0) - test_initializesystem(sys, u0map, pmap, p, 0 ~ 2q - p) + test_initializesystem(prob, p, p ~ 2q) prob2 = remake(prob; u0 = u0map, p = pmap) prob2 = remake(prob2; p = setp_oop(prob2, p)(prob2, 0.0)) test_parameter(prob2, p, 2.0) @@ -692,7 +690,7 @@ end _pmap = merge(pmap, Dict(p => q)) prob = Problem(sys, u0map, (0.0, 1.0), _pmap; u0_constructor, p_constructor) test_parameter(prob, p, _pmap[q]) - test_initializesystem(sys, u0map, _pmap, p, 0 ~ q - p) + test_initializesystem(prob, p, p ~ q) # Problem dependent value with guess, no `missing` @mtkbuild sys = System( [D(x) ~ y * q + p + rhss[1], D(y) ~ x * p + q + rhss[2]], t; guesses = [p => 0.0]) @@ -937,11 +935,11 @@ end @brownian a b x = _x(t) - @testset "$Problem with $(SciMLBase.parameterless_type(typeof(alg)))" for (System, Problem, alg, rhss) in [ - (ModelingToolkit.System, ODEProblem, Tsit5(), zeros(2)), - (ModelingToolkit.System, SDEProblem, ImplicitEM(), [a, b]), - (ModelingToolkit.System, DDEProblem, MethodOfSteps(Tsit5()), [_x(t - 0.1), 0.0]), - (ModelingToolkit.System, SDDEProblem, ImplicitEM(), [_x(t - 0.1) + a, b]) + @testset "$Problem with $(SciMLBase.parameterless_type(typeof(alg)))" for (Problem, alg, rhss) in [ + (ODEProblem, Tsit5(), zeros(2)), + (SDEProblem, ImplicitEM(), [a, b]), + (DDEProblem, MethodOfSteps(Tsit5()), [_x(t - 0.1), 0.0]), + (SDDEProblem, ImplicitEM(), [_x(t - 0.1) + a, b]) ] @mtkbuild sys = System( [D(x) ~ x + rhss[1], p ~ x + y + rhss[2]], t; defaults = [p => missing], guesses = [ @@ -1327,13 +1325,8 @@ end u0s = [I => 1, R => 0] ps = [S0 => 999, β => 0.01, γ => 0.001] - dprob = DiscreteProblem(js, u0s, (0.0, 10.0), ps) - @test_broken dprob.f.initialization_data !== nothing - sol = solve(dprob, FunctionMap()) - @test sol[S, 1] ≈ 999 - @test SciMLBase.successful_retcode(sol) - jprob = JumpProblem(js, dprob) + jprob = JumpProblem(js, u0s, (0.0, 10.0), ps) sol = solve(jprob, SSAStepper()) @test sol[S, 1] ≈ 999 @test SciMLBase.successful_retcode(sol) From 5849d3491e59b2f6893e0a5aad8a9afdf3ec2e51 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Thu, 8 May 2025 18:40:06 +0530 Subject: [PATCH 181/235] test: fix jump system tests test: fix jumpsystem tests --- test/jumpsystem.jl | 46 ++++++++++++++++++++++++---------------------- 1 file changed, 24 insertions(+), 22 deletions(-) diff --git a/test/jumpsystem.jl b/test/jumpsystem.jl index d80ee40666..50d59b3313 100644 --- a/test/jumpsystem.jl +++ b/test/jumpsystem.jl @@ -188,12 +188,13 @@ sol = solve(jprob, SSAStepper()); @testset "Combined system name collisions" begin sys1 = JumpSystem([maj1, maj2], t, [S], [β, γ], name = :sys1) sys2 = JumpSystem([maj1, maj2], t, [S], [β, γ], name = :sys1) - @test_throws ArgumentError JumpSystem([sys1.γ ~ sys2.γ], t, [], [], + @test_throws ModelingToolkit.NonUniqueSubsystemsError JumpSystem( + [sys1.γ ~ sys2.γ], t, [], [], systems = [sys1, sys2], name = :foo) end # test if param mapper is setup correctly for callbacks -let +@testset "Parammapper with callbacks" begin @parameters k1 k2 k3 @variables A(t) B(t) maj1 = MassActionJump(k1 * k3, [0 => 1], [A => -1, B => 1]) @@ -220,15 +221,17 @@ let end # observed variable handling -@variables OBS(t) -@named js5 = JumpSystem([maj1, maj2], t, [S], [β, γ]; observed = [OBS ~ 2 * S * h]) -OBS2 = OBS -@test isequal(OBS2, @nonamespace js5.OBS) -@unpack OBS = js5 -@test isequal(OBS2, OBS) +@testset "Observed handling tests" begin + @variables OBS(t) + @named js5 = JumpSystem([maj1, maj2], t, [S], [β, γ]; observed = [OBS ~ 2 * S * h]) + OBS2 = OBS + @test isequal(OBS2, @nonamespace js5.OBS) + @unpack OBS = js5 + @test isequal(OBS2, OBS) +end # test to make sure dep graphs are correct -let +@testset "Dependency graph tests" begin # A + 2X --> 3X # 3X --> A + 2X # B --> X @@ -239,8 +242,8 @@ let MassActionJump(1.0, [B => 1], [B => -1, X => 1]), MassActionJump(1.0, [X => 1], [B => 1, X => -1])] @named js = JumpSystem(jumps, t, [A, X, B], []) - jdeps = asgraph(js) - vdeps = variable_dependencies(js) + jdeps = asgraph(js; eqs = MT.jumps(js)) + vdeps = variable_dependencies(js; eqs = MT.jumps(js)) vtoj = jdeps.badjlist @test vtoj == [[1], [1, 2, 4], [3]] jtov = vdeps.badjlist @@ -284,7 +287,7 @@ 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 -let +@testset "Autosolver test" begin @parameters k @variables X(t) rate = k @@ -302,7 +305,7 @@ let end # basic VariableRateJump test -let +@testset "VRJ test" begin N = 1000 # number of simulations for testing solve accuracy Random.seed!(rng, 1111) @variables A(t) B(t) C(t) @@ -310,7 +313,7 @@ let 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])) jprob = JumpProblem( - js, [A => 0, C => 0], (0.0, 10.0), [k => 1.0]; aggregtor = Direct(), rng) + js, [A => 0, C => 0], (0.0, 10.0), [k => 1.0]; aggregator = Direct(), rng) @test jprob.prob isa ODEProblem sol = solve(jprob, Tsit5()) @@ -346,7 +349,7 @@ let end # collect_vars! tests for jumps -let +@testset "`collect_vars!` for jumps" begin @variables x1(t) x2(t) x3(t) x4(t) x5(t) @parameters p1 p2 p3 p4 p5 j1 = ConstantRateJump(p1, [x1 ~ Pre(x1) + 1]) @@ -381,7 +384,7 @@ let end # scoping tests -let +@testset "Scoping tests" begin @variables x1(t) x2(t) x3(t) x4(t) x2 = ParentScope(x2) x3 = ParentScope(ParentScope(x3)) @@ -426,7 +429,7 @@ let end # PDMP test -let +@testset "PDMP test" begin seed = 1111 Random.seed!(rng, seed) @variables X(t) Y(t) @@ -467,7 +470,7 @@ let end # that mixes ODEs and jump types, and then contin events -let +@testset "ODEs + Jumps + Continuous events" begin seed = 1111 Random.seed!(rng, seed) @variables X(t) Y(t) @@ -482,7 +485,7 @@ let u0map = [X => p.X₀, Y => p.Y₀] pmap = [α => p.α, β => p.β] tspan = (0.0, 20.0) - jprob = JumpProblem(jsys, u0, tspan, pmap; rng, save_positions = (false, false)) + jprob = JumpProblem(jsys, u0map, tspan, pmap; rng, save_positions = (false, false)) times = range(0.0, tspan[2], length = 100) Nsims = 4000 Xv = zeros(length(times)) @@ -520,7 +523,7 @@ let continuous_events = cevents) jsys = complete(jsys) tspan = (0.0, 200.0) - jprob = JumpProblem(jsys, u0, tspan, pmap; rng, save_positions = (false, false)) + jprob = JumpProblem(jsys, u0map, tspan, pmap; rng, save_positions = (false, false)) Xsamp = 0.0 Nsims = 4000 for n in 1:Nsims @@ -560,8 +563,7 @@ end crj = ConstantRateJump(rate, affect) @named jsys = JumpSystem([crj, eq], t, [X], [a, b]) jsys = complete(jsys) - oprob = ODEProblem(jsys, [:X => 1.0], (0.0, 10.0), [:a => 1.0, :b => 0.5]) - jprob = JumpProblem(jsys, oprob) + jprob = JumpProblem(jsys, [:X => 1.0], (0.0, 10.0), [:a => 1.0, :b => 0.5]) jprob2 = remake(jprob; u0 = [:X => 10.0]) @test jprob2[X] ≈ 10.0 end From 3c539414262a14f869e0eac42409383aa47a9588 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Thu, 8 May 2025 18:40:15 +0530 Subject: [PATCH 182/235] test: remove redundant namespacing tests --- test/namespacing.jl | 206 +++++--------------------------------------- 1 file changed, 23 insertions(+), 183 deletions(-) diff --git a/test/namespacing.jl b/test/namespacing.jl index 50cbd7e3a3..6a8a654ecf 100644 --- a/test/namespacing.jl +++ b/test/namespacing.jl @@ -1,186 +1,26 @@ using ModelingToolkit using ModelingToolkit: t_nounits as t, D_nounits as D, iscomplete, does_namespacing -@testset "System" begin - @variables x(t) - @parameters p - sys = System(D(x) ~ p * x, t; name = :inner) - @test !iscomplete(sys) - @test does_namespacing(sys) - - csys = complete(sys) - @test iscomplete(csys) - @test !does_namespacing(csys) - - nsys = toggle_namespacing(sys, false) - @test !iscomplete(nsys) - @test !does_namespacing(nsys) - - @test isequal(x, csys.x) - @test isequal(x, nsys.x) - @test !isequal(x, sys.x) - @test isequal(p, csys.p) - @test isequal(p, nsys.p) - @test !isequal(p, sys.p) - - @test_throws ["namespacing", "inner"] System( - Equation[], t; systems = [nsys], name = :a) -end - -@testset "SDESystem" begin - @variables x(t) - @parameters p - sys = SDESystem([D(x) ~ p * x], [x], t, [x], [p]; name = :inner) - @test !iscomplete(sys) - @test does_namespacing(sys) - - csys = complete(sys) - @test iscomplete(csys) - @test !does_namespacing(csys) - - nsys = toggle_namespacing(sys, false) - @test !iscomplete(nsys) - @test !does_namespacing(nsys) - - @test isequal(x, csys.x) - @test isequal(x, nsys.x) - @test !isequal(x, sys.x) - @test isequal(p, csys.p) - @test isequal(p, nsys.p) - @test !isequal(p, sys.p) - - @test_throws ["namespacing", "inner"] SDESystem( - Equation[], [], t; systems = [nsys], name = :a) -end - -@testset "DiscreteSystem" begin - @variables x(t) - @parameters p - k = ShiftIndex(t) - sys = System([x(k) ~ p * x(k - 1)], t; name = :inner) - @test !iscomplete(sys) - @test does_namespacing(sys) - - csys = complete(sys) - @test iscomplete(csys) - @test !does_namespacing(csys) - - nsys = toggle_namespacing(sys, false) - @test !iscomplete(nsys) - @test !does_namespacing(nsys) - - @test isequal(x, csys.x) - @test isequal(x, nsys.x) - @test !isequal(x, sys.x) - @test isequal(p, csys.p) - @test isequal(p, nsys.p) - @test !isequal(p, sys.p) - - @test_throws ["namespacing", "inner"] System( - Equation[], t; systems = [nsys], name = :a) -end - -@testset "ImplicitDiscreteSystem" begin - @variables x(t) - @parameters p - k = ShiftIndex(t) - sys = System([x(k) ~ p + x(k - 1) * x(k)], t; name = :inner) - @test !iscomplete(sys) - @test does_namespacing(sys) - - csys = complete(sys) - @test iscomplete(csys) - @test !does_namespacing(csys) - - nsys = toggle_namespacing(sys, false) - @test !iscomplete(nsys) - @test !does_namespacing(nsys) - - @test isequal(x, csys.x) - @test isequal(x, nsys.x) - @test !isequal(x, sys.x) - @test isequal(p, csys.p) - @test isequal(p, nsys.p) - @test !isequal(p, sys.p) - - @test_throws ["namespacing", "inner"] System( - Equation[], t; systems = [nsys], name = :a) -end - -@testset "NonlinearSystem" begin - @variables x - @parameters p - sys = System([x ~ p * x^2 + 1]; name = :inner) - @test !iscomplete(sys) - @test does_namespacing(sys) - - csys = complete(sys) - @test iscomplete(csys) - @test !does_namespacing(csys) - - nsys = toggle_namespacing(sys, false) - @test !iscomplete(nsys) - @test !does_namespacing(nsys) - - @test isequal(x, csys.x) - @test isequal(x, nsys.x) - @test !isequal(x, sys.x) - @test isequal(p, csys.p) - @test isequal(p, nsys.p) - @test !isequal(p, sys.p) - - @test_throws ["namespacing", "inner"] System( - Equation[]; systems = [nsys], name = :a) -end - -@testset "OptimizationSystem" begin - @variables x - @parameters p - sys = OptimizationSystem(p * x^2 + 1; name = :inner) - @test !iscomplete(sys) - @test does_namespacing(sys) - - csys = complete(sys) - @test iscomplete(csys) - @test !does_namespacing(csys) - - nsys = toggle_namespacing(sys, false) - @test !iscomplete(nsys) - @test !does_namespacing(nsys) - - @test isequal(x, csys.x) - @test isequal(x, nsys.x) - @test !isequal(x, sys.x) - @test isequal(p, csys.p) - @test isequal(p, nsys.p) - @test !isequal(p, sys.p) - - @test_throws ["namespacing", "inner"] OptimizationSystem( - []; systems = [nsys], name = :a) -end - -@testset "ConstraintsSystem" begin - @variables x - @parameters p - sys = ConstraintsSystem([x^2 + p ~ 0], [x], [p]; name = :inner) - @test !iscomplete(sys) - @test does_namespacing(sys) - - csys = complete(sys) - @test iscomplete(csys) - @test !does_namespacing(csys) - - nsys = toggle_namespacing(sys, false) - @test !iscomplete(nsys) - @test !does_namespacing(nsys) - - @test isequal(x, csys.x) - @test isequal(x, nsys.x) - @test !isequal(x, sys.x) - @test isequal(p, csys.p) - @test isequal(p, nsys.p) - @test !isequal(p, sys.p) - - @test_throws ["namespacing", "inner"] ConstraintsSystem( - [], [], []; systems = [nsys], name = :a) -end +@variables x(t) +@parameters p +sys = System(D(x) ~ p * x, t; name = :inner) +@test !iscomplete(sys) +@test does_namespacing(sys) + +csys = complete(sys) +@test iscomplete(csys) +@test !does_namespacing(csys) + +nsys = toggle_namespacing(sys, false) +@test !iscomplete(nsys) +@test !does_namespacing(nsys) + +@test isequal(x, csys.x) +@test isequal(x, nsys.x) +@test !isequal(x, sys.x) +@test isequal(p, csys.p) +@test isequal(p, nsys.p) +@test !isequal(p, sys.p) + +@test_throws ["namespacing", "inner"] System( + Equation[], t; systems = [nsys], name = :a) From eb53edd57bab94ec3928a9f773feb785754e1947 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Thu, 8 May 2025 18:40:46 +0530 Subject: [PATCH 183/235] fix: fix `independent_variables` --- src/systems/abstractsystem.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/systems/abstractsystem.jl b/src/systems/abstractsystem.jl index 04d520838b..4021f1fc44 100644 --- a/src/systems/abstractsystem.jl +++ b/src/systems/abstractsystem.jl @@ -236,7 +236,7 @@ function independent_variables(sys::AbstractSystem) if !(sys isa System) @warn "Please declare ($(typeof(sys))) as a subtype of `AbstractTimeDependentSystem`, `AbstractTimeIndependentSystem` or `AbstractMultivariateSystem`." end - if isdefined(sys, :iv) + if isdefined(sys, :iv) && getfield(sys, :iv) !== nothing return [getfield(sys, :iv)] elseif isdefined(sys, :ivs) return getfield(sys, :ivs) From e2f142255da8fdd8b6ce494054896828d794526e Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Thu, 8 May 2025 18:41:03 +0530 Subject: [PATCH 184/235] feat: export newly added functions --- src/ModelingToolkit.jl | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/ModelingToolkit.jl b/src/ModelingToolkit.jl index 7f228eabbc..65be6cbf75 100644 --- a/src/ModelingToolkit.jl +++ b/src/ModelingToolkit.jl @@ -307,7 +307,8 @@ export Differential, expand_derivatives, @derivatives export Equation, ConstrainedEquation export Term, Sym export SymScope, LocalScope, ParentScope, GlobalScope -export independent_variable, equations, controls, observed, full_equations, jumps +export independent_variable, equations, controls, observed, full_equations, jumps, cost, + brownians export initialization_equations, guesses, defaults, parameter_dependencies, hierarchy export structural_simplify, expand_connections, linearize, linearization_function, LinearizationProblem @@ -315,12 +316,12 @@ export solve export Pre export calculate_jacobian, generate_jacobian, generate_rhs, generate_custom_function, - generate_W + generate_W, calculate_hessian export calculate_control_jacobian, generate_control_jacobian export calculate_tgrad, generate_tgrad -export calculate_gradient, generate_gradient +export generate_cost, calculate_cost_gradient, generate_cost_gradient export calculate_factorized_W, generate_factorized_W -export calculate_hessian, generate_hessian +export calculate_cost_hessian, generate_cost_hessian export calculate_massmatrix, generate_diffusion_function export stochastic_integral_transform export TearingState From 4a39883b94819fa8ef1f663d66d37c1d654ede83 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Wed, 14 May 2025 13:57:03 +0530 Subject: [PATCH 185/235] feat: add `noise_to_brownians` --- src/ModelingToolkit.jl | 3 +- src/systems/diffeqs/basic_transformations.jl | 54 ++++++++++++++++++++ 2 files changed, 56 insertions(+), 1 deletion(-) diff --git a/src/ModelingToolkit.jl b/src/ModelingToolkit.jl index 65be6cbf75..06b9d43653 100644 --- a/src/ModelingToolkit.jl +++ b/src/ModelingToolkit.jl @@ -301,7 +301,8 @@ export isinput, isoutput, getbounds, hasbounds, getguess, hasguess, isdisturbanc hasunit, getunit, hasconnect, getconnect, hasmisc, getmisc, state_priority export ode_order_lowering, dae_order_lowering, liouville_transform, - change_independent_variable, substitute_component, add_accumulations + change_independent_variable, substitute_component, add_accumulations, + noise_to_brownians export PDESystem export Differential, expand_derivatives, @derivatives export Equation, ConstrainedEquation diff --git a/src/systems/diffeqs/basic_transformations.jl b/src/systems/diffeqs/basic_transformations.jl index c9d5234751..9b7c72494d 100644 --- a/src/systems/diffeqs/basic_transformations.jl +++ b/src/systems/diffeqs/basic_transformations.jl @@ -447,3 +447,57 @@ function add_accumulations(sys::System, vars::Vector{<:Pair}) @set! sys.defaults = merge(get_defaults(sys), Dict(a => 0.0 for a in avars)) return sys end + +""" + $(TYPEDSIGNATURES) + +Given a system with noise in the form of noise equation (`get_noise_eqs(sys) !== nothing`) +return an equivalent system which represents the noise using brownian variables. + +# Keyword Arguments + +- `names`: The name(s) to use for the brownian variables. If this is a `Symbol`, variables + with the given name and successive numeric `_i` suffixes will be used. If a `Vector`, + this must have appropriate length for the noise equations of the system. The + corresponding number of brownian variables are created with the given names. +""" +function noise_to_brownians(sys::System; names::Union{Symbol, Vector{Symbol}} = :α) + neqs = get_noise_eqs(sys) + if neqs === nothing + throw(ArgumentError("Expected a system with `noise_eqs`.")) + end + if !isempty(get_systems(sys)) + throw(ArgumentError("The system must be flattened.")) + end + # vector means diagonal noise + nbrownians = ndims(neqs) == 1 ? length(neqs) : size(neqs, 2) + if names isa Symbol + names = [Symbol(names, :_, i) for i in 1:nbrownians] + end + if length(names) != nbrownians + throw(ArgumentError(""" + The system has $nbrownians brownian variables. Received $(length(names)) names \ + for the brownian variables. Provide $nbrownians names or a single `Symbol` to use \ + an array variable of the appropriately length. + """)) + end + brownvars = map(names) do name + only(@brownian $name) + end + + terms = if ndims(neqs) == 1 + neqs .* brownvars + else + neqs * brownvars + end + + eqs = map(get_eqs(sys), terms) do eq, term + eq.lhs ~ eq.rhs + term + end + + @set! sys.eqs = eqs + @set! sys.brownians = brownvars + @set! sys.noise_eqs = nothing + + return sys +end From bfcae7232b0c174118bbe38a0034e6953a98127f Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Wed, 14 May 2025 14:01:57 +0530 Subject: [PATCH 186/235] feat: add `convert_system_indepvar` --- src/systems/diffeqs/basic_transformations.jl | 71 ++++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/src/systems/diffeqs/basic_transformations.jl b/src/systems/diffeqs/basic_transformations.jl index 9b7c72494d..4bf3b40177 100644 --- a/src/systems/diffeqs/basic_transformations.jl +++ b/src/systems/diffeqs/basic_transformations.jl @@ -501,3 +501,74 @@ function noise_to_brownians(sys::System; names::Union{Symbol, Vector{Symbol}} = return sys end + +""" + $(TYPEDSIGNATURES) + +Function which takes a system `sys` and an independent variable `t` and changes the +independent variable of `sys` to `t`. This is different from +[`change_independent_variable`](@ref) since this function only does a symbolic substitution +of the independent variable. `sys` must not be a reduced system (`observed(sys)` must be +empty). If `sys` is time-independent, this can be used to turn it into a time-dependent +system. + +# Keyword arguments + +- `name`: The name of the returned system. +""" +function convert_system_indepvar(sys::System, t; name = nameof(sys)) + isempty(observed(sys)) || + throw(ArgumentError(""" + `convert_system_indepvar` cannot handle reduced model (i.e. observed(sys) is non-\ + empty). + """)) + t = value(t) + varmap = Dict() + sts = unknowns(sys) + newsts = similar(sts, Any) + for (i, s) in enumerate(sts) + if iscall(s) + args = arguments(s) + length(args) == 1 || + throw(InvalidSystemException("Illegal unknown: $s. The unknown can have at most one argument like `x(t)`.")) + arg = args[1] + if isequal(arg, t) + newsts[i] = s + continue + end + ns = maketerm(typeof(s), operation(s), Any[t], + SymbolicUtils.metadata(s)) + newsts[i] = ns + varmap[s] = ns + else + ns = variable(getname(s); T = FnType)(t) + newsts[i] = ns + varmap[s] = ns + end + end + sub = Base.Fix2(substitute, varmap) + if is_time_dependent(sys) + iv = only(independent_variables(sys)) + sub.x[iv] = t # otherwise the Differentials aren't fixed + end + neweqs = map(sub, equations(sys)) + defs = Dict(sub(k) => sub(v) for (k, v) in defaults(sys)) + neqs = get_noise_eqs(sys) + if neqs !== nothing + neqs = map(sub, neqs) + end + cstrs = map(sub, get_constraints(sys)) + costs = Vector{Union{Real, BasicSymbolic}}(map(sub, get_costs(sys))) + @set! sys.eqs = neweqs + @set! sys.iv = t + @set! sys.unknowns = newsts + @set! sys.defaults = defs + @set! sys.name = name + @set! sys.noise_eqs = neqs + @set! sys.constraints = cstrs + @set! sys.costs = costs + + var_to_name = Dict(k => get(varmap, v, v) for (k, v) in get_var_to_name(sys)) + @set! sys.var_to_name = var_to_name + return sys +end From d94e88b0767ac2795ecd2d36b0cac00bf010f9e4 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Wed, 14 May 2025 14:02:56 +0530 Subject: [PATCH 187/235] fix: fix structural simplification for SDEs --- src/systems/systems.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/systems/systems.jl b/src/systems/systems.jl index eb9e27d409..13ee0767cf 100644 --- a/src/systems/systems.jl +++ b/src/systems/systems.jl @@ -134,7 +134,7 @@ function __structural_simplify(sys::AbstractSystem; simplify = false, if sorted_g_rows isa AbstractMatrix && size(sorted_g_rows, 2) == 1 # If there's only one brownian variable referenced across all the equations, # we get a Nx1 matrix of noise equations, which is a special case known as scalar noise - noise_eqs = sorted_g_rows[:, 1] + noise_eqs = reshape(sorted_g_rows[:, 1], (:, 1)) is_scalar_noise = true elseif __num_isdiag_noise(sorted_g_rows) # If each column of the noise matrix has either 0 or 1 non-zero entry, then this is "diagonal noise". From 3cfba7f1c913eade2df26ccb9a241283c9fc8b92 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Wed, 14 May 2025 14:04:19 +0530 Subject: [PATCH 188/235] refactor: remove `convert_system` --- src/ModelingToolkit.jl | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/ModelingToolkit.jl b/src/ModelingToolkit.jl index 06b9d43653..37b4629992 100644 --- a/src/ModelingToolkit.jl +++ b/src/ModelingToolkit.jl @@ -216,11 +216,6 @@ include("inputoutput.jl") include("adjoints.jl") -for S in subtypes(ModelingToolkit.AbstractSystem) - S = nameof(S) - @eval convert_system(::Type{<:$S}, sys::$S) = sys -end - const t_nounits = let only(@independent_variables t) end @@ -274,8 +269,8 @@ export AbstractTimeDependentSystem, AbstractTimeIndependentSystem, AbstractMultivariateSystem -export ODEFunction, ODEFunctionExpr, ODEProblemExpr, convert_system, - System, OptimizationSystem, JumpSystem, SDESystem +export ODEFunction, ODEFunctionExpr, ODEProblemExpr, convert_system_indepvar, + System, OptimizationSystem, JumpSystem, SDESystem, NonlinearSystem export DAEFunctionExpr, DAEProblemExpr export SDEFunction, SDEFunctionExpr, SDEProblemExpr export SystemStructure From 4bea746d861cff3979f48fc397b81fe34007e30e Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Wed, 14 May 2025 14:05:14 +0530 Subject: [PATCH 189/235] test: fix nonlinearsystem tests --- test/nonlinearsystem.jl | 46 +++++++++++++---------------------------- 1 file changed, 14 insertions(+), 32 deletions(-) diff --git a/test/nonlinearsystem.jl b/test/nonlinearsystem.jl index e120e75e07..3eb141ebc1 100644 --- a/test/nonlinearsystem.jl +++ b/test/nonlinearsystem.jl @@ -30,7 +30,7 @@ eqs = [0 ~ σ * (y - x) * h, @test eval(toexpr(ns)) == ns test_nlsys_inference("standard", ns, (x, y, z), (σ, ρ, β)) @test begin - f = generate_function(ns, [x, y, z], [σ, ρ, β], expression = Val{false})[2] + f = generate_rhs(ns, [x, y, z], [σ, ρ, β], expression = Val{false})[2] du = [0.0, 0.0, 0.0] f(du, [1, 2, 3], [1, 2, 3]) du ≈ [1, -3, -7] @@ -57,9 +57,6 @@ jac = calculate_jacobian(ns) @test canonequal(jac[3, 2], x) @test canonequal(jac[3, 3], -1 * β) end -nlsys_func = generate_function(ns, [x, y, z], [σ, ρ, β]) -jac_func = generate_jacobian(ns) -f = @eval eval(nlsys_func) # Intermediate calculations a = y - x @@ -69,7 +66,7 @@ eqs = [0 ~ σ * a * h, 0 ~ x * y - β * z] @named ns = System(eqs, [x, y, z], [σ, ρ, β]) ns = complete(ns) -nlsys_func = generate_function(ns, [x, y, z], [σ, ρ, β]) +nlsys_func = generate_rhs(ns, [x, y, z], [σ, ρ, β]) nf = NonlinearFunction(ns) jac = calculate_jacobian(ns) @@ -119,14 +116,14 @@ lorenz2 = lorenz(:lorenz2) using OrdinaryDiffEq @independent_variables t D = Differential(t) -@named subsys = convert_system(System, lorenz1, t) +@named subsys = convert_system_indepvar(lorenz1, t) @named sys = System([D(subsys.x) ~ subsys.x + subsys.x], t, systems = [subsys]) 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) @test sol[subsys.x] + sol[subsys.y] - sol[subsys.z]≈sol[subsys.u] atol=1e-7 -@test_throws ArgumentError convert_system(System, sys, t) +@test_throws ArgumentError convert_system_indepvar(sys, t) @parameters σ ρ β @variables x y z @@ -152,7 +149,8 @@ np = NonlinearProblem( function issue819() sys1 = makesys(:sys1) sys2 = makesys(:sys1) - @test_throws ArgumentError System([sys2.f ~ sys1.x, sys1.f ~ 0], [], [], + @test_throws ModelingToolkit.NonUniqueSubsystemsError System( + [sys2.f ~ sys1.x, sys1.f ~ 0], [], [], systems = [sys1, sys2], name = :foo) end issue819() @@ -200,23 +198,6 @@ eq = [v1 ~ sin(2pi * t * h) @named sys = System(eq, t) @test length(equations(structural_simplify(sys))) == 0 -@testset "Issue: 1504" begin - @variables u[1:4] - - eqs = [u[1] ~ 1, - u[2] ~ 1, - u[3] ~ 1, - u[4] ~ h] - - sys = System(eqs, collect(u[1:4]), Num[], defaults = Dict([]), name = :test) - sys = complete(sys) - prob = NonlinearProblem(sys, ones(length(unknowns(sys)))) - - sol = NonlinearSolve.solve(prob, NewtonRaphson()) - - @test sol[u] ≈ ones(4) -end - @variables x(t) @parameters a eqs = [0 ~ a * x] @@ -371,17 +352,18 @@ end prob = IntervalNonlinearProblem(sys, (0.0, 2.0), [p => 1.0]) sol = @test_nowarn solve(prob, ITP()) @test SciMLBase.successful_retcode(sol) - @test_nowarn IntervalNonlinearProblemExpr(sys, (0.0, 2.0), [p => 1.0]) + @test_nowarn IntervalNonlinearProblem( + sys, (0.0, 2.0), [p => 1.0]; expression = Val{true}) end @variables y @mtkbuild sys = System([0 ~ x * x - p * x + p, 0 ~ x * y + p]) - @test_throws ["single equation", "unknown"] IntervalNonlinearProblem(sys, (0.0, 1.0)) - @test_throws ["single equation", "unknown"] IntervalNonlinearFunction(sys, (0.0, 1.0)) - @test_throws ["single equation", "unknown"] IntervalNonlinearProblemExpr( - sys, (0.0, 1.0)) - @test_throws ["single equation", "unknown"] IntervalNonlinearFunctionExpr( - sys, (0.0, 1.0)) + @test_throws ["single unknown"] IntervalNonlinearProblem(sys, (0.0, 1.0)) + @test_throws ["single unknown"] IntervalNonlinearFunction(sys) + @test_throws ["single unknown"] IntervalNonlinearProblem( + sys, (0.0, 1.0); expression = Val{true}) + @test_throws ["single unknown"] IntervalNonlinearFunction( + sys; expression = Val{true}) end @testset "Vector parameter used unscalarized and partially scalarized" begin From 966ccea0059189358f9902722ddfe9364ebe14ff Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Wed, 14 May 2025 14:05:21 +0530 Subject: [PATCH 190/235] test: fix optimizationsystem tests --- test/optimizationsystem.jl | 26 +++++++++----------------- 1 file changed, 9 insertions(+), 17 deletions(-) diff --git a/test/optimizationsystem.jl b/test/optimizationsystem.jl index 2ec9516721..a59cb6421b 100644 --- a/test/optimizationsystem.jl +++ b/test/optimizationsystem.jl @@ -22,14 +22,14 @@ using ModelingToolkit: get_metadata unknowns(combinedsys) parameters(combinedsys) - calculate_gradient(combinedsys) - calculate_hessian(combinedsys) - generate_function(combinedsys) - generate_gradient(combinedsys) - generate_hessian(combinedsys) - hess_sparsity = ModelingToolkit.hessian_sparsity(sys1) + calculate_cost_gradient(combinedsys) + calculate_cost_hessian(combinedsys) + generate_cost(combinedsys) + generate_cost_gradient(combinedsys) + generate_cost_hessian(combinedsys) + hess_sparsity = ModelingToolkit.cost_hessian_sparsity(sys1) sparse_prob = OptimizationProblem(complete(sys1), - [x, y], + [x => 1, y => 1], [a => 0.0, b => 0.0], grad = true, sparse = true) @@ -158,7 +158,7 @@ end constraints = [sys1.x + sys2.y ~ 2], checks = false)) prob = OptimizationProblem(sys, [0.0, 0.0]) @test isequal(constraints(sys), vcat(sys1.x + sys2.y ~ 2, sys1.x ~ 1, sys2.y ~ 1)) - @test isequal(equations(sys), (sys1.x - sys1.a)^2 + (sys2.y - 1 / 2)^2) + @test isequal(cost(sys), (sys1.x - sys1.a)^2 + (sys2.y - 1 / 2)^2) @test isequal(unknowns(sys), [sys1.x, sys2.y]) prob_ = remake(prob, u0 = [1.0, 0.0], p = [2.0]) @@ -177,7 +177,7 @@ end @named sys3 = OptimizationSystem(x3, [x3], [], systems = [sys2]) @named sys4 = OptimizationSystem(x4, [x4], [], systems = [sys3]) - @test isequal(equations(sys4), sys3.sys2.sys1.x1 + sys3.sys2.x2 + sys3.x3 + x4) + @test isequal(cost(sys4), sys3.sys2.sys1.x1 + sys3.sys2.x2 + sys3.x3 + x4) end @testset "time dependent var" begin @@ -299,14 +299,6 @@ end loss = (a - x)^2 + b * (y - x^2)^2 @named sys = OptimizationSystem(loss, [x, y], [a, b], constraints = [x^2 + y^2 ≲ 0.0]) sys = complete(sys) - @test_throws Any OptimizationProblem(sys, - [x => 0.0, y => 0.0], - [a => 1.0, b => 100.0], - lcons = [0.0]) - @test_throws Any OptimizationProblem(sys, - [x => 0.0, y => 0.0], - [a => 1.0, b => 100.0], - ucons = [0.0]) prob = OptimizationProblem(sys, [x => 0.0, y => 0.0], [a => 1.0, b => 100.0]) @test prob.f.expr isa Symbolics.Symbolic From 24a56f93ca75926c1dcbbc7aa9e1c2739184f786 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Wed, 14 May 2025 14:05:27 +0530 Subject: [PATCH 191/235] test: fix sdesystem tests --- test/sdesystem.jl | 130 +++++++++++++++++++++++----------------------- 1 file changed, 66 insertions(+), 64 deletions(-) diff --git a/test/sdesystem.jl b/test/sdesystem.jl index 96251ceb00..5bdc033498 100644 --- a/test/sdesystem.jl +++ b/test/sdesystem.jl @@ -1,6 +1,7 @@ using ModelingToolkit, StaticArrays, LinearAlgebra using StochasticDiffEq, OrdinaryDiffEq, SparseArrays using Random, Test +using Setfield using Statistics # imported as tt because `t` is used extensively below using ModelingToolkit: t_nounits as tt, D_nounits as D, MTKParameters @@ -19,9 +20,8 @@ noiseeqs = [0.1 * x, # System -> SDESystem shorthand constructor @named sys = System(eqs, tt, [x, y, z], [σ, ρ, β]) -@test SDESystem(sys, noiseeqs, name = :foo) isa SDESystem -@named de = SDESystem(eqs, noiseeqs, tt, [x, y, z], [σ, ρ, β], tspan = (0.0, 10.0)) +@named de = SDESystem(eqs, noiseeqs, tt, [x, y, z], [σ, ρ, β]) de = complete(de) f = eval(generate_diffusion_function(de)[1]) @test f(ones(3), rand(3), nothing) == 0.1ones(3) @@ -105,11 +105,11 @@ f1!(du, u, p, t) = (du .= p[1] * u) prob = SDEProblem(f1!, σ1!, u0, trange, p) # no correction sys = modelingtoolkitize(prob) -fdrift = eval(generate_function(sys)[1]) +fdrift = eval(generate_rhs(sys)[1]) fdif = eval(generate_diffusion_function(sys)[1]) @test fdrift(u0, p, t) == p[1] * u0 @test fdif(u0, p, t) == p[2] * u0 -fdrift! = eval(generate_function(sys)[2]) +fdrift! = eval(generate_rhs(sys)[2]) fdif! = eval(generate_diffusion_function(sys)[2]) du = similar(u0) fdrift!(du, u0, p, t) @@ -119,11 +119,11 @@ fdif!(du, u0, p, t) # Ito -> Strat sys2 = stochastic_integral_transform(sys, -1 // 2) -fdrift = eval(generate_function(sys2)[1]) +fdrift = eval(generate_rhs(sys2)[1]) fdif = eval(generate_diffusion_function(sys2)[1]) @test fdrift(u0, p, t) == p[1] * u0 - 1 // 2 * p[2]^2 * u0 @test fdif(u0, p, t) == p[2] * u0 -fdrift! = eval(generate_function(sys2)[2]) +fdrift! = eval(generate_rhs(sys2)[2]) fdif! = eval(generate_diffusion_function(sys2)[2]) du = similar(u0) fdrift!(du, u0, p, t) @@ -133,11 +133,11 @@ fdif!(du, u0, p, t) # Strat -> Ito sys2 = stochastic_integral_transform(sys, 1 // 2) -fdrift = eval(generate_function(sys2)[1]) +fdrift = eval(generate_rhs(sys2)[1]) fdif = eval(generate_diffusion_function(sys2)[1]) @test fdrift(u0, p, t) == p[1] * u0 + 1 // 2 * p[2]^2 * u0 @test fdif(u0, p, t) == p[2] * u0 -fdrift! = eval(generate_function(sys2)[2]) +fdrift! = eval(generate_rhs(sys2)[2]) fdif! = eval(generate_diffusion_function(sys2)[2]) du = similar(u0) fdrift!(du, u0, p, t) @@ -153,11 +153,11 @@ u0 = rand(1) prob = SDEProblem(f2!, σ2!, u0, trange) # no correction sys = modelingtoolkitize(prob) -fdrift = eval(generate_function(sys)[1]) +fdrift = eval(generate_rhs(sys)[1]) fdif = eval(generate_diffusion_function(sys)[1]) @test fdrift(u0, p, t) == @. sin(t) + cos(u0) @test fdif(u0, p, t) == pi .+ atan.(u0) -fdrift! = eval(generate_function(sys)[2]) +fdrift! = eval(generate_rhs(sys)[2]) fdif! = eval(generate_diffusion_function(sys)[2]) du = similar(u0) fdrift!(du, u0, p, t) @@ -167,11 +167,11 @@ fdif!(du, u0, p, t) # Ito -> Strat sys2 = stochastic_integral_transform(sys, -1 // 2) -fdrift = eval(generate_function(sys2)[1]) +fdrift = eval(generate_rhs(sys2)[1]) fdif = eval(generate_diffusion_function(sys2)[1]) @test fdrift(u0, p, t) == @. sin(t) + cos(u0) - 1 // 2 * 1 / (1 + u0^2) * (pi + atan(u0)) @test fdif(u0, p, t) == pi .+ atan.(u0) -fdrift! = eval(generate_function(sys2)[2]) +fdrift! = eval(generate_rhs(sys2)[2]) fdif! = eval(generate_diffusion_function(sys2)[2]) du = similar(u0) fdrift!(du, u0, p, t) @@ -181,11 +181,11 @@ fdif!(du, u0, p, t) # Strat -> Ito sys2 = stochastic_integral_transform(sys, 1 // 2) -fdrift = eval(generate_function(sys2)[1]) +fdrift = eval(generate_rhs(sys2)[1]) fdif = eval(generate_diffusion_function(sys2)[1]) @test fdrift(u0, p, t) ≈ @. sin(t) + cos(u0) + 1 // 2 * 1 / (1 + u0^2) * (pi + atan(u0)) @test fdif(u0, p, t) == pi .+ atan.(u0) -fdrift! = eval(generate_function(sys2)[2]) +fdrift! = eval(generate_rhs(sys2)[2]) fdif! = eval(generate_diffusion_function(sys2)[2]) du = similar(u0) fdrift!(du, u0, p, t) @@ -210,11 +210,11 @@ end prob = SDEProblem(f3!, σ3!, u0, trange, p) # no correction sys = modelingtoolkitize(prob) -fdrift = eval(generate_function(sys)[1]) +fdrift = eval(generate_rhs(sys)[1]) fdif = eval(generate_diffusion_function(sys)[1]) @test fdrift(u0, p, t) == u0 / 2 @test fdif(u0, p, t) == reverse(u0) -fdrift! = eval(generate_function(sys)[2]) +fdrift! = eval(generate_rhs(sys)[2]) fdif! = eval(generate_diffusion_function(sys)[2]) du = similar(u0) fdrift!(du, u0, p, t) @@ -224,11 +224,11 @@ fdif!(du, u0, p, t) # Ito -> Strat sys2 = stochastic_integral_transform(sys, -1 // 2) -fdrift = eval(generate_function(sys2)[1]) +fdrift = eval(generate_rhs(sys2)[1]) fdif = eval(generate_diffusion_function(sys2)[1]) @test fdrift(u0, p, t) == u0 * 0 @test fdif(u0, p, t) == reverse(u0) -fdrift! = eval(generate_function(sys2)[2]) +fdrift! = eval(generate_rhs(sys2)[2]) fdif! = eval(generate_diffusion_function(sys2)[2]) du = similar(u0) fdrift!(du, u0, p, t) @@ -238,11 +238,11 @@ fdif!(du, u0, p, t) # Strat -> Ito sys2 = stochastic_integral_transform(sys, 1 // 2) -fdrift = eval(generate_function(sys2)[1]) +fdrift = eval(generate_rhs(sys2)[1]) fdif = eval(generate_diffusion_function(sys2)[1]) @test fdrift(u0, p, t) == u0 @test fdif(u0, p, t) == reverse(u0) -fdrift! = eval(generate_function(sys2)[2]) +fdrift! = eval(generate_rhs(sys2)[2]) fdif! = eval(generate_diffusion_function(sys2)[2]) du = similar(u0) fdrift!(du, u0, p, t) @@ -260,11 +260,11 @@ f1(u, p, t) = p[1] * u prob = SDEProblem(f1, σ1, u0, trange, p) # no correction sys = modelingtoolkitize(prob) -fdrift = eval(generate_function(sys)[1]) +fdrift = eval(generate_rhs(sys)[1]) fdif = eval(generate_diffusion_function(sys)[1]) @test fdrift(u0, p, t) == p[1] * u0 @test fdif(u0, p, t) == p[2] * u0 -fdrift! = eval(generate_function(sys)[2]) +fdrift! = eval(generate_rhs(sys)[2]) fdif! = eval(generate_diffusion_function(sys)[2]) du = similar(u0) fdrift!(du, u0, p, t) @@ -274,11 +274,11 @@ fdif!(du, u0, p, t) # Ito -> Strat sys2 = stochastic_integral_transform(sys, -1 // 2) -fdrift = eval(generate_function(sys2)[1]) +fdrift = eval(generate_rhs(sys2)[1]) fdif = eval(generate_diffusion_function(sys2)[1]) @test fdrift(u0, p, t) == p[1] * u0 - 1 // 2 * p[2]^2 * u0 @test fdif(u0, p, t) == p[2] * u0 -fdrift! = eval(generate_function(sys2)[2]) +fdrift! = eval(generate_rhs(sys2)[2]) fdif! = eval(generate_diffusion_function(sys2)[2]) du = similar(u0) fdrift!(du, u0, p, t) @@ -288,11 +288,11 @@ fdif!(du, u0, p, t) # Strat -> Ito sys2 = stochastic_integral_transform(sys, 1 // 2) -fdrift = eval(generate_function(sys2)[1]) +fdrift = eval(generate_rhs(sys2)[1]) fdif = eval(generate_diffusion_function(sys2)[1]) @test fdrift(u0, p, t) == p[1] * u0 + 1 // 2 * p[2]^2 * u0 @test fdif(u0, p, t) == p[2] * u0 -fdrift! = eval(generate_function(sys2)[2]) +fdrift! = eval(generate_rhs(sys2)[2]) fdif! = eval(generate_diffusion_function(sys2)[2]) du = similar(u0) fdrift!(du, u0, p, t) @@ -316,12 +316,12 @@ end prob = SDEProblem(f4!, g4!, u0, trange, noise_rate_prototype = zeros(2, 2), p) # no correction sys = modelingtoolkitize(prob) -fdrift = eval(generate_function(sys)[1]) +fdrift = eval(generate_rhs(sys)[1]) fdif = eval(generate_diffusion_function(sys)[1]) @test fdrift(u0, p, t) == p[1] * u0 @test fdif(u0, p, t) == [p[2]*u0[1] p[3]*u0[1] p[4]*u0[1] p[5]*u0[2]] -fdrift! = eval(generate_function(sys)[2]) +fdrift! = eval(generate_rhs(sys)[2]) fdif! = eval(generate_diffusion_function(sys)[2]) du = similar(u0) fdrift!(du, u0, p, t) @@ -333,7 +333,7 @@ fdif!(du, u0, p, t) # Ito -> Strat sys2 = stochastic_integral_transform(sys, -1 // 2) -fdrift = eval(generate_function(sys2)[1]) +fdrift = eval(generate_rhs(sys2)[1]) fdif = eval(generate_diffusion_function(sys2)[1]) @test fdrift(u0, p, t) ≈ [ p[1] * u0[1] - 1 // 2 * (p[2]^2 * u0[1] + p[3]^2 * u0[1]), @@ -341,7 +341,7 @@ fdif = eval(generate_diffusion_function(sys2)[1]) ] @test fdif(u0, p, t) == [p[2]*u0[1] p[3]*u0[1] p[4]*u0[1] p[5]*u0[2]] -fdrift! = eval(generate_function(sys2)[2]) +fdrift! = eval(generate_rhs(sys2)[2]) fdif! = eval(generate_diffusion_function(sys2)[2]) du = similar(u0) fdrift!(du, u0, p, t) @@ -356,7 +356,7 @@ fdif!(du, u0, p, t) # Strat -> Ito sys2 = stochastic_integral_transform(sys, 1 // 2) -fdrift = eval(generate_function(sys2)[1]) +fdrift = eval(generate_rhs(sys2)[1]) fdif = eval(generate_diffusion_function(sys2)[1]) @test fdrift(u0, p, t) ≈ [ p[1] * u0[1] + 1 // 2 * (p[2]^2 * u0[1] + p[3]^2 * u0[1]), @@ -364,7 +364,7 @@ fdif = eval(generate_diffusion_function(sys2)[1]) ] @test fdif(u0, p, t) == [p[2]*u0[1] p[3]*u0[1] p[4]*u0[1] p[5]*u0[2]] -fdrift! = eval(generate_function(sys2)[2]) +fdrift! = eval(generate_rhs(sys2)[2]) fdif! = eval(generate_diffusion_function(sys2)[2]) du = similar(u0) fdrift!(du, u0, p, t) @@ -397,13 +397,13 @@ end prob = SDEProblem(f5!, g5!, u0, trange, noise_rate_prototype = zeros(2, 4), p) # no correction sys = modelingtoolkitize(prob) -fdrift = eval(generate_function(sys)[1]) +fdrift = eval(generate_rhs(sys)[1]) fdif = eval(generate_diffusion_function(sys)[1]) @test fdrift(u0, p, t) == 0 * u0 @test fdif(u0, p, t) == [cos(p[1])*sin(u0[1]) cos(p[1])*cos(u0[1]) -sin(p[1])*sin(u0[2]) -sin(p[1])*cos(u0[2]) sin(p[1])*sin(u0[1]) sin(p[1])*cos(u0[1]) cos(p[1])*sin(u0[2]) cos(p[1])*cos(u0[2])] -fdrift! = eval(generate_function(sys)[2]) +fdrift! = eval(generate_rhs(sys)[2]) fdif! = eval(generate_diffusion_function(sys)[2]) du = similar(u0) fdrift!(du, u0, p, t) @@ -416,13 +416,13 @@ fdif!(du, u0, p, t) # Ito -> Strat sys2 = stochastic_integral_transform(sys, -1 // 2) -fdrift = eval(generate_function(sys2)[1]) +fdrift = eval(generate_rhs(sys2)[1]) fdif = eval(generate_diffusion_function(sys2)[1]) @test fdrift(u0, p, t) == 0 * u0 @test fdif(u0, p, t) == [cos(p[1])*sin(u0[1]) cos(p[1])*cos(u0[1]) -sin(p[1])*sin(u0[2]) -sin(p[1])*cos(u0[2]) sin(p[1])*sin(u0[1]) sin(p[1])*cos(u0[1]) cos(p[1])*sin(u0[2]) cos(p[1])*cos(u0[2])] -fdrift! = eval(generate_function(sys2)[2]) +fdrift! = eval(generate_rhs(sys2)[2]) fdif! = eval(generate_diffusion_function(sys2)[2]) du = similar(u0) fdrift!(du, u0, p, t) @@ -435,13 +435,13 @@ fdif!(du, u0, p, t) # Strat -> Ito sys2 = stochastic_integral_transform(sys, 1 // 2) -fdrift = eval(generate_function(sys2)[1]) +fdrift = eval(generate_rhs(sys2)[1]) fdif = eval(generate_diffusion_function(sys2)[1]) @test fdrift(u0, p, t) == 0 * u0 @test fdif(u0, p, t) == [cos(p[1])*sin(u0[1]) cos(p[1])*cos(u0[1]) -sin(p[1])*sin(u0[2]) -sin(p[1])*cos(u0[2]) sin(p[1])*sin(u0[1]) sin(p[1])*cos(u0[1]) cos(p[1])*sin(u0[2]) cos(p[1])*cos(u0[2])] -fdrift! = eval(generate_function(sys2)[2]) +fdrift! = eval(generate_rhs(sys2)[2]) fdif! = eval(generate_diffusion_function(sys2)[2]) du = similar(u0) fdrift!(du, u0, p, t) @@ -463,7 +463,8 @@ fdif!(du, u0, p, t) x - y] sys1 = SDESystem(eqs_short, noise_eqs, t, [x, y, z], [σ, ρ, β], name = :sys1) sys2 = SDESystem(eqs_short, noise_eqs, t, [x, y, z], [σ, ρ, β], name = :sys1) - @test_throws ArgumentError SDESystem([sys2.y ~ sys1.z], [sys2.y], t, [], [], + @test_throws ModelingToolkit.NonUniqueSubsystemsError SDESystem( + [sys2.y ~ sys1.z], [sys2.y], t, [], [], systems = [sys1, sys2], name = :foo) end @@ -608,6 +609,8 @@ diffusion_eqs = [s*x 0 sys2 = SDESystem(drift_eqs, diffusion_eqs, tt, sts, ps, name = :sys1) sys2 = complete(sys2) +@set! sys1.parent = nothing +@set! sys2.parent = nothing @test sys1 == sys2 prob = SDEProblem(sys1, sts .=> [1.0, 0.0, 0.0], @@ -621,7 +624,8 @@ solve(prob, LambaEulerHeun(), seed = 1) @variables X(t) eqs = [D(X) ~ p - d * X] noise_eqs = [sqrt(p), -sqrt(d * X)] -@test_throws ArgumentError SDESystem(eqs, noise_eqs, t, [X], [p, d]; name = :ssys) +@test_throws ModelingToolkit.IllFormedNoiseEquationsError SDESystem( + eqs, noise_eqs, t, [X], [p, d]; name = :ssys) noise_eqs = reshape([sqrt(p), -sqrt(d * X)], 1, 2) ssys = SDESystem(eqs, noise_eqs, t, [X], [p, d]; name = :ssys) @@ -745,7 +749,7 @@ end @test_throws ErrorException solve(prob, SOSRI()).retcode==ReturnCode.Success # ImplicitEM does work for non-diagonal noise @test solve(prob, ImplicitEM()).retcode == ReturnCode.Success - @test size(ModelingToolkit.get_noiseeqs(de)) == (3, 6) + @test size(ModelingToolkit.get_noise_eqs(de)) == (3, 6) end @testset "Diagonal noise, less brownians than equations" begin @@ -795,7 +799,7 @@ end sys = System(eqs, t, sts, ps; name = :name) sys = structural_simplify(sys) - @test ModelingToolkit.get_noiseeqs(sys) ≈ [1.0] + @test ModelingToolkit.get_noise_eqs(sys) ≈ [1.0] prob = SDEProblem(sys, [], (0.0, 1.0), []) @test_nowarn solve(prob, RKMil()) end @@ -804,7 +808,6 @@ end @variables x(t) y(t) z(t) @brownian a @mtkbuild sys = System([D(x) ~ x + a, D(y) ~ y + a, z ~ x + y], t) - @test sys isa SDESystem @test length(observed(sys)) == 1 prob = SDEProblem(sys, [x => 1.0, y => 1.0], (0.0, 1.0)) @test prob[z] ≈ 2.0 @@ -815,11 +818,11 @@ end @testset "Scalar noise" begin @named sys = SDESystem([D(x) ~ x, D(y) ~ y, z ~ x + y], [x, y, 3], t, [x, y, z], [], is_scalar_noise = true) - odesys = System(sys) - @test odesys isa System + odesys = noise_to_brownians(sys) vs = ModelingToolkit.vars(equations(odesys)) nbrownian = count( v -> ModelingToolkit.getvariabletype(v) == ModelingToolkit.BROWNIAN, vs) + @test length(brownians(odesys)) == 3 @test nbrownian == 3 for eq in equations(odesys) ModelingToolkit.isdiffeq(eq) || continue @@ -828,10 +831,9 @@ end end @testset "Non-scalar vector noise" begin - @named sys = SDESystem([D(x) ~ x, D(y) ~ y, z ~ x + y], [x, y, 0], - t, [x, y, z], [], is_scalar_noise = false) - odesys = System(sys) - @test odesys isa System + @named sys = SDESystem([D(x) ~ x, D(y) ~ y, z ~ x + y], [x; y; 0;;], + t, [x, y, z], []; is_scalar_noise = false) + odesys = noise_to_brownians(sys) vs = ModelingToolkit.vars(equations(odesys)) nbrownian = count( v -> ModelingToolkit.getvariabletype(v) == ModelingToolkit.BROWNIAN, vs) @@ -847,8 +849,7 @@ end 2y 2z 2x z+1 x+1 y+1] @named sys = SDESystem([D(x) ~ x, D(y) ~ y, D(z) ~ z], noiseeqs, t, [x, y, z], []) - odesys = System(sys) - @test odesys isa System + odesys = noise_to_brownians(sys) vs = ModelingToolkit.vars(equations(odesys)) nbrownian = count( v -> ModelingToolkit.getvariabletype(v) == ModelingToolkit.BROWNIAN, vs) @@ -862,10 +863,9 @@ end @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) - @test sys isa SDESystem + [D(x) ~ x, y ~ 2x], [x, 0], t, [x, y], []) @test length(equations(sys)) == 1 - @test length(ModelingToolkit.get_noiseeqs(sys)) == 1 + @test length(ModelingToolkit.get_noise_eqs(sys)) == 1 @test length(observed(sys)) == 1 end @@ -875,9 +875,11 @@ end @variables X(t)::Int64 @brownian z eq2 = D(X) ~ p - d * X + z - @test_throws ArgumentError @mtkbuild ssys = System([eq2], t) + @test_throws ModelingToolkit.ContinuousOperatorDiscreteArgumentError @mtkbuild ssys = System( + [eq2], t) noiseeq = [1] - @test_throws ArgumentError @named ssys = SDESystem([eq2], [noiseeq], t) + @test_throws ModelingToolkit.ContinuousOperatorDiscreteArgumentError @named ssys = SDESystem( + [eq2], [noiseeq], t) end @testset "SDEFunctionExpr" begin @@ -894,30 +896,30 @@ end @named sys = System(eqs, tt, [x, y, z], [σ, ρ, β]) - @named de = SDESystem(eqs, noiseeqs, tt, [x, y, z], [σ, ρ, β], tspan = (0.0, 10.0)) + @named de = SDESystem(eqs, noiseeqs, tt, [x, y, z], [σ, ρ, β]) de = complete(de) - f = SDEFunctionExpr(de) + f = SDEFunction(de; expression = Val{true}) @test f isa Expr @testset "Configuration Tests" begin # Test with `tgrad` - f_tgrad = SDEFunctionExpr(de; tgrad = true) + f_tgrad = SDEFunction(de; tgrad = true, expression = Val{true}) @test f_tgrad isa Expr # Test with `jac` - f_jac = SDEFunctionExpr(de; jac = true) + f_jac = SDEFunction(de; jac = true, expression = Val{true}) @test f_jac isa Expr # Test with sparse Jacobian - f_sparse = SDEFunctionExpr(de; sparse = true) + f_sparse = SDEFunction(de; sparse = true, expression = Val{true}) @test f_sparse isa Expr end @testset "Ordering Tests" begin dvs = [z, y, x] ps = [β, ρ, σ] - f_order = SDEFunctionExpr(de, dvs, ps) + f_order = SDEFunction(de; expression = Val{true}) @test f_order isa Expr end end @@ -947,7 +949,7 @@ end @test ssys1 !== ssys2 end -@testset "Error when constructing SDESystem without `structural_simplify`" begin +@testset "Error when constructing SDEProblem without `structural_simplify`" begin @parameters σ ρ β @variables x(tt) y(tt) z(tt) @brownian a @@ -961,7 +963,7 @@ 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 ["Brownian", "structural_simplify"] SDEProblem( de, u0map, (0.0, 100.0), parammap) de = structural_simplify(de) @test SDEProblem(de, u0map, (0.0, 100.0), parammap) isa SDEProblem From c871e5221b0094aad46f61fdeab4fa052b3b708e Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Wed, 14 May 2025 14:05:54 +0530 Subject: [PATCH 192/235] fix: unwrap in `add_toterms!`, don't override existing values --- src/systems/problem_utils.jl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/systems/problem_utils.jl b/src/systems/problem_utils.jl index 912496fc69..84a77364ae 100644 --- a/src/systems/problem_utils.jl +++ b/src/systems/problem_utils.jl @@ -37,7 +37,8 @@ the old value should be removed. """ function add_toterms!(varmap::AbstractDict; toterm = default_toterm, replace = false) for k in collect(keys(varmap)) - ttk = toterm(k) + ttk = toterm(unwrap(k)) + haskey(varmap, ttk) && continue varmap[ttk] = varmap[k] !isequal(k, ttk) && replace && delete!(varmap, k) end From 192dff72099c6495fd19439152b090f7e39346a3 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Wed, 14 May 2025 14:06:13 +0530 Subject: [PATCH 193/235] fix: unwrap `varmap` and add toterms in `better_varmap_to_vars` --- src/systems/problem_utils.jl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/systems/problem_utils.jl b/src/systems/problem_utils.jl index 84a77364ae..8562bb1cfa 100644 --- a/src/systems/problem_utils.jl +++ b/src/systems/problem_utils.jl @@ -352,6 +352,8 @@ function better_varmap_to_vars(varmap::AbstractDict, vars::Vector; allow_symbolic = false, is_initializeprob = false) isempty(vars) && return nothing + varmap = recursive_unwrap(varmap) + add_toterms!(varmap; toterm) if check missing_vars = missingvars(varmap, vars; toterm) isempty(missing_vars) || throw(MissingVariablesError(missing_vars)) From 1e10954b516afdd7c8ca2d8871f1c0f1c28f59d8 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Thu, 15 May 2025 00:41:46 +0530 Subject: [PATCH 194/235] test: update mtkparameters tests --- test/mtkparameters.jl | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/test/mtkparameters.jl b/test/mtkparameters.jl index 122b7acdd1..347043d437 100644 --- a/test/mtkparameters.jl +++ b/test/mtkparameters.jl @@ -181,8 +181,8 @@ function level1() D(y) ~ -p3 * y + p4 * x * y] sys = structural_simplify(complete(System( - eqs, t, tspan = (0, 3.0), name = :sys, parameter_dependencies = [y0 => 2p4]))) - prob = ODEProblem{true, SciMLBase.FullSpecialize}(sys) + eqs, t, name = :sys, parameter_dependencies = [y0 => 2p4]))) + prob = ODEProblem{true, SciMLBase.FullSpecialize}(sys, [], (0.0, 3.0)) end # scalar and vector parameters @@ -195,8 +195,8 @@ function level2() D(y) ~ -p23[2] * y + p4 * x * y] sys = structural_simplify(complete(System( - eqs, t, tspan = (0, 3.0), name = :sys, parameter_dependencies = [y0 => 2p4]))) - prob = ODEProblem{true, SciMLBase.FullSpecialize}(sys) + eqs, t, name = :sys, parameter_dependencies = [y0 => 2p4]))) + prob = ODEProblem{true, SciMLBase.FullSpecialize}(sys, [], (0.0, 3.0)) end # scalar and vector parameters with different scalar types @@ -209,8 +209,8 @@ function level3() D(y) ~ -p23[2] * y + p4 * x * y] sys = structural_simplify(complete(System( - eqs, t, tspan = (0, 3.0), name = :sys, parameter_dependencies = [y0 => 2p4]))) - prob = ODEProblem{true, SciMLBase.FullSpecialize}(sys) + eqs, t, name = :sys, parameter_dependencies = [y0 => 2p4]))) + prob = ODEProblem{true, SciMLBase.FullSpecialize}(sys, [], (0.0, 3.0)) end @testset "level$i" for (i, prob) in enumerate([level1(), level2(), level3()]) From c2f0424ade618818b9ad436e4422cfab84c7a4eb Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Thu, 15 May 2025 00:41:55 +0530 Subject: [PATCH 195/235] test: update serialization tests --- test/serialization.jl | 24 +++--------------------- 1 file changed, 3 insertions(+), 21 deletions(-) diff --git a/test/serialization.jl b/test/serialization.jl index cce015c776..1a0105d155 100644 --- a/test/serialization.jl +++ b/test/serialization.jl @@ -8,8 +8,8 @@ sys = complete(sys) for prob in [ eval(ModelingToolkit.ODEProblem{false}(sys, nothing, nothing, SciMLBase.NullParameters())), - eval(ModelingToolkit.ODEProblemExpr{false}(sys, nothing, nothing, - SciMLBase.NullParameters())) + eval(ModelingToolkit.ODEProblem{false}(sys, nothing, nothing, + SciMLBase.NullParameters(); expression = Val{true})) ] _fn = tempname() @@ -47,26 +47,8 @@ sol_ = solve(prob_, ImplicitEuler()) ## Check ODEProblemExpr with Observables ----------- # build the observable function expression -obs_exps = [] -for var in all_obs - f = ModelingToolkit.build_explicit_observed_function(ss, var; expression = true) - sym = ModelingToolkit.getname(var) |> string - ex = :(if name == Symbol($sym) - return $f(u0, p, t) - end) - push!(obs_exps, ex) -end -# observedfun expression for ODEFunctionExpr -observedfun_exp = :(function obs(var, u0, p, t) - if var isa AbstractArray - return obs.(var, (u0,), (p,), (t,)) - end - name = ModelingToolkit.getname(var) - $(obs_exps...) -end) - # ODEProblemExpr with observedfun_exp included -probexpr = ODEProblemExpr{true}(ss, [capacitor.v => 0.0], (0, 0.1); observedfun_exp); +probexpr = ODEProblem{true}(ss, [capacitor.v => 0.0], (0, 0.1); expr = Val{true}); prob_obs = eval(probexpr) sol_obs = solve(prob_obs, ImplicitEuler()) @show all_obs From cf61fe3abba26c8d97dba5b21b1919ecf5116b74 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Thu, 15 May 2025 00:42:22 +0530 Subject: [PATCH 196/235] test: fix symbolic indexing interface tests --- test/symbolic_indexing_interface.jl | 27 ++++++++++++--------------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/test/symbolic_indexing_interface.jl b/test/symbolic_indexing_interface.jl index 9c478565fa..14cf0c10a7 100644 --- a/test/symbolic_indexing_interface.jl +++ b/test/symbolic_indexing_interface.jl @@ -179,21 +179,18 @@ end @test isequal(parameters(pdesys), [h]) end -# Issue#2767 -using ModelingToolkit -using ModelingToolkit: t_nounits as t, D_nounits as D -using SymbolicIndexingInterface - -@parameters p1[1:2]=[1.0, 2.0] p2[1:2]=[0.0, 0.0] -@variables x(t) = 0 - -@named sys = System( - [D(x) ~ sum(p1) * t + sum(p2)], - t; -) -prob = ODEProblem(complete(sys)) -get_dep = @test_nowarn getu(prob, 2p1) -@test get_dep(prob) == [2.0, 4.0] +@testset "Issue#2767" begin + @parameters p1[1:2]=[1.0, 2.0] p2[1:2]=[0.0, 0.0] + @variables x(t) = 0 + + @named sys = System( + [D(x) ~ sum(p1) * t + sum(p2)], + t; + ) + prob = ODEProblem(complete(sys), [], (0.0, 1.0)) + get_dep = @test_nowarn getu(prob, 2p1) + @test get_dep(prob) == [2.0, 4.0] +end @testset "Observed functions with variables as `Symbol`s" begin @variables x(t) y(t) z(t)[1:2] From 573146e102f21144f5133b00c970fed1841e043f Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Thu, 15 May 2025 00:43:02 +0530 Subject: [PATCH 197/235] fix: fix `Pre` parameter discovery for `AffectSystem` --- src/systems/callbacks.jl | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/systems/callbacks.jl b/src/systems/callbacks.jl index d0bd18a65a..631e9fe216 100644 --- a/src/systems/callbacks.jl +++ b/src/systems/callbacks.jl @@ -285,12 +285,17 @@ function make_affect(affect::Vector{Equation}; discrete_parameters = Any[], dvs = OrderedSet() params = OrderedSet() + _varsbuf = Set() 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 the algebraic equation is unsatisfiable, such as X ~ X + 1." end collect_vars!(dvs, params, eq, iv; op = Pre) + empty!(_varsbuf) + vars!(_varsbuf, eq; op = Pre) + filter!(x -> iscall(x) && operation(x) isa Pre, _varsbuf) + union!(params, _varsbuf) diffvs = collect_applied_operators(eq, Differential) union!(dvs, diffvs) end From f3d62880fd8a695079db6d23079a90d9ac0484a0 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Thu, 15 May 2025 00:43:53 +0530 Subject: [PATCH 198/235] fix: fix `compile_condition`, respect `eval_expression` and `eval_module` --- src/systems/callbacks.jl | 52 ++++++++++++++++++++++++++-------------- 1 file changed, 34 insertions(+), 18 deletions(-) diff --git a/src/systems/callbacks.jl b/src/systems/callbacks.jl index 631e9fe216..365f3c4eab 100644 --- a/src/systems/callbacks.jl +++ b/src/systems/callbacks.jl @@ -596,6 +596,33 @@ Base.isempty(cb::AbstractCallback) = isempty(cb.conditions) #################################### ####### Compilation functions ###### #################################### + +struct CompiledCondition{IsDiscrete, F} + f::F +end + +function CompiledCondition{ID}(f::F) where {ID, F} + return CompiledCondition{ID, F}(f) +end + +function (cc::CompiledCondition)(out, u, t, integ) + cc.f(out, u, parameter_values(integ), t) +end + +function (cc::CompiledCondition{false})(u, t, integ) + if DiffEqBase.isinplace(SciMLBase.get_sol(integ).prob) + tmp, = DiffEqBase.get_tmp_cache(integ) + cc.f(tmp, u, parameter_values(integ), t) + tmp[1] + else + cc.f(u, parameter_values(integ), t) + end +end + +function (cc::CompiledCondition{true})(u, t, integ) + cc.f(u, parameter_values(integ), t) +end + """ compile_condition(cb::AbstractCallback, sys, dvs, ps; expression, kwargs...) @@ -615,30 +642,19 @@ function compile_condition( end if !is_discrete(cbs) - condit = reduce(vcat, flatten_equations(condit)) + condit = reduce(vcat, flatten_equations(Vector{Equation}(condit))) condit = condit isa AbstractVector ? [c.lhs - c.rhs for c in condit] : [condit.lhs - condit.rhs] end 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 - (out, u, t, integ) -> f_iip(out, u, parameter_values(integ), t) - elseif is_discrete(cbs) - (u, t, integ) -> f_oop(u, parameter_values(integ), t) - else - function (u, t, integ) - if DiffEqBase.isinplace(SciMLBase.get_sol(integ).prob) - tmp, = DiffEqBase.get_tmp_cache(integ) - f_iip(tmp, u, parameter_values(integ), t) - tmp[1] - else - f_oop(u, parameter_values(integ), t) - end - end + sys, condit, u, p..., t; kwargs..., cse = false) + if is_discrete(cbs) + fs = (fs, nothing) end + fs = GeneratedFunctionWrapper{(2, 3, is_split(sys))}( + Val{false}, fs...; eval_expression, eval_module) + return CompiledCondition{is_discrete(cbs)}(fs) end """ From 0d8fcb8bf49460dba4642e65d752b83ebfa14f7b Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Thu, 15 May 2025 00:44:27 +0530 Subject: [PATCH 199/235] fix: respect `eval_expression`, `eval_module` in `compile_equational_affect` --- src/systems/callbacks.jl | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/systems/callbacks.jl b/src/systems/callbacks.jl index 365f3c4eab..0bb2318e4b 100644 --- a/src/systems/callbacks.jl +++ b/src/systems/callbacks.jl @@ -895,7 +895,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...) + aff::Union{AffectSystem, Vector{Equation}}, sys; reset_jumps = false, + eval_expression = false, eval_module = @__MODULE__, kwargs...) if aff isa AbstractVector aff = make_affect( aff; iv = get_iv(sys), warn_no_algebraic = false) @@ -930,11 +931,13 @@ function compile_equational_affect( 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, cse = false) + wrap_code = add_integrator_header(sys, integ, :u), expression = Val{false}, + outputidxs = u_idxs, wrap_mtkparameters, cse = false, eval_expression, + eval_module) 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, cse = false) + wrap_code = add_integrator_header(sys, integ, :p), expression = Val{false}, + outputidxs = p_idxs, wrap_mtkparameters, cse = false, eval_expression, + eval_module) 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! @@ -963,7 +966,8 @@ function compile_equational_affect( affprob = ImplicitDiscreteProblem(affsys, [dv => 0 for dv in unknowns(affsys)], (0, 0), [p => 0.0 for p in parameters(affsys)]; - build_initializeprob = false, check_length = false) + build_initializeprob = false, check_length = false, eval_expression, + eval_module, check_compatibility = false) function implicit_affect!(integ) new_u0 = affu_getter(integ) From 3a7a54ea2aa1663149f586b095c259f62263010a Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Thu, 15 May 2025 00:44:34 +0530 Subject: [PATCH 200/235] test: fix symbolic event tests --- test/symbolic_events.jl | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/test/symbolic_events.jl b/test/symbolic_events.jl index 1d611360a3..ee0eaf6392 100644 --- a/test/symbolic_events.jl +++ b/test/symbolic_events.jl @@ -238,11 +238,11 @@ end cb = ModelingToolkit.generate_continuous_callbacks(sys) cond = cb.condition out = [0.0] - cond.f_iip(out, [0], p0, t0) + cond.f(out, [0], p0, t0) @test out[] ≈ -1 # signature is u,p,t - cond.f_iip(out, [1], p0, t0) + cond.f(out, [1], p0, t0) @test out[] ≈ 0 # signature is u,p,t - cond.f_iip(out, [2], p0, t0) + cond.f(out, [2], p0, t0) @test out[] ≈ 1 # signature is u,p,t prob = ODEProblem(sys, Pair[], (0.0, 2.0)) @@ -270,20 +270,20 @@ end cond = cb.condition out = [0.0, 0.0] # the root to find is 2 - cond.f_iip(out, [0, 0], p0, t0) + cond.f(out, [0, 0], p0, t0) @test out[1] ≈ -2 # signature is u,p,t - cond.f_iip(out, [1, 0], p0, t0) + cond.f(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 + cond.f(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) + cond.f(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 + cond.f(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) + cond.f(out, [0, 2], p0, t0) @test out[2] ≈ 1 # signature is u,p,t sol = solve(prob, Tsit5()) @@ -351,7 +351,7 @@ end out = [0.0, 0.0, 0.0] p0 = 0.0 t0 = 0.0 - cond.f_iip(out, [0, 0, 0, 0], p0, t0) + cond.f(out, [0, 0, 0, 0], p0, t0) @test out ≈ [0, 1.5, -1.5] sol = solve(prob, Tsit5()) From e5223b818a5d291c2542f27cac414125e3869928 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Thu, 15 May 2025 11:59:56 +0530 Subject: [PATCH 201/235] fix: update CasADi extension to new semantics --- ext/MTKCasADiDynamicOptExt.jl | 76 +++++++++++++++++------------------ 1 file changed, 37 insertions(+), 39 deletions(-) diff --git a/ext/MTKCasADiDynamicOptExt.jl b/ext/MTKCasADiDynamicOptExt.jl index 6aa15fb55a..a5400e3c8d 100644 --- a/ext/MTKCasADiDynamicOptExt.jl +++ b/ext/MTKCasADiDynamicOptExt.jl @@ -56,9 +56,9 @@ function (M::MXLinearInterpolation)(τ) end """ - CasADiDynamicOptProblem(sys::ODESystem, u0, tspan, p; dt, steps) + CasADiDynamicOptProblem(sys::System, u0, tspan, p; dt, steps) -Convert an ODESystem representing an optimal control system into a CasADi model +Convert an System representing an optimal control system into a CasADi model for solving using optimization. Must provide either `dt`, the timestep between collocation points (which, along with the timespan, determines the number of points), or directly provide the number of points as `steps`. @@ -68,10 +68,10 @@ The optimization variables: - a vector-of-vectors V representing the controls as an interpolation array The constraints are: -- The set of user constraints passed to the ODESystem via `constraints` +- The set of user constraints passed to the System via `constraints` - The solver constraints that encode the time-stepping used by the solver """ -function MTK.CasADiDynamicOptProblem(sys::ODESystem, u0map, tspan, pmap; +function MTK.CasADiDynamicOptProblem(sys::System, u0map, tspan, pmap; dt = nothing, steps = nothing, guesses = Dict(), kwargs...) @@ -80,7 +80,8 @@ function MTK.CasADiDynamicOptProblem(sys::ODESystem, u0map, tspan, pmap; f, u0, p = MTK.process_SciMLProblem(ODEInputFunction, sys, _u0map, pmap; t = tspan !== nothing ? tspan[1] : tspan, output_type = MX, kwargs...) - pmap = Dict{Any, Any}(pmap) + pmap = MTK.recursive_unwrap(MTK.AnyDict(pmap)) + MTK.evaluate_varmap!(pmap, keys(pmap)) steps, is_free_t = MTK.process_tspan(tspan, dt, steps) model = init_model(sys, tspan, steps, u0map, pmap, u0; is_free_t) @@ -143,15 +144,15 @@ function set_casadi_bounds!(model, sys, pmap) for (i, u) in enumerate(unknowns(sys)) if MTK.hasbounds(u) lo, hi = MTK.getbounds(u) - subject_to!(opti, Symbolics.fixpoint_sub(lo, pmap) <= U.u[i, :]) - subject_to!(opti, U.u[i, :] <= Symbolics.fixpoint_sub(hi, pmap)) + subject_to!(opti, Symbolics.fast_substitute(lo, pmap) <= U.u[i, :]) + subject_to!(opti, U.u[i, :] <= Symbolics.fast_substitute(hi, pmap)) end end for (i, v) in enumerate(MTK.unbound_inputs(sys)) if MTK.hasbounds(v) lo, hi = MTK.getbounds(v) - subject_to!(opti, Symbolics.fixpoint_sub(lo, pmap) <= V.u[i, :]) - subject_to!(opti, V.u[i, :] <= Symbolics.fixpoint_sub(hi, pmap)) + subject_to!(opti, Symbolics.fast_substitute(lo, pmap) <= V.u[i, :]) + subject_to!(opti, V.u[i, :] <= Symbolics.fast_substitute(hi, pmap)) end end end @@ -167,15 +168,15 @@ function add_user_constraints!(model::CasADiModel, sys, tspan, pmap; is_free_t) @unpack opti, U, V, tₛ = model iv = MTK.get_iv(sys) - conssys = MTK.get_constraintsystem(sys) - jconstraints = isnothing(conssys) ? nothing : MTK.get_constraints(conssys) + jconstraints = MTK.get_constraints(sys) (isnothing(jconstraints) || isempty(jconstraints)) && return nothing stidxmap = Dict([v => i for (i, v) in enumerate(unknowns(sys))]) ctidxmap = Dict([v => i for (i, v) in enumerate(MTK.unbound_inputs(sys))]) - cons_unknowns = map(MTK.default_toterm, unknowns(conssys)) + cons_dvs, cons_ps = MTK.process_constraint_system( + jconstraints, Set(unknowns(sys)), parameters(sys), iv; validate = false) - auxmap = Dict([u => MTK.default_toterm(MTK.value(u)) for u in unknowns(conssys)]) + auxmap = Dict([u => MTK.default_toterm(MTK.value(u)) for u in cons_dvs]) jconstraints = substitute_casadi_vars(model, sys, pmap, jconstraints; is_free_t, auxmap) # Manually substitute fixed-t variables for (i, cons) in enumerate(jconstraints) @@ -207,9 +208,8 @@ end function add_cost_function!(model::CasADiModel, sys, tspan, pmap; is_free_t) @unpack opti, U, V, tₛ = model - jcosts = copy(MTK.get_costs(sys)) - consolidate = MTK.get_consolidate(sys) - if isnothing(jcosts) || isempty(jcosts) + jcosts = cost(sys) + if Symbolics._iszero(jcosts) minimize!(opti, MX(0)) return end @@ -218,24 +218,22 @@ function add_cost_function!(model::CasADiModel, sys, tspan, pmap; is_free_t) stidxmap = Dict([v => i for (i, v) in enumerate(unknowns(sys))]) ctidxmap = Dict([v => i for (i, v) in enumerate(MTK.unbound_inputs(sys))]) - jcosts = substitute_casadi_vars(model, sys, pmap, jcosts; is_free_t) + jcosts = substitute_casadi_vars(model, sys, pmap, [jcosts]; is_free_t)[1] # Substitute fixed-time variables. - for i in 1:length(jcosts) - costvars = MTK.vars(jcosts[i]) - for st in costvars - MTK.iscall(st) || continue - x = operation(st) - t = only(arguments(st)) - MTK.symbolic_type(t) === MTK.NotSymbolic() || continue - if haskey(stidxmap, x(iv)) - idx = stidxmap[x(iv)] - cv = U - else - idx = ctidxmap[x(iv)] - cv = V - end - jcosts[i] = Symbolics.substitute(jcosts[i], Dict(x(t) => cv(t)[idx])) + costvars = MTK.vars(jcosts) + for st in costvars + MTK.iscall(st) || continue + x = operation(st) + t = only(arguments(st)) + MTK.symbolic_type(t) === MTK.NotSymbolic() || continue + if haskey(stidxmap, x(iv)) + idx = stidxmap[x(iv)] + cv = U + else + idx = ctidxmap[x(iv)] + cv = V end + jcosts = Symbolics.substitute(jcosts, Dict(x(t) => cv(t)[idx])) end dt = U.t[2] - U.t[1] @@ -249,9 +247,9 @@ function add_cost_function!(model::CasADiModel, sys, tspan, pmap; is_free_t) # Approximate integral as sum. intmap[int] = dt * tₛ * sum(arg) end - jcosts = map(c -> Symbolics.substitute(c, intmap), jcosts) - jcosts = MTK.value.(jcosts) - minimize!(opti, MX(MTK.value(consolidate(jcosts)))) + jcosts = Symbolics.substitute(jcosts, intmap) + jcosts = MTK.value(jcosts) + minimize!(opti, MX(jcosts)) end function substitute_casadi_vars( @@ -264,20 +262,20 @@ function substitute_casadi_vars( x_ops = [MTK.operation(MTK.unwrap(st)) for st in sts] c_ops = [MTK.operation(MTK.unwrap(ct)) for ct in cts] - exprs = map(c -> Symbolics.fixpoint_sub(c, auxmap), exprs) - exprs = map(c -> Symbolics.fixpoint_sub(c, Dict(pmap)), exprs) + exprs = map(c -> Symbolics.fast_substitute(c, auxmap), exprs) + exprs = map(c -> Symbolics.fast_substitute(c, Dict(pmap)), exprs) # tf means different things in different contexts; a [tf] in a cost function # should be tₛ, while a x(tf) should translate to x[1] if is_free_t free_t_map = Dict([[x(tₛ) => U.u[i, end] for (i, x) in enumerate(x_ops)]; [c(tₛ) => V.u[i, end] for (i, c) in enumerate(c_ops)]]) - exprs = map(c -> Symbolics.fixpoint_sub(c, free_t_map), exprs) + exprs = map(c -> Symbolics.fast_substitute(c, free_t_map), exprs) end # for variables like x(t) whole_interval_map = Dict([[v => U.u[i, :] for (i, v) in enumerate(sts)]; [v => V.u[i, :] for (i, v) in enumerate(cts)]]) - exprs = map(c -> Symbolics.fixpoint_sub(c, whole_interval_map), exprs) + exprs = map(c -> Symbolics.fast_substitute(c, whole_interval_map), exprs) exprs end From 96e3d810092297349365194d00259355bed14ac0 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Thu, 15 May 2025 12:00:13 +0530 Subject: [PATCH 202/235] fix: update InfiniteOpt extension to new semantics --- ext/MTKInfiniteOptExt.jl | 44 +++++++++++++++++++++------------------- 1 file changed, 23 insertions(+), 21 deletions(-) diff --git a/ext/MTKInfiniteOptExt.jl b/ext/MTKInfiniteOptExt.jl index dd7da24672..f4fff61dff 100644 --- a/ext/MTKInfiniteOptExt.jl +++ b/ext/MTKInfiniteOptExt.jl @@ -63,7 +63,8 @@ function MTK.JuMPDynamicOptProblem(sys::System, u0map, tspan, pmap; f, u0, p = MTK.process_SciMLProblem(ODEInputFunction, sys, _u0map, pmap; t = tspan !== nothing ? tspan[1] : tspan, kwargs...) - pmap = Dict{Any, Any}(pmap) + pmap = MTK.recursive_unwrap(MTK.AnyDict(pmap)) + MTK.evaluate_varmap!(pmap, keys(pmap)) steps, is_free_t = MTK.process_tspan(tspan, dt, steps) model = init_model(sys, tspan, steps, u0map, pmap, u0; is_free_t) @@ -89,7 +90,8 @@ function MTK.InfiniteOptDynamicOptProblem(sys::System, u0map, tspan, pmap; f, u0, p = MTK.process_SciMLProblem(ODEInputFunction, sys, _u0map, pmap; t = tspan !== nothing ? tspan[1] : tspan, kwargs...) - pmap = Dict{Any, Any}(pmap) + pmap = MTK.recursive_unwrap(MTK.AnyDict(pmap)) + MTK.evaluate_varmap!(pmap, keys(pmap)) steps, is_free_t = MTK.process_tspan(tspan, dt, steps) model = init_model(sys, tspan, steps, u0map, pmap, u0; is_free_t) @@ -150,8 +152,8 @@ function set_jump_bounds!(model, sys, pmap) for (i, u) in enumerate(unknowns(sys)) if MTK.hasbounds(u) lo, hi = MTK.getbounds(u) - set_lower_bound(U[i], Symbolics.fixpoint_sub(lo, pmap)) - set_upper_bound(U[i], Symbolics.fixpoint_sub(hi, pmap)) + set_lower_bound(U[i], Symbolics.fast_substitute(lo, pmap)) + set_upper_bound(U[i], Symbolics.fast_substitute(hi, pmap)) end end @@ -159,20 +161,19 @@ function set_jump_bounds!(model, sys, pmap) for (i, v) in enumerate(MTK.unbound_inputs(sys)) if MTK.hasbounds(v) lo, hi = MTK.getbounds(v) - set_lower_bound(V[i], Symbolics.fixpoint_sub(lo, pmap)) - set_upper_bound(V[i], Symbolics.fixpoint_sub(hi, pmap)) + set_lower_bound(V[i], Symbolics.fast_substitute(lo, pmap)) + set_upper_bound(V[i], Symbolics.fast_substitute(hi, pmap)) end end end function add_jump_cost_function!(model::InfiniteModel, sys, tspan, pmap; is_free_t = false) - jcosts = MTK.get_costs(sys) - consolidate = MTK.get_consolidate(sys) - if isnothing(jcosts) || isempty(jcosts) + jcosts = cost(sys) + if Symbolics._iszero(jcosts) @objective(model, Min, 0) return end - jcosts = substitute_jump_vars(model, sys, pmap, jcosts; is_free_t) + jcosts = substitute_jump_vars(model, sys, pmap, [jcosts]; is_free_t)[1] tₛ = is_free_t ? model[:tf] : 1 # Substitute integral @@ -187,17 +188,18 @@ function add_jump_cost_function!(model::InfiniteModel, sys, tspan, pmap; is_free hi = haskey(pmap, hi) ? 1 : MTK.value(hi) intmap[int] = tₛ * InfiniteOpt.∫(arg, model[:t], lo, hi) end - jcosts = map(c -> Symbolics.substitute(c, intmap), jcosts) - @objective(model, Min, consolidate(jcosts)) + jcosts = Symbolics.substitute(jcosts, intmap) + @objective(model, Min, MTK.value(jcosts)) end function add_user_constraints!(model::InfiniteModel, sys, pmap; is_free_t = false) - conssys = MTK.get_constraintsystem(sys) - jconstraints = isnothing(conssys) ? nothing : MTK.get_constraints(conssys) + jconstraints = MTK.get_constraints(sys) (isnothing(jconstraints) || isempty(jconstraints)) && return nothing + cons_dvs, cons_ps = MTK.process_constraint_system( + jconstraints, Set(unknowns(sys)), parameters(sys), MTK.get_iv(sys); validate = false) if is_free_t - for u in MTK.get_unknowns(conssys) + for u in cons_dvs x = MTK.operation(u) t = only(arguments(u)) if (MTK.symbolic_type(t) === MTK.NotSymbolic()) @@ -206,7 +208,7 @@ function add_user_constraints!(model::InfiniteModel, sys, pmap; is_free_t = fals end end - auxmap = Dict([u => MTK.default_toterm(MTK.value(u)) for u in unknowns(conssys)]) + auxmap = Dict([u => MTK.default_toterm(MTK.value(u)) for u in cons_dvs]) jconstraints = substitute_jump_vars(model, sys, pmap, jconstraints; auxmap, is_free_t) # Substitute to-term'd variables @@ -235,25 +237,25 @@ function substitute_jump_vars(model, sys, pmap, exprs; auxmap = Dict(), is_free_ x_ops = [MTK.operation(MTK.unwrap(st)) for st in sts] c_ops = [MTK.operation(MTK.unwrap(ct)) for ct in cts] - exprs = map(c -> Symbolics.fixpoint_sub(c, auxmap), exprs) - exprs = map(c -> Symbolics.fixpoint_sub(c, Dict(pmap)), exprs) + exprs = map(c -> Symbolics.fast_substitute(c, auxmap), exprs) + exprs = map(c -> Symbolics.fast_substitute(c, Dict(pmap)), exprs) if is_free_t tf = model[:tf] free_t_map = Dict([[x(tf) => U[i](1) for (i, x) in enumerate(x_ops)]; [c(tf) => V[i](1) for (i, c) in enumerate(c_ops)]]) - exprs = map(c -> Symbolics.fixpoint_sub(c, free_t_map), exprs) + exprs = map(c -> Symbolics.fast_substitute(c, free_t_map), exprs) end # for variables like x(t) whole_interval_map = Dict([[v => U[i] for (i, v) in enumerate(sts)]; [v => V[i] for (i, v) in enumerate(cts)]]) - exprs = map(c -> Symbolics.fixpoint_sub(c, whole_interval_map), exprs) + exprs = map(c -> Symbolics.fast_substitute(c, whole_interval_map), exprs) # for variables like x(1.0) fixed_t_map = Dict([[x_ops[i] => U[i] for i in 1:length(U)]; [c_ops[i] => V[i] for i in 1:length(V)]]) - exprs = map(c -> Symbolics.fixpoint_sub(c, fixed_t_map), exprs) + exprs = map(c -> Symbolics.fast_substitute(c, fixed_t_map), exprs) exprs end From 23ff019a40b6b3b77903736ac748b8b2131864e2 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Thu, 15 May 2025 12:01:09 +0530 Subject: [PATCH 203/235] fix: update `optimal_control_interface.jl` to new semantics --- src/systems/optimal_control_interface.jl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/systems/optimal_control_interface.jl b/src/systems/optimal_control_interface.jl index 2ee2d0e9ca..c88aceabff 100644 --- a/src/systems/optimal_control_interface.jl +++ b/src/systems/optimal_control_interface.jl @@ -21,9 +21,9 @@ function InfiniteOptDynamicOptProblem end function CasADiDynamicOptProblem end function warn_overdetermined(sys, u0map) - constraintsys = get_constraintsystem(sys) - if !isnothing(constraintsys) - (length(constraints(constraintsys)) + length(u0map) > length(unknowns(sys))) && + cstrs = constraints(sys) + if !isempty(cstrs) + (length(cstrs) + length(u0map) > length(unknowns(sys))) && @warn "The control problem is overdetermined. The total number of conditions (# constraints + # fixed initial values given by u0map) exceeds the total number of states. The solvers will default to doing a nonlinear least-squares optimization." end end From 17caec8f39c5c9fdf5e27c2b21890647bd3c6bd2 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Thu, 15 May 2025 14:09:19 +0530 Subject: [PATCH 204/235] fix: recognize delayed derivatives in `isdelay` --- src/systems/codegen_utils.jl | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/systems/codegen_utils.jl b/src/systems/codegen_utils.jl index 9ee8a7fd81..8c2c322e31 100644 --- a/src/systems/codegen_utils.jl +++ b/src/systems/codegen_utils.jl @@ -115,6 +115,9 @@ variable. """ function isdelay(var, iv) iv === nothing && return false + if iscall(var) && ModelingToolkit.isoperator(var, Differential) + return isdelay(arguments(var)[1], iv) + end isvariable(var) || return false isparameter(var) && return false if iscall(var) && !ModelingToolkit.isoperator(var, Symbolics.Operator) From c1c87463b39814f5d6e3b25b8149c4e40f294a60 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Thu, 15 May 2025 14:10:04 +0530 Subject: [PATCH 205/235] fix: use 2-argument `consolidate` in dynamic optimization tests --- test/extensions/dynamic_optimization.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/extensions/dynamic_optimization.jl b/test/extensions/dynamic_optimization.jl index 1074cae620..0d4e9d94e2 100644 --- a/test/extensions/dynamic_optimization.jl +++ b/test/extensions/dynamic_optimization.jl @@ -259,7 +259,7 @@ end eqs = [D(x(t)) ~ -2 + 0.5 * u(t)] # Integral cost function costs = [-Symbolics.Integral(t in (0, tf))(x(t) - u(t)), -x(tf)] - consolidate(u) = u[1] + u[2] + consolidate(u, sub) = u[1] + u[2] + sum(sub) @named rocket = System(eqs, t; costs, consolidate) rocket = structural_simplify(rocket; inputs = [u(t)]) From 995b8aebd7f987a1ce620afdf2751968d70c64c5 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Fri, 16 May 2025 12:03:06 +0530 Subject: [PATCH 206/235] build: bump CasADi compat --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index ca03a35ba0..41568022ab 100644 --- a/Project.toml +++ b/Project.toml @@ -89,7 +89,7 @@ BifurcationKit = "0.4" BlockArrays = "1.1" BoundaryValueDiffEqAscher = "1.6.0" BoundaryValueDiffEqMIRK = "1.7.0" -CasADi = "1.0.6" +CasADi = "1.0.7" ChainRulesCore = "1" Combinatorics = "1" CommonSolve = "0.2.4" From 229e8c265973ad595682fe1af3d9207447d1df05 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Tue, 20 May 2025 11:33:27 +0530 Subject: [PATCH 207/235] fix: fix homotopy continuation --- src/systems/nonlinear/homotopy_continuation.jl | 1 - 1 file changed, 1 deletion(-) diff --git a/src/systems/nonlinear/homotopy_continuation.jl b/src/systems/nonlinear/homotopy_continuation.jl index e68737c19b..103780cb21 100644 --- a/src/systems/nonlinear/homotopy_continuation.jl +++ b/src/systems/nonlinear/homotopy_continuation.jl @@ -394,7 +394,6 @@ function transform_system(sys::System, transformation::PolynomialTransformation; @set! sys2.unknowns = new_dvs # remove observed equations to avoid adding them in codegen @set! sys2.observed = Equation[] - @set! sys2.substitutions = nothing return PolynomialTransformationResult(sys2, denoms) end From 71dd1ca570d0f73db1c6cb467132e04e46b8d0be Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Tue, 20 May 2025 11:33:44 +0530 Subject: [PATCH 208/235] fix: fix AD on `default_consolidate` --- src/systems/system.jl | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/systems/system.jl b/src/systems/system.jl index a638debfb4..940ca39350 100644 --- a/src/systems/system.jl +++ b/src/systems/system.jl @@ -107,7 +107,9 @@ struct System <: AbstractSystem end function default_consolidate(costs, subcosts) - return sum(costs; init = 0.0) + sum(subcosts; init = 0.0) + # `reduce` instead of `sum` because the rrule for `sum` doesn't + # handle the `init` kwarg. + return reduce(+, costs; init = 0.0) + reduce(+, subcosts; init = 0.0) end function System(eqs::Vector{Equation}, iv, dvs, ps, brownians = []; From 545616f1eb1a4f089dcca13b3e0f8641272804fe Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Tue, 20 May 2025 11:33:52 +0530 Subject: [PATCH 209/235] test: fix labelledarrays test --- test/labelledarrays.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/labelledarrays.jl b/test/labelledarrays.jl index 59c269dff7..96c9e1c32b 100644 --- a/test/labelledarrays.jl +++ b/test/labelledarrays.jl @@ -14,7 +14,7 @@ eqs = [D(x) ~ σ * (y - x), @named de = System(eqs, t) de = complete(de) -ff = ODEFunction(de, [x, y, z], [σ, ρ, β], jac = true) +ff = ODEFunction(de; jac = true) a = @SVector [1.0, 2.0, 3.0] b = SLVector(x = 1.0, y = 2.0, z = 3.0) From a9e5df57ccd1f009c32222b44b1d950735560c17 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Tue, 20 May 2025 13:04:10 +0530 Subject: [PATCH 210/235] fix: fix `define_params` in `LabelledArraysExt` --- ext/MTKLabelledArraysExt.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/MTKLabelledArraysExt.jl b/ext/MTKLabelledArraysExt.jl index dda04d07da..c10400b109 100644 --- a/ext/MTKLabelledArraysExt.jl +++ b/ext/MTKLabelledArraysExt.jl @@ -6,7 +6,7 @@ function ModelingToolkit.define_vars(u::Union{SLArray, LArray}, t) [ModelingToolkit._defvar(x)(t) for x in LabelledArrays.symnames(typeof(u))] end -function ModelingToolkit.define_params(p::Union{SLArray, LArray}, names = nothing) +function ModelingToolkit.define_params(p::Union{SLArray, LArray}, t, names = nothing) if names === nothing [toparam(variable(x)) for x in LabelledArrays.symnames(typeof(p))] else From 130f98f144779a5f491ad21d84dd461364e8b4a7 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Tue, 20 May 2025 14:24:01 +0530 Subject: [PATCH 211/235] fix: fix BifurcationKitExt --- ext/MTKBifurcationKitExt.jl | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/ext/MTKBifurcationKitExt.jl b/ext/MTKBifurcationKitExt.jl index bcb3152702..870b523985 100644 --- a/ext/MTKBifurcationKitExt.jl +++ b/ext/MTKBifurcationKitExt.jl @@ -5,6 +5,7 @@ module MTKBifurcationKitExt # Imports using ModelingToolkit, Setfield import BifurcationKit +using SymbolicIndexingInterface: is_time_dependent ### Observable Plotting Handling ### @@ -94,6 +95,14 @@ function BifurcationKit.BifurcationProblem(nsys::System, if !ModelingToolkit.iscomplete(nsys) error("A completed `System` is required. Call `complete` or `structural_simplify` on the system before creating a `BifurcationProblem`") end + if is_time_dependent(nsys) + nsys = System([0 ~ eq.rhs for eq in full_equations(nsys)], + unknowns(nsys), + parameters(nsys); + observed = observed(nsys), + name = nameof(nsys)) + nsys = complete(nsys) + end @set! nsys.index_cache = nothing # force usage of a parameter vector instead of `MTKParameters` # Creates F and J functions. ofun = NonlinearFunction(nsys; jac = jac) @@ -143,17 +152,4 @@ function BifurcationKit.BifurcationProblem(nsys::System, kwargs...) end -# When input is a ODESystem. -function BifurcationKit.BifurcationProblem(osys::System, args...; kwargs...) - if !ModelingToolkit.iscomplete(osys) - error("A completed `ODESystem` is required. Call `complete` or `structural_simplify` on the system before creating a `BifurcationProblem`") - end - nsys = System([0 ~ eq.rhs for eq in full_equations(osys)], - unknowns(osys), - parameters(osys); - observed = observed(osys), - name = nameof(osys)) - return BifurcationKit.BifurcationProblem(complete(nsys), args...; kwargs...) -end - end # module From 98de7ec7d996db7a4291e43f5df671bf7f1272f3 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Tue, 20 May 2025 16:21:24 +0530 Subject: [PATCH 212/235] refactor: remove `ode_order_lowering` and `dae_order_lowering` --- src/ModelingToolkit.jl | 6 +- src/systems/diffeqs/first_order_transform.jl | 106 ----------------- test/lowering_solving.jl | 76 ------------ test/odesystem.jl | 110 ------------------ test/runtests.jl | 1 - test/state_selection.jl | 1 - .../index_reduction.jl | 90 +------------- 7 files changed, 5 insertions(+), 385 deletions(-) delete mode 100644 src/systems/diffeqs/first_order_transform.jl delete mode 100644 test/lowering_solving.jl diff --git a/src/ModelingToolkit.jl b/src/ModelingToolkit.jl index 37b4629992..bf6dfc3af2 100644 --- a/src/ModelingToolkit.jl +++ b/src/ModelingToolkit.jl @@ -190,7 +190,6 @@ include("modelingtoolkitize/nonlinearproblem.jl") include("systems/nonlinear/homotopy_continuation.jl") include("systems/nonlinear/initializesystem.jl") -include("systems/diffeqs/first_order_transform.jl") include("systems/diffeqs/basic_transformations.jl") include("systems/pde/pdesystem.jl") @@ -295,9 +294,8 @@ export isinput, isoutput, getbounds, hasbounds, getguess, hasguess, isdisturbanc tunable_parameters, isirreducible, getdescription, hasdescription, hasunit, getunit, hasconnect, getconnect, hasmisc, getmisc, state_priority -export ode_order_lowering, dae_order_lowering, liouville_transform, - change_independent_variable, substitute_component, add_accumulations, - noise_to_brownians +export liouville_transform, change_independent_variable, substitute_component, + add_accumulations, noise_to_brownians export PDESystem export Differential, expand_derivatives, @derivatives export Equation, ConstrainedEquation diff --git a/src/systems/diffeqs/first_order_transform.jl b/src/systems/diffeqs/first_order_transform.jl deleted file mode 100644 index d017ea362b..0000000000 --- a/src/systems/diffeqs/first_order_transform.jl +++ /dev/null @@ -1,106 +0,0 @@ -""" -$(TYPEDSIGNATURES) - -Takes a Nth order System and returns a new System written in first order -form by defining new variables which represent the N-1 derivatives. -""" -function ode_order_lowering(sys::System) - iv = get_iv(sys) - eqs_lowered, new_vars = ode_order_lowering(equations(sys), iv, unknowns(sys)) - @set! sys.eqs = eqs_lowered - @set! sys.unknowns = new_vars - return sys -end - -function dae_order_lowering(sys::System) - iv = get_iv(sys) - eqs_lowered, new_vars = dae_order_lowering(equations(sys), iv, unknowns(sys)) - @set! sys.eqs = eqs_lowered - @set! sys.unknowns = new_vars - return sys -end - -function ode_order_lowering(eqs, iv, unknown_vars) - var_order = OrderedDict{Any, Int}() - D = Differential(iv) - diff_eqs = Equation[] - diff_vars = [] - alge_eqs = Equation[] - - for (i, eq) in enumerate(eqs) - if !isdiffeq(eq) - push!(alge_eqs, eq) - else - var, maxorder = var_from_nested_derivative(eq.lhs) - maxorder > get(var_order, var, 1) && (var_order[var] = maxorder) - var′ = lower_varname(var, iv, maxorder - 1) - rhs′ = diff2term_with_unit(eq.rhs, iv) - push!(diff_vars, var′) - push!(diff_eqs, D(var′) ~ rhs′) - end - end - - for (var, order) in var_order - for o in (order - 1):-1:1 - lvar = lower_varname(var, iv, o - 1) - rvar = lower_varname(var, iv, o) - push!(diff_vars, lvar) - - rhs = rvar - eq = Differential(iv)(lvar) ~ rhs - push!(diff_eqs, eq) - end - end - - # we want to order the equations and variables to be `(diff, alge)` - return (vcat(diff_eqs, alge_eqs), vcat(diff_vars, setdiff(unknown_vars, diff_vars))) -end - -function dae_order_lowering(eqs, iv, unknown_vars) - var_order = OrderedDict{Any, Int}() - D = Differential(iv) - diff_eqs = Equation[] - diff_vars = OrderedSet() - alge_eqs = Equation[] - vars = Set() - subs = Dict() - - for (i, eq) in enumerate(eqs) - vars!(vars, eq) - n_diffvars = 0 - for vv in vars - isdifferential(vv) || continue - var, maxorder = var_from_nested_derivative(vv) - isparameter(var) && continue - n_diffvars += 1 - order = get(var_order, var, nothing) - seen = order !== nothing - if !seen - order = 1 - end - maxorder > order && (var_order[var] = maxorder) - var′ = lower_varname(var, iv, maxorder - 1) - subs[vv] = D(var′) - if !seen - push!(diff_vars, var′) - end - end - n_diffvars == 0 && push!(alge_eqs, eq) - empty!(vars) - end - - for (var, order) in var_order - for o in (order - 1):-1:1 - lvar = lower_varname(var, iv, o - 1) - rvar = lower_varname(var, iv, o) - push!(diff_vars, lvar) - - rhs = rvar - eq = Differential(iv)(lvar) ~ rhs - push!(diff_eqs, eq) - end - end - - return ([diff_eqs; substitute.(eqs, (subs,))], - vcat(collect(diff_vars), setdiff(unknown_vars, diff_vars))) -end diff --git a/test/lowering_solving.jl b/test/lowering_solving.jl deleted file mode 100644 index 280848f6ad..0000000000 --- a/test/lowering_solving.jl +++ /dev/null @@ -1,76 +0,0 @@ -using ModelingToolkit, OrdinaryDiffEq, Test, LinearAlgebra -using ModelingToolkit: t_nounits as t, D_nounits as D - -@parameters σ ρ β -@variables x(t) y(t) z(t) k(t) - -eqs = [D(D(x)) ~ σ * (y - x), - D(y) ~ x * (ρ - z) - y, - D(z) ~ x * y - β * z] - -@named sys′ = System(eqs, t) -sys = ode_order_lowering(sys′) - -eqs2 = [0 ~ x * y - k, - D(D(x)) ~ σ * (y - x), - D(y) ~ x * (ρ - z) - y, - D(z) ~ x * y - β * z] -@named sys2 = System(eqs2, t, [x, y, z, k], parameters(sys′)) -sys2 = ode_order_lowering(sys2) -# test equation/variable ordering -ModelingToolkit.calculate_massmatrix(sys2) == Diagonal([1, 1, 1, 1, 0]) - -u0 = [D(x) => 2.0, - x => 1.0, - y => 0.0, - z => 0.0] - -p = [σ => 28.0, - ρ => 10.0, - β => 8 / 3] - -tspan = (0.0, 100.0) - -sys = complete(sys) -prob = ODEProblem(sys, u0, tspan, p, jac = true) -probexpr = ODEProblem(sys, u0, tspan, p; jac = true, expression = Val{true}) -sol = solve(prob, Tsit5()) -solexpr = solve(eval(prob), Tsit5()) -@test all(x -> x == 0, Array(sol - solexpr)) -#using Plots; plot(sol,idxs=(:x,:y)) - -@parameters σ ρ β -@variables x(t) y(t) z(t) - -eqs = [D(x) ~ σ * (y - x), - D(y) ~ x * (ρ - z) - y, - D(z) ~ x * y - β * z] - -lorenz1 = System(eqs, t, name = :lorenz1) -lorenz2 = System(eqs, t, name = :lorenz2) - -@variables α(t) -@parameters γ -connections = [0 ~ lorenz1.x + lorenz2.y + α * γ] -@named connected = System(connections, t, [α], [γ], systems = [lorenz1, lorenz2]) -connected = complete(connected) -u0 = [lorenz1.x => 1.0, - lorenz1.y => 0.0, - lorenz1.z => 0.0, - lorenz2.x => 0.0, - lorenz2.y => 1.0, - lorenz2.z => 0.0] - -p = [lorenz1.σ => 10.0, - lorenz1.ρ => 28.0, - lorenz1.β => 8 / 3, - lorenz2.σ => 10.0, - lorenz2.ρ => 28.0, - lorenz2.β => 8 / 3, - γ => 2.0] - -tspan = (0.0, 100.0) -prob = ODEProblem(connected, u0, tspan, p) -sol = solve(prob, Rodas5()) -@test maximum(sol[lorenz1.x] + sol[lorenz2.y] + 2sol[α]) < 1e-12 -#using Plots; plot(sol,idxs=(:α,Symbol(lorenz1.x),Symbol(lorenz2.y))) diff --git a/test/odesystem.jl b/test/odesystem.jl index 8b24691fea..904ca58d1a 100644 --- a/test/odesystem.jl +++ b/test/odesystem.jl @@ -153,32 +153,6 @@ du = [0.0] f(du, [1.0], [t -> t + 2], 5.0) @test du ≈ [27561] -@testset "Issue#17: Conversion to first order ODEs" begin - D3 = D^3 - D2 = D^2 - @variables u(t) uˍtt(t) uˍt(t) xˍt(t) - eqs = [D3(u) ~ 2(D2(u)) + D(u) + D(x) + 1 - D2(x) ~ D(x) + 2] - @named de = System(eqs, t) - de1 = ode_order_lowering(de) - - @testset "Issue#219: Ordering of equations in `ode_order_lowering`" begin - lowered_eqs = [D(uˍtt) ~ 2uˍtt + uˍt + xˍt + 1 - D(xˍt) ~ xˍt + 2 - D(uˍt) ~ uˍtt - D(u) ~ uˍt - D(x) ~ xˍt] - @test isequal( - [ModelingToolkit.var_from_nested_derivative(eq.lhs)[1] for eq in equations(de1)], - unknowns(@named lowered = System(lowered_eqs, t))) - end - - test_diffeq_inference("first-order transform", de1, t, [uˍtt, xˍt, uˍt, u, x], []) - du = zeros(5) - ODEFunction(complete(de1))(du, ones(5), nothing, 0.1) - @test du == [5.0, 3.0, 1.0, 1.0, 1.0] -end - # Internal calculations @parameters σ a = y - x @@ -348,16 +322,6 @@ eqs = [D(x) ~ σ * (y - x), @test issym(equations(sys)[1].rhs) end -@testset "Issue#708" begin - @parameters a - @variables x(t) y(t) z(t) - @named sys = System([D(x) ~ y, 0 ~ x + z, 0 ~ x - y], t, [z, y, x], []) - - sys2 = ode_order_lowering(sys) - M = ModelingToolkit.calculate_massmatrix(sys2) - @test M == Diagonal([1, 0, 0]) -end - # issue #609 @variables x1(t) x2(t) @@ -416,22 +380,6 @@ eqs = [ ] @test_throws ArgumentError ModelingToolkit.System(eqs, t, vars, pars, name = :foo) -@variables x(t) -@parameters M b k -eqs = [D(D(x)) ~ -b / M * D(x) - k / M * x] -ps = [M, b, k] -default_u0 = [D(x) => 0.0, x => 10.0] -default_p = [M => 1.0, b => 1.0, k => 1.0] -@named sys = System(eqs, t, [x], ps; defaults = [default_u0; default_p]) -sys = ode_order_lowering(sys) -sys = complete(sys) -prob = ODEProblem(sys, nothing, tspan) -sol = solve(prob, Tsit5()) -@test sol.t[end] == tspan[end] -@test sum(abs, sol.u[end]) < 1 -prob = ODEProblem{false}(sys, nothing, tspan; u0_constructor = x -> SVector(x...)) -@test prob.u0 isa SVector - # check_eqs_u0 kwarg test @variables x1(t) x2(t) eqs = [D(x1) ~ -x1] @@ -1530,64 +1478,6 @@ end @test osys1 !== osys2 end -@testset "dae_order_lowering basic test" begin - @parameters a - @variables x(t) y(t) z(t) - @named dae_sys = System([ - D(x) ~ y, - 0 ~ x + z, - 0 ~ x - y + z - ], t, [z, y, x], []) - - lowered_dae_sys = dae_order_lowering(dae_sys) - @variables x1(t) y1(t) z1(t) - expected_eqs = [ - 0 ~ x + z, - 0 ~ x - y + z, - Differential(t)(x) ~ y - ] - lowered_eqs = equations(lowered_dae_sys) - sorted_lowered_eqs = sort(lowered_eqs, by = string) - sorted_expected_eqs = sort(expected_eqs, by = string) - @test sorted_lowered_eqs == sorted_expected_eqs - - expected_vars = Set([z, y, x]) - lowered_vars = Set(unknowns(lowered_dae_sys)) - @test lowered_vars == expected_vars -end - -@testset "dae_order_lowering test with structural_simplify" begin - @variables x(t) y(t) z(t) - @parameters M b k - eqs = [ - D(D(x)) ~ -b / M * D(x) - k / M * x, - 0 ~ y - D(x), - 0 ~ z - x - ] - ps = [M, b, k] - default_u0 = [ - D(x) => 0.0, x => 10.0, y => 0.0, z => 10.0 - ] - default_p = [M => 1.0, b => 1.0, k => 1.0] - @named dae_sys = System(eqs, t, [x, y, z], ps; defaults = [default_u0; default_p]) - - simplified_dae_sys = structural_simplify(dae_sys) - - lowered_dae_sys = dae_order_lowering(simplified_dae_sys) - lowered_dae_sys = complete(lowered_dae_sys) - - tspan = (0.0, 10.0) - prob = ODEProblem(lowered_dae_sys, nothing, tspan) - sol = solve(prob, Tsit5()) - - @test sol.t[end] == tspan[end] - @test sum(abs, sol.u[end]) < 1 - - prob = ODEProblem{false}( - lowered_dae_sys, nothing, tspan; u0_constructor = x -> SVector(x...)) - @test prob.u0 isa SVector -end - @testset "Constraint system construction" begin @variables x(..) y(..) z(..) @parameters a b c d e diff --git a/test/runtests.jl b/test/runtests.jl index 86d69228ce..426993c414 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -51,7 +51,6 @@ end @safetestset "Symbolic Event Test" include("symbolic_events.jl") @safetestset "Stream Connect Test" include("stream_connectors.jl") @safetestset "Domain Connect Test" include("domain_connectors.jl") - @safetestset "Lowering Integration Test" include("lowering_solving.jl") @safetestset "Dependency Graph Test" include("dep_graphs.jl") @safetestset "Function Registration Test" include("function_registration.jl") @safetestset "Precompiled Modules Test" include("precompile_test.jl") diff --git a/test/state_selection.jl b/test/state_selection.jl index 6db8e8c5a0..ba74cc04a2 100644 --- a/test/state_selection.jl +++ b/test/state_selection.jl @@ -23,7 +23,6 @@ end @test_skip let pss = partial_state_selection(sys) @test length(equations(pss)) == 1 @test length(unknowns(pss)) == 2 - @test length(equations(ode_order_lowering(pss))) == 2 end @parameters σ ρ β diff --git a/test/structural_transformation/index_reduction.jl b/test/structural_transformation/index_reduction.jl index 26f73db4e7..0703221418 100644 --- a/test/structural_transformation/index_reduction.jl +++ b/test/structural_transformation/index_reduction.jl @@ -3,26 +3,13 @@ using Graphs using DiffEqBase using Test using UnPack +using OrdinaryDiffEq +using LinearAlgebra using ModelingToolkit: t_nounits as t, D_nounits as D # Define some variables @parameters L g -@variables x(t) y(t) w(t) z(t) T(t) xˍt(t) yˍt(t) xˍˍt(t) yˍˍt(t) - -eqs2 = [D(D(x)) ~ T * x, - D(D(y)) ~ T * y - g, - 0 ~ x^2 + y^2 - L^2] -pendulum2 = System(eqs2, t, [x, y, T], [L, g], name = :pendulum) -lowered_sys = ModelingToolkit.ode_order_lowering(pendulum2) - -lowered_eqs = [D(xˍt) ~ T * x, - D(yˍt) ~ T * y - g, - D(x) ~ xˍt, - D(y) ~ yˍt, - 0 ~ x^2 + y^2 - L^2] -@test System(lowered_eqs, t, [xˍt, yˍt, x, y, T], [L, g], name = :pendulum) == - lowered_sys -@test isequal(equations(lowered_sys), lowered_eqs) +@variables x(t) y(t) z(t) w(t) T(t) # Simple pendulum in cartesian coordinates eqs = [D(x) ~ w, @@ -39,82 +26,11 @@ state = TearingState(pendulum) map(x -> x == 0 ? StructuralTransformations.unassigned : x, [3, 4, 2, 5, 0, 0, 0, 0, 0]) -using ModelingToolkit -@parameters L g -@variables x(t) y(t) w(t) z(t) T(t) xˍt(t) yˍt(t) -idx1_pendulum = [D(x) ~ w, - D(y) ~ z, - #0 ~ x^2 + y^2 - L^2, - D(w) ~ T * x, - D(z) ~ T * y - g, - # intermediate 1: 0 ~ 2x*D(x) + 2y*D(y) - 0, - # intermediate 2(a): 0 ~ 2x*w + 2y*z - 0, (substitute D(x) and D(y)) - #0 ~ 2x*w + 2y*z, - # D(D(x)) ~ D(w) and substitute the rhs - D(xˍt) ~ T * x, - # D(D(y)) ~ D(z) and substitute the rhs - D(yˍt) ~ T * y - g, - # 2x*D(D(x)) + 2*D(x)*D(x) + 2y*D(D(y)) + 2*D(y)*D(y) and - # substitute the rhs - 0 ~ 2x * (T * x) + 2 * xˍt * xˍt + 2y * (T * y - g) + 2 * yˍt * yˍt] -@named idx1_pendulum = System(idx1_pendulum, t, [x, y, w, z, xˍt, yˍt, T], [L, g]) -first_order_idx1_pendulum = complete(ode_order_lowering(idx1_pendulum)) - -using OrdinaryDiffEq -using LinearAlgebra -prob = ODEProblem(first_order_idx1_pendulum, - # [x, y, w, z, xˍt, yˍt, T] - [1, 0, 0, 0, 0, 0, 0.0],# 0, 0, 0, 0], - (0, 10.0), - [1, 9.8]) -sol = solve(prob, Rodas5()); -#plot(sol, idxs=(1, 2)) - -new_sys = complete(dae_index_lowering(ModelingToolkit.ode_order_lowering(pendulum2))) - -prob_auto = ODEProblem(new_sys, - [D(x) => 0, - D(y) => 0, - x => 1, - y => 0, - T => 0.0], - (0, 100.0), - [L => 1, g => 9.8]) -sol = solve(prob_auto, Rodas5()); -#plot(sol, idxs=(x, y)) - -# Define some variables -@parameters L g -@variables x(t) y(t) T(t) - eqs2 = [D(D(x)) ~ T * x, D(D(y)) ~ T * y - g, 0 ~ x^2 + y^2 - L^2] pendulum2 = System(eqs2, t, [x, y, T], [L, g], name = :pendulum) -# Turn into a first order differential equation system -first_order_sys = ModelingToolkit.ode_order_lowering(pendulum2) - -# Perform index reduction to get an Index 1 DAE -new_sys = complete(dae_index_lowering(first_order_sys)) - -u0 = [ - D(x) => 0.0, - D(y) => 0.0, - x => 1.0, - y => 0.0, - T => 0.0 -] - -p = [ - L => 1.0, - g => 9.8 -] - -prob_auto = ODEProblem(new_sys, u0, (0.0, 10.0), p) -sol = solve(prob_auto, Rodas5()); -#plot(sol, idxs=(D(x), y)) - @test_skip begin let pss_pendulum2 = partial_state_selection(pendulum2) length(equations(pss_pendulum2)) <= 6 From abc6abe20fd5d64c2b12a04c1370583c1d902b1c Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Mon, 19 May 2025 23:07:50 +0530 Subject: [PATCH 213/235] refactor: remove code related to parsing and substitution of constants --- src/inputoutput.jl | 3 +- src/problems/optimizationproblem.jl | 4 +- .../StructuralTransformations.jl | 2 +- src/structural_transformation/codegen.jl | 265 +----------------- src/structural_transformation/utils.jl | 4 +- src/systems/abstractsystem.jl | 12 - src/systems/callbacks.jl | 5 - src/systems/codegen_utils.jl | 12 +- src/systems/nonlinear/initializesystem.jl | 3 +- src/systems/parameter_buffer.jl | 3 +- src/systems/problem_utils.jl | 14 +- src/systems/systemstructure.jl | 6 +- src/utils.jl | 117 +------- 13 files changed, 19 insertions(+), 431 deletions(-) diff --git a/src/inputoutput.jl b/src/inputoutput.jl index 1beb229664..19603b76cd 100644 --- a/src/inputoutput.jl +++ b/src/inputoutput.jl @@ -222,7 +222,7 @@ function generate_control_function(sys::AbstractSystem, inputs = unbound_inputs( disturbance_inputs = unwrap.(disturbance_inputs) eqs = [eq for eq in full_equations(sys)] - eqs = map(subs_constants, eqs) + if disturbance_inputs !== nothing && !disturbance_argument # Set all disturbance *inputs* to zero (we just want to keep the disturbance state) subs = Dict(disturbance_inputs .=> 0) @@ -237,7 +237,6 @@ function generate_control_function(sys::AbstractSystem, inputs = unbound_inputs( p = reorder_parameters(sys, ps) t = get_iv(sys) - # pre = has_difference ? (ex -> ex) : get_postprocess_fbody(sys) if disturbance_argument args = (dvs, inputs, p..., t, disturbance_inputs) else diff --git a/src/problems/optimizationproblem.jl b/src/problems/optimizationproblem.jl index 243d453ada..e0de2f78ff 100644 --- a/src/problems/optimizationproblem.jl +++ b/src/problems/optimizationproblem.jl @@ -56,10 +56,10 @@ function SciMLBase.OptimizationFunction{iip}(sys::System; else _cons_h = cons_hess_prototype = nothing end - cons_expr = subs_constants(cstr) + cons_expr = cstr end - obj_expr = subs_constants(cost(sys)) + obj_expr = cost(sys) observedfun = ObservedFunctionCache( sys; expression, eval_expression, eval_module, checkbounds, cse) diff --git a/src/structural_transformation/StructuralTransformations.jl b/src/structural_transformation/StructuralTransformations.jl index 06d8e440cc..2ba469e26a 100644 --- a/src/structural_transformation/StructuralTransformations.jl +++ b/src/structural_transformation/StructuralTransformations.jl @@ -21,7 +21,7 @@ using ModelingToolkit: System, AbstractSystem, var_from_nested_derivative, Diffe has_tearing_state, defaults, InvalidSystemException, ExtraEquationsSystemException, ExtraVariablesSystemException, - get_postprocess_fbody, vars!, + vars!, IncrementalCycleTracker, add_edge_checked!, topological_sort, invalidate_cache!, Substitutions, get_or_construct_tearing_state, filter_kwargs, lower_varname_with_unit, diff --git a/src/structural_transformation/codegen.jl b/src/structural_transformation/codegen.jl index 144e19aa31..9afe7ec5e7 100644 --- a/src/structural_transformation/codegen.jl +++ b/src/structural_transformation/codegen.jl @@ -1,6 +1,6 @@ using LinearAlgebra -using ModelingToolkit: process_events, get_preprocess_constants +using ModelingToolkit: process_events const MAX_INLINE_NLSOLVE_SIZE = 8 @@ -96,136 +96,6 @@ function torn_system_with_nlsolve_jacobian_sparsity(state, var_eq_matching, var_ sparse(I, J, true, length(eqs_idxs), length(states_idxs)) end -function gen_nlsolve!(is_not_prepended_assignment, eqs, vars, u0map::AbstractDict, - assignments, (deps, invdeps), var2assignment; checkbounds = true) - isempty(vars) && throw(ArgumentError("vars may not be empty")) - length(eqs) == length(vars) || - throw(ArgumentError("vars must be of the same length as the number of equations to find the roots of")) - rhss = map(x -> x.rhs, eqs) - # We use `vars` instead of `graph` to capture parameters, too. - paramset = ModelingToolkit.vars(r for r in rhss) - - # Compute necessary assignments for the nlsolve expr - init_assignments = [var2assignment[p] for p in paramset if haskey(var2assignment, p)] - if isempty(init_assignments) - needed_assignments_idxs = Int[] - needed_assignments = similar(assignments, 0) - else - tmp = [init_assignments] - # `deps[init_assignments]` gives the dependency of `init_assignments` - while true - next_assignments = unique(reduce(vcat, deps[init_assignments])) - isempty(next_assignments) && break - init_assignments = next_assignments - push!(tmp, init_assignments) - end - needed_assignments_idxs = unique(reduce(vcat, reverse(tmp))) - needed_assignments = assignments[needed_assignments_idxs] - end - - # Compute `params`. They are like enclosed variables - rhsvars = [ModelingToolkit.vars(r.rhs) for r in needed_assignments] - vars_set = Set(vars) - outer_set = BitSet() - inner_set = BitSet() - for (i, vs) in enumerate(rhsvars) - j = needed_assignments_idxs[i] - if isdisjoint(vars_set, vs) - push!(outer_set, j) - else - push!(inner_set, j) - end - end - init_refine = BitSet() - for i in inner_set - union!(init_refine, invdeps[i]) - end - intersect!(init_refine, outer_set) - setdiff!(outer_set, init_refine) - union!(inner_set, init_refine) - - next_refine = BitSet() - while true - for i in init_refine - id = invdeps[i] - isempty(id) && break - union!(next_refine, id) - end - intersect!(next_refine, outer_set) - isempty(next_refine) && break - setdiff!(outer_set, next_refine) - union!(inner_set, next_refine) - - init_refine, next_refine = next_refine, init_refine - empty!(next_refine) - end - global2local = Dict(j => i for (i, j) in enumerate(needed_assignments_idxs)) - inner_idxs = [global2local[i] for i in collect(inner_set)] - outer_idxs = [global2local[i] for i in collect(outer_set)] - extravars = reduce(union!, rhsvars[inner_idxs], init = Set()) - union!(paramset, extravars) - setdiff!(paramset, vars) - setdiff!(paramset, [needed_assignments[i].lhs for i in inner_idxs]) - union!(paramset, [needed_assignments[i].lhs for i in outer_idxs]) - params = collect(paramset) - - # splatting to tighten the type - u0 = [] - for v in vars - v in keys(u0map) || (push!(u0, 1e-3); continue) - u = substitute(v, u0map) - for i in 1:length(u0map) - u = substitute(u, u0map) - u isa Number && (push!(u0, u); break) - end - u isa Number || error("$v doesn't have a default.") - end - u0 = [u0...] - # specialize on the scalar case - isscalar = length(u0) == 1 - u0 = isscalar ? u0[1] : SVector(u0...) - - fname = gensym("fun") - # f is the function to find roots on - if isscalar - funex = rhss[1] - pre = get_preprocess_constants(funex) - else - funex = MakeArray(rhss, SVector) - pre = get_preprocess_constants(rhss) - end - f = Func( - [DestructuredArgs(vars, inbounds = !checkbounds) - DestructuredArgs(params, inbounds = !checkbounds)], - [], - pre(Let(needed_assignments[inner_idxs], - funex, - false))) |> SymbolicUtils.Code.toexpr - - # solver call contains code to call the root-finding solver on the function f - solver_call = LiteralExpr(quote - $numerical_nlsolve($fname, - # initial guess - $u0, - # "captured variables" - ($(params...),)) - end) - - preassignments = [] - for i in outer_idxs - ii = needed_assignments_idxs[i] - is_not_prepended_assignment[ii] || continue - is_not_prepended_assignment[ii] = false - push!(preassignments, assignments[ii]) - end - - nlsolve_expr = Assignment[preassignments - fname ← drop_expr(@RuntimeGeneratedFunction(f)) - DestructuredArgs(vars, inbounds = !checkbounds) ← solver_call] - - nlsolve_expr -end - """ find_solve_sequence(sccs, vars) @@ -242,136 +112,3 @@ function find_solve_sequence(sccs, vars) return find_solve_sequence(sccs, vars′) end end - -function build_observed_function(state, ts, var_eq_matching, var_sccs, - is_solver_unknown_idxs, - assignments, - deps, - sol_states, - var2assignment; - expression = false, - output_type = Array, - checkbounds = true) - is_not_prepended_assignment = trues(length(assignments)) - if (isscalar = !(ts isa AbstractVector)) - ts = [ts] - end - ts = unwrap.(Symbolics.scalarize(ts)) - - vars = Set() - sys = state.sys - foreach(Base.Fix1(vars!, vars), ts) - ivs = independent_variables(sys) - dep_vars = collect(setdiff(vars, ivs)) - - fullvars = state.fullvars - s = state.structure - unknown_vars = fullvars[is_solver_unknown_idxs] - algvars = fullvars[.!is_solver_unknown_idxs] - - required_algvars = Set(intersect(algvars, vars)) - obs = observed(sys) - observed_idx = Dict(x.lhs => i for (i, x) in enumerate(obs)) - namespaced_to_obs = Dict(unknowns(sys, x.lhs) => x.lhs for x in obs) - namespaced_to_sts = Dict(unknowns(sys, x) => x for x in unknowns(sys)) - sts = Set(unknowns(sys)) - - # FIXME: This is a rather rough estimate of dependencies. We assume - # the expression depends on everything before the `maxidx`. - subs = Dict() - maxidx = 0 - for (i, s) in enumerate(dep_vars) - idx = get(observed_idx, s, nothing) - if idx !== nothing - idx > maxidx && (maxidx = idx) - else - s′ = get(namespaced_to_obs, s, nothing) - if s′ !== nothing - subs[s] = s′ - s = s′ - idx = get(observed_idx, s, nothing) - end - if idx !== nothing - idx > maxidx && (maxidx = idx) - elseif !(s in sts) - s′ = get(namespaced_to_sts, s, nothing) - if s′ !== nothing - subs[s] = s′ - continue - end - throw(ArgumentError("$s is either an observed nor an unknown variable.")) - end - continue - end - end - ts = map(t -> substitute(t, subs), ts) - vs = Set() - for idx in 1:maxidx - vars!(vs, obs[idx].rhs) - union!(required_algvars, intersect(algvars, vs)) - empty!(vs) - end - for eq in assignments - vars!(vs, eq.rhs) - union!(required_algvars, intersect(algvars, vs)) - empty!(vs) - end - - varidxs = findall(x -> x in required_algvars, fullvars) - subset = find_solve_sequence(var_sccs, varidxs) - if !isempty(subset) - eqs = equations(sys) - - nested_torn_vars_idxs = [] - for iscc in subset - torn_vars_idxs = Int[var - for var in var_sccs[iscc] - if var_eq_matching[var] !== unassigned] - isempty(torn_vars_idxs) || push!(nested_torn_vars_idxs, torn_vars_idxs) - end - torn_eqs = [[eqs[var_eq_matching[i]] for i in idxs] - for idxs in nested_torn_vars_idxs] - torn_vars = [fullvars[idxs] for idxs in nested_torn_vars_idxs] - u0map = defaults(sys) - assignments = copy(assignments) - solves = map(zip(torn_eqs, torn_vars)) do (eqs, vars) - gen_nlsolve!(is_not_prepended_assignment, eqs, vars, - u0map, assignments, deps, var2assignment; - checkbounds = checkbounds) - end - else - solves = [] - end - - subs = [] - for sym in vars - eqidx = get(observed_idx, sym, nothing) - eqidx === nothing && continue - push!(subs, sym ← obs[eqidx].rhs) - end - pre = get_postprocess_fbody(sys) - cpre = get_preprocess_constants([obs[1:maxidx]; - isscalar ? ts[1] : MakeArray(ts, output_type)]) - pre2 = x -> pre(cpre(x)) - ex = Code.toexpr( - Func( - [DestructuredArgs(unknown_vars, inbounds = !checkbounds) - DestructuredArgs(parameters(sys), inbounds = !checkbounds) - independent_variables(sys)], - [], - pre2(Let( - [collect(Iterators.flatten(solves)) - assignments[is_not_prepended_assignment] - map(eq -> eq.lhs ← eq.rhs, obs[1:maxidx]) - subs], - isscalar ? ts[1] : MakeArray(ts, output_type), - false))), - sol_states) - - expression ? ex : drop_expr(@RuntimeGeneratedFunction(ex)) -end - -struct ODAEProblem{iip} end - -@deprecate ODAEProblem(args...; kw...) ODEProblem(args...; kw...) -@deprecate ODAEProblem{iip}(args...; kw...) where {iip} ODEProblem{iip}(args...; kw...) diff --git a/src/structural_transformation/utils.jl b/src/structural_transformation/utils.jl index 191c25ab68..2bf316cfa6 100644 --- a/src/structural_transformation/utils.jl +++ b/src/structural_transformation/utils.jl @@ -224,14 +224,12 @@ function find_eq_solvables!(state::TearingState, ieq, to_rm = Int[], coeffs = no a, b, islinear = linear_expansion(term, var) a, b = unwrap(a), unwrap(b) islinear || (all_int_vars = false; continue) - a = ModelingToolkit.fold_constants(a) - b = ModelingToolkit.fold_constants(b) if a isa Symbolic all_int_vars = false if !allow_symbolic if allow_parameter all( - x -> ModelingToolkit.isparameter(x) || ModelingToolkit.isconstant(x), + x -> ModelingToolkit.isparameter(x), vars(a)) || continue else continue diff --git a/src/systems/abstractsystem.jl b/src/systems/abstractsystem.jl index 4021f1fc44..1a90faad92 100644 --- a/src/systems/abstractsystem.jl +++ b/src/systems/abstractsystem.jl @@ -2540,18 +2540,6 @@ function debug_system( return sys end -function eliminate_constants(sys::AbstractSystem) - if has_eqs(sys) - eqs = get_eqs(sys) - eq_cs = collect_constants(eqs) - if !isempty(eq_cs) - new_eqs = eliminate_constants(eqs, eq_cs) - @set! sys.eqs = new_eqs - end - end - return sys -end - @latexrecipe function f(sys::AbstractSystem) return latexify(equations(sys)) end diff --git a/src/systems/callbacks.jl b/src/systems/callbacks.jl index 0bb2318e4b..59ff0e0d98 100644 --- a/src/systems/callbacks.jl +++ b/src/systems/callbacks.jl @@ -635,11 +635,6 @@ function compile_condition( p = map.(value, reorder_parameters(sys, ps)) t = get_iv(sys) condit = conditions(cbs) - cs = collect_constants(condit) - if !isempty(cs) - cmap = map(x -> x => getdefault(x), cs) - condit = substitute(condit, Dict(cmap)) - end if !is_discrete(cbs) condit = reduce(vcat, flatten_equations(Vector{Equation}(condit))) diff --git a/src/systems/codegen_utils.jl b/src/systems/codegen_utils.jl index 8c2c322e31..c3b652740e 100644 --- a/src/systems/codegen_utils.jl +++ b/src/systems/codegen_utils.jl @@ -247,15 +247,7 @@ function build_function_wrapper(sys::AbstractSystem, expr, args...; p_start = 2, p_end += 1 end pdeps = parameter_dependencies(sys) - # get the constants to add to the code - cmap, _ = get_cmap(sys) - extra_constants = collect_constants(expr) - filter!(extra_constants) do c - !any(x -> isequal(c, x.lhs), cmap) - end - for c in extra_constants - push!(cmap, c ~ getdefault(c)) - end + # only get the necessary observed equations, avoiding extra computation if add_observed && !isempty(obs) obsidxs = observed_equations_used_by(sys, expr; obs) @@ -270,7 +262,7 @@ function build_function_wrapper(sys::AbstractSystem, expr, args...; p_start = 2, # assignments for reconstructing scalarized array symbolics assignments = array_variable_assignments(args...) - for eq in Iterators.flatten((cmap, pdeps[pdepidxs], obs[obsidxs])) + for eq in Iterators.flatten((pdeps[pdepidxs], obs[obsidxs])) push!(assignments, eq.lhs ← eq.rhs) end append!(assignments, extra_assignments) diff --git a/src/systems/nonlinear/initializesystem.jl b/src/systems/nonlinear/initializesystem.jl index 646ce81112..a550f9d740 100644 --- a/src/systems/nonlinear/initializesystem.jl +++ b/src/systems/nonlinear/initializesystem.jl @@ -533,7 +533,6 @@ function SciMLBase.remake_initialization_data( symbols_to_symbolics!(sys, pmap) guesses = Dict() defs = defaults(sys) - cmap, cs = get_cmap(sys) use_scc = true initialization_eqs = Equation[] @@ -588,7 +587,7 @@ function SciMLBase.remake_initialization_data( filter_missing_values!(pmap) op, missing_unknowns, missing_pars = build_operating_point!(sys, - u0map, pmap, defs, cmap, dvs, ps) + u0map, pmap, defs, dvs, ps) floatT = float_type_from_varmap(op) u0_constructor = p_constructor = identity if newu0 isa StaticArray diff --git a/src/systems/parameter_buffer.jl b/src/systems/parameter_buffer.jl index c3d2a0e831..83d165eefc 100644 --- a/src/systems/parameter_buffer.jl +++ b/src/systems/parameter_buffer.jl @@ -45,13 +45,12 @@ function MTKParameters( p = to_varmap(p, ps) symbols_to_symbolics!(sys, p) defs = add_toterms(recursive_unwrap(defaults(sys))) - cmap, cs = get_cmap(sys) is_time_dependent(sys) && add_observed!(sys, u0) add_parameter_dependencies!(sys, p) op, missing_unknowns, missing_pars = build_operating_point!(sys, - u0, p, defs, cmap, dvs, ps) + u0, p, defs, dvs, ps) if t0 !== nothing op[get_iv(sys)] = t0 diff --git a/src/systems/problem_utils.jl b/src/systems/problem_utils.jl index 8562bb1cfa..8cff86f9d3 100644 --- a/src/systems/problem_utils.jl +++ b/src/systems/problem_utils.jl @@ -532,15 +532,15 @@ end $(TYPEDSIGNATURES) Construct the operating point of the system from the user-provided `u0map` and `pmap`, system -defaults `defs`, constant equations `cmap` (from `get_cmap(sys)`), unknowns `dvs` and -parameters `ps`. Return the operating point as a dictionary, the list of unknowns for which -no values can be determined, and the list of parameters for which no values can be determined. +defaults `defs`, unknowns `dvs` and parameters `ps`. Return the operating point as a dictionary, +the list of unknowns for which no values can be determined, and the list of parameters for which +no values can be determined. Also updates `u0map` and `pmap` in-place to contain all the initial conditions in `op`, split by unknowns and parameters respectively. """ function build_operating_point!(sys::AbstractSystem, - u0map::AbstractDict, pmap::AbstractDict, defs::AbstractDict, cmap, dvs, ps) + u0map::AbstractDict, pmap::AbstractDict, defs::AbstractDict, dvs, ps) op = add_toterms(u0map) missing_unknowns = add_fallbacks!(op, dvs, defs) for (k, v) in defs @@ -552,9 +552,6 @@ function build_operating_point!(sys::AbstractSystem, merge!(op, pmap) missing_pars = add_fallbacks!(op, ps, defs) filter_missing_values!(op; missing_values = missing_pars) - for eq in cmap - op[eq.lhs] = eq.rhs - end filter!(kvp -> kvp[2] === nothing, u0map) filter!(kvp -> kvp[2] === nothing, pmap) @@ -1254,7 +1251,6 @@ function process_SciMLProblem( check_inputmap_keys(sys, u0map, pmap) defs = add_toterms(recursive_unwrap(defaults(sys))) - cmap, cs = get_cmap(sys) kwargs = NamedTuple(kwargs) if eltype(eqs) <: Equation @@ -1264,7 +1260,7 @@ function process_SciMLProblem( end op, missing_unknowns, missing_pars = build_operating_point!(sys, - u0map, pmap, defs, cmap, dvs, ps) + u0map, pmap, defs, dvs, ps) floatT = Bool if u0Type <: AbstractArray && eltype(u0Type) <: Real && eltype(u0Type) != Union{} diff --git a/src/systems/systemstructure.jl b/src/systems/systemstructure.jl index 144aad148e..c1b2c337ac 100644 --- a/src/systems/systemstructure.jl +++ b/src/systems/systemstructure.jl @@ -5,7 +5,7 @@ using SymbolicUtils: quick_cancel, maketerm using ..ModelingToolkit import ..ModelingToolkit: isdiffeq, var_from_nested_derivative, vars!, flatten, value, InvalidSystemException, isdifferential, _iszero, - isparameter, isconstant, + isparameter, independent_variables, SparseMatrixCLIL, AbstractSystem, equations, isirreducible, input_timedomain, TimeDomain, InferredTimeDomain, @@ -314,7 +314,7 @@ function TearingState(sys; quick_cancel = false, check = true, sort_eqs = true) _var, _ = var_from_nested_derivative(v) any(isequal(_var), ivs) && continue if isparameter(_var) || - (iscall(_var) && isparameter(operation(_var)) || isconstant(_var)) + (iscall(_var) && isparameter(operation(_var))) if is_time_dependent_parameter(_var, iv) && !haskey(param_derivative_map, Differential(iv)(_var)) # Parameter derivatives default to zero - they stay constant @@ -339,7 +339,7 @@ function TearingState(sys; quick_cancel = false, check = true, sort_eqs = true) _var, _ = var_from_nested_derivative(var) any(isequal(_var), ivs) && continue if isparameter(_var) || - (iscall(_var) && isparameter(operation(_var)) || isconstant(_var)) + (iscall(_var) && isparameter(operation(_var))) continue end varidx = addvar!(var) diff --git a/src/utils.jl b/src/utils.jl index efa6196af8..e1e8c50af4 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -708,7 +708,7 @@ function collect_var!(unknowns, parameters, var, iv; depth = 0) collect_vars!(unknowns, parameters, arguments(var), iv) elseif isparameter(var) || (iscall(var) && isparameter(operation(var))) push!(parameters, var) - elseif !isconstant(var) + else push!(unknowns, var) end # Add also any parameters that appear only as defaults in the var @@ -734,90 +734,6 @@ function check_scope_depth(scope, depth) end end -""" -Find all the symbolic constants of some equations or terms and return them as a vector. -""" -function collect_constants(x) - constants = BasicSymbolic[] - collect_constants!(constants, x) - return constants -end - -collect_constants!(::Any, ::Symbol) = nothing - -function collect_constants!(constants, arr::AbstractArray) - for el in arr - collect_constants!(constants, el) - end -end - -function collect_constants!(constants, eq::Equation) - collect_constants!(constants, eq.lhs) - collect_constants!(constants, eq.rhs) -end - -function collect_constants!(constants, eq::Inequality) - collect_constants!(constants, eq.lhs) - collect_constants!(constants, eq.rhs) -end - -collect_constants!(constants, x::Num) = collect_constants!(constants, unwrap(x)) -collect_constants!(constants, x::Real) = nothing -collect_constants(n::Nothing) = BasicSymbolic[] - -function collect_constants!(constants, expr::Symbolic) - if issym(expr) && isconstant(expr) - push!(constants, expr) - else - evars = vars(expr) - if length(evars) == 1 && isequal(only(evars), expr) - return nothing #avoid infinite recursion for vars(x(t)) == [x(t)] - else - for var in evars - collect_constants!(constants, var) - end - end - end -end - -function collect_constants!(constants, expr::Union{ConstantRateJump, VariableRateJump}) - collect_constants!(constants, expr.rate) - collect_constants!(constants, expr.affect!) -end - -function collect_constants!(constants, ::MassActionJump) - return constants -end - -""" -Replace symbolic constants with their literal values -""" -function eliminate_constants(eqs, cs) - cmap = Dict(x => getdefault(x) for x in cs) - return substitute(eqs, cmap) -end - -""" -Create a function preface containing assignments of default values to constants. -""" -function get_preprocess_constants(eqs) - cs = collect_constants(eqs) - pre = ex -> Let(Assignment[Assignment(x, getdefault(x)) for x in cs], - ex, false) - return pre -end - -function get_postprocess_fbody(sys) - if has_preface(sys) && (pre = preface(sys); pre !== nothing) - pre_ = let pre = pre - ex -> Let(pre, ex, false) - end - else - pre_ = ex -> ex - end - return pre_ -end - """ $(SIGNATURES) @@ -838,22 +754,6 @@ end isarray(x) = x isa AbstractArray || x isa Symbolics.Arr -function get_cmap(sys, exprs = nothing) - #Inject substitutions for constants => values - buffer = [] - has_eqs(sys) && append!(buffer, collect(get_eqs(sys))) - has_observed(sys) && append!(buffer, collect(get_observed(sys))) - has_op(sys) && push!(buffer, get_op(sys)) - has_constraints(sys) && append!(buffer, get_constraints(sys)) - cs = collect_constants(buffer) #ctrls? what else? - if exprs !== nothing - cs = [cs; collect_constants(exprs)] - end - # Swap constants for their values - cmap = map(x -> x ~ getdefault(x), cs) - return cmap, cs -end - function empty_substitutions(sys) isempty(observed(sys)) end @@ -1043,21 +943,6 @@ function Base.iterate(it::StatefulBFS, queue = (eltype(it)[(0, it.t)])) return (lv, t), queue end -function fold_constants(ex) - if iscall(ex) - maketerm(typeof(ex), operation(ex), map(fold_constants, arguments(ex)), - metadata(ex)) - elseif issym(ex) && isconstant(ex) - if (unit = getmetadata(ex, VariableUnit, nothing); unit !== nothing) - ex # we cannot fold constant with units - else - getdefault(ex) - end - else - ex - end -end - normalize_to_differential(s) = s function restrict_array_to_union(arr) From 92781396cc22c13d288688a3371458223fd75dd7 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Mon, 19 May 2025 23:08:06 +0530 Subject: [PATCH 214/235] refactor: make `@constants` create non-tunable parameters --- src/constants.jl | 25 ++++--------------------- 1 file changed, 4 insertions(+), 21 deletions(-) diff --git a/src/constants.jl b/src/constants.jl index a0a38fd057..4113287ad4 100644 --- a/src/constants.jl +++ b/src/constants.jl @@ -1,13 +1,9 @@ -import SymbolicUtils: symtype, term, hasmetadata, issym -struct MTKConstantCtx end - -isconstant(x::Num) = isconstant(unwrap(x)) """ Test whether `x` is a constant-type Sym. """ function isconstant(x) x = unwrap(x) - x isa Symbolic && getmetadata(x, MTKConstantCtx, false) + x isa Symbolic && !getmetadata(x, VariableTunable, true) end """ @@ -16,12 +12,11 @@ end Maps the parameter to a constant. The parameter must have a default. """ function toconstant(s) - hasmetadata(s, Symbolics.VariableDefaultValue) || - throw(ArgumentError("Constant `$(s)` must be assigned a default value.")) - setmetadata(s, MTKConstantCtx, true) + s = toparam(s) + setmetadata(s, VariableTunable, false) end -toconstant(s::Num) = wrap(toconstant(value(s))) +toconstant(s::Union{Num, Symbolics.Arr}) = wrap(toconstant(value(s))) """ $(SIGNATURES) @@ -36,15 +31,3 @@ macro constants(xs...) xs, toconstant) |> esc end - -""" -Substitute all `@constants` in the given expression -""" -function subs_constants(eqs) - consts = collect_constants(eqs) - if !isempty(consts) - csubs = Dict(c => getdefault(c) for c in consts) - eqs = substitute(eqs, csubs) - end - return eqs -end From 2b140de66adea58c5d5a6aa280aa80afc1be3dce Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Mon, 19 May 2025 23:08:16 +0530 Subject: [PATCH 215/235] refactor: update `@constants` parsing in `@mtkmodel` --- src/systems/model_parsing.jl | 76 +++++++++++++----------------------- 1 file changed, 28 insertions(+), 48 deletions(-) diff --git a/src/systems/model_parsing.jl b/src/systems/model_parsing.jl index 8fe07f7f99..9d293fd40d 100644 --- a/src/systems/model_parsing.jl +++ b/src/systems/model_parsing.jl @@ -53,7 +53,6 @@ function _model_macro(mod, fullname::Union{Expr, Symbol}, expr, isconnector) end exprs = Expr(:block) dict = Dict{Symbol, Any}( - :constants => Dict{Symbol, Dict}(), :defaults => Dict{Symbol, Any}(), :kwargs => Dict{Symbol, Dict}(), :structural_parameters => Dict{Symbol, Dict}() @@ -125,7 +124,7 @@ function _model_macro(mod, fullname::Union{Expr, Symbol}, expr, isconnector) description = get(dict, :description, "") @inline pop_structure_dict!.( - Ref(dict), [:constants, :defaults, :kwargs, :structural_parameters]) + Ref(dict), [:defaults, :kwargs, :structural_parameters]) sys = :($type($(flatten_equations)(equations), $iv, variables, parameters; name, description = $description, systems, gui_metadata = $gui_metadata, @@ -320,6 +319,10 @@ Base.@nospecializeinfer function parse_variable_def!( Meta.isexpr(a, :call) && assert_unique_independent_var(dict, a.args[end]) var = :($varname = $first(@parameters ($a[$(indices...)]::$type = $varval), $meta_val)) + elseif varclass == :constants + Meta.isexpr(a, :call) && assert_unique_independent_var(dict, a.args[end]) + var = :($varname = $first(@constants ($a[$(indices...)]::$type = $varval), + $meta_val)) else Meta.isexpr(a, :call) || throw("$a is not a variable of the independent variable") @@ -351,6 +354,12 @@ Base.@nospecializeinfer function parse_variable_def!( var = :($varname = $varname === $NO_VALUE ? $val : $varname; $varname = $first(@parameters ($a[$(indices...)]::$type = $varval), $(def_n_meta...))) + elseif varclass == :constants + Meta.isexpr(a, :call) && + assert_unique_independent_var(dict, a.args[end]) + var = :($varname = $varname === $NO_VALUE ? $val : $varname; + $varname = $first(@constants ($a[$(indices...)]::$type = $varval), + $(def_n_meta...))) else Meta.isexpr(a, :call) || throw("$a is not a variable of the independent variable") @@ -366,6 +375,11 @@ Base.@nospecializeinfer function parse_variable_def!( assert_unique_independent_var(dict, a.args[end]) var = :($varname = $varname === $NO_VALUE ? $def_n_meta : $varname; $varname = $first(@parameters $a[$(indices...)]::$type = $varname)) + elseif varclass == :constants + Meta.isexpr(a, :call) && + assert_unique_independent_var(dict, a.args[end]) + var = :($varname = $varname === $NO_VALUE ? $def_n_meta : $varname; + $varname = $first(@constants $a[$(indices...)]::$type = $varname)) else Meta.isexpr(a, :call) || throw("$a is not a variable of the independent variable") @@ -393,6 +407,9 @@ Base.@nospecializeinfer function parse_variable_def!( if varclass == :parameters Meta.isexpr(a, :call) && assert_unique_independent_var(dict, a.args[end]) var = :($varname = $first(@parameters $a[$(indices...)]::$type = $varname)) + elseif varclass == :constants + Meta.isexpr(a, :call) && assert_unique_independent_var(dict, a.args[end]) + var = :($varname = $first(@constants $a[$(indices...)]::$type = $varname)) elseif varclass == :variables Meta.isexpr(a, :call) || throw("$a is not a variable of the independent variable") @@ -453,6 +470,8 @@ function generate_var(a, varclass; type = Real) var = Symbolics.variable(a; T = type) if varclass == :parameters var = toparam(var) + elseif varclass == :constants + var = toconstant(var) elseif varclass == :independent_variables var = toiv(var) end @@ -513,6 +532,8 @@ function generate_var!(dict, a, b, varclass, mod; end if varclass == :parameters var = toparam(var) + elseif varclass == :constants + var = toconstant(var) end var end @@ -622,7 +643,7 @@ function parse_model!(exprs, comps, ext, eqs, icon, vs, ps, sps, c_evts, d_evts, elseif mname == Symbol("@equations") parse_equations!(exprs, eqs, dict, body) elseif mname == Symbol("@constants") - parse_constants!(exprs, dict, body, mod) + parse_variables!(exprs, ps, dict, mod, body, :constants, kwargs, where_types) elseif mname == Symbol("@continuous_events") parse_continuous_events!(c_evts, dict, body) elseif mname == Symbol("@discrete_events") @@ -643,49 +664,6 @@ function parse_model!(exprs, comps, ext, eqs, icon, vs, ps, sps, c_evts, d_evts, end end -function parse_constants!(exprs, dict, body, mod) - Base.remove_linenums!(body) - for arg in body.args - MLStyle.@match arg begin - Expr(:(=), Expr(:(::), a, type), Expr(:tuple, b, metadata)) || Expr(:(=), Expr(:(::), a, type), b) => begin - type = getfield(mod, type) - b = _type_check!(get_var(mod, b), a, type, :constants) - push!(exprs, - :($(Symbolics._parse_vars( - :constants, type, [:($a = $b), metadata], toconstant)))) - dict[:constants][a] = Dict(:value => b, :type => type) - if @isdefined metadata - for data in metadata.args - dict[:constants][a][data.args[1]] = data.args[2] - end - end - end - Expr(:(=), a, Expr(:tuple, b, metadata)) => begin - push!(exprs, - :($(Symbolics._parse_vars( - :constants, Real, [:($a = $b), metadata], toconstant)))) - dict[:constants][a] = Dict{Symbol, Any}(:value => get_var(mod, b)) - for data in metadata.args - dict[:constants][a][data.args[1]] = data.args[2] - end - end - Expr(:(=), a, b) => begin - push!(exprs, - :($(Symbolics._parse_vars( - :constants, Real, [:($a = $b)], toconstant)))) - dict[:constants][a] = Dict(:value => get_var(mod, b)) - end - _ => error("""Malformed constant definition `$arg`. Please use the following syntax: - ``` - @constants begin - var = value, [description = "This is an example constant."] - end - ``` - """) - end - end -end - push_additional_defaults!(dict, a, b::Number) = dict[:defaults][a] = b push_additional_defaults!(dict, a, b::QuoteNode) = dict[:defaults][a] = b.value function push_additional_defaults!(dict, a, b::Expr) @@ -950,6 +928,7 @@ function handle_conditional_vars!( arg, conditional_branch, mod, varclass, kwargs, where_types) conditional_dict = Dict(:kwargs => Dict(), :parameters => Any[Dict{Symbol, Dict{Symbol, Any}}()], + :constants => Any[Dict{Symbol, Dict{Symbol, Any}}()], :variables => Any[Dict{Symbol, Dict{Symbol, Any}}()]) for _arg in arg.args name, ex = parse_variable_arg( @@ -964,7 +943,7 @@ function prune_conditional_dict!(conditional_tuple::Tuple) prune_conditional_dict!.(collect(conditional_tuple)) end function prune_conditional_dict!(conditional_dict::Dict) - for k in [:parameters, :variables] + for k in [:parameters, :variables, :constants] length(conditional_dict[k]) == 1 && isempty(first(conditional_dict[k])) && delete!(conditional_dict, k) end @@ -981,7 +960,7 @@ end function get_conditional_dict!(conditional_dict::Dict, conditional_y_tuple::Dict) merge!(conditional_dict[:kwargs], conditional_y_tuple[:kwargs]) - for key in [:parameters, :variables] + for key in [:parameters, :variables, :constants] merge!(conditional_dict[key][1], conditional_y_tuple[key][1]) end conditional_dict @@ -1000,6 +979,7 @@ function push_conditional_dict!(dict, condition, conditional_dict, end conditional_y_dict = Dict(:kwargs => Dict(), :parameters => Any[Dict{Symbol, Dict{Symbol, Any}}()], + :constants => Any[Dict{Symbol, Dict{Symbol, Any}}()], :variables => Any[Dict{Symbol, Dict{Symbol, Any}}()]) get_conditional_dict!(conditional_y_dict, conditional_y_tuple) From 9f39e289a04842d5c810022b4c2cc9396f686181 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Mon, 19 May 2025 23:08:36 +0530 Subject: [PATCH 216/235] test: account for new `@constants` behavior in tests --- test/components.jl | 2 +- test/constants.jl | 13 +++++-------- test/discrete_system.jl | 2 +- test/dq_units.jl | 3 +-- test/funcaffect.jl | 2 +- test/input_output_handling.jl | 2 +- test/jumpsystem.jl | 13 ++++++++----- test/model_parsing.jl | 2 +- test/nonlinearsystem.jl | 18 +++++++++--------- test/odesystem.jl | 13 +++++++------ test/parameter_dependencies.jl | 8 ++++---- test/structural_transformation/tearing.jl | 12 +++++------- 12 files changed, 44 insertions(+), 46 deletions(-) diff --git a/test/components.jl b/test/components.jl index 7680afc50c..6102762d01 100644 --- a/test/components.jl +++ b/test/components.jl @@ -230,7 +230,7 @@ end eqs = [ v ~ i * R ] - extend(System(eqs, t, [], []; name = name), oneport) + extend(System(eqs, t, [], [R]; name = name), oneport) end capacitor = Capacitor(; name = :c1, C = 1.0) resistor = FixedResistor(; name = :r1) diff --git a/test/constants.jl b/test/constants.jl index 5e97d52d7f..bd28517ae6 100644 --- a/test/constants.jl +++ b/test/constants.jl @@ -4,7 +4,8 @@ MT = ModelingToolkit UMT = ModelingToolkit.UnitfulUnitCheck @constants a = 1 -@test_throws MT.ArgumentError @constants b +@test isconstant(a) +@test !istunable(a) @independent_variables t @variables x(t) w(t) @@ -14,9 +15,6 @@ eqs = [D(x) ~ a] prob = ODEProblem(complete(sys), [0], [0.0, 1.0], []) sol = solve(prob, Tsit5()) -newsys = MT.eliminate_constants(sys) -@test isequal(equations(newsys), [D(x) ~ 1]) - # Test structural_simplify substitutions & observed values eqs = [D(x) ~ 1, w ~ a] @@ -29,6 +27,7 @@ simp = structural_simplify(sys) @constants β=1 [unit = u"m/s"] UMT.get_unit(β) @test MT.isconstant(β) +@test !MT.istunable(β) @independent_variables t [unit = u"s"] @variables x(t) [unit = u"m"] D = Differential(t) @@ -36,17 +35,15 @@ eqs = [D(x) ~ β] @named sys = System(eqs, t) simp = structural_simplify(sys) -@test isempty(MT.collect_constants(nothing)) - @testset "Issue#3044" begin - @constants h = 1 + @constants h @parameters τ = 0.5 * h @variables x(MT.t_nounits) = h eqs = [MT.D_nounits(x) ~ (h - x) / τ] @mtkbuild fol_model = System(eqs, MT.t_nounits) - prob = ODEProblem(fol_model, [], (0.0, 10.0)) + prob = ODEProblem(fol_model, [], (0.0, 10.0), [h => 1]) @test prob[x] ≈ 1 @test prob.ps[τ] ≈ 0.5 end diff --git a/test/discrete_system.jl b/test/discrete_system.jl index 1ed6438d76..f3c5bff496 100644 --- a/test/discrete_system.jl +++ b/test/discrete_system.jl @@ -30,7 +30,7 @@ eqs = [S ~ S(k - 1) - infection * h, R ~ R(k - 1) + recovery] # System -@named sys = System(eqs, t, [S, I, R], [c, nsteps, δt, β, γ]) +@named sys = System(eqs, t, [S, I, R], [c, nsteps, δt, β, γ, h]) syss = structural_simplify(sys) @test syss == syss diff --git a/test/dq_units.jl b/test/dq_units.jl index 4d8c245e06..f0dc2dbe23 100644 --- a/test/dq_units.jl +++ b/test/dq_units.jl @@ -233,8 +233,7 @@ end L(t), [unit = u"m"] L_out(t), [unit = u"1"] end -@test to_m in ModelingToolkit.vars(ModelingToolkit.fold_constants(Symbolics.unwrap(L_out * - -to_m))) +@test to_m in ModelingToolkit.vars(Symbolics.unwrap(L_out * -to_m)) # test units for registered functions let diff --git a/test/funcaffect.jl b/test/funcaffect.jl index 8e73280eb3..b0745c8a9d 100644 --- a/test/funcaffect.jl +++ b/test/funcaffect.jl @@ -282,7 +282,7 @@ function bb_affect!(integ, u, p, ctx) integ.u[u.v] = -integ.u[u.v] end -@named bb_model = System(bb_eqs, t, sts, par, +@named bb_model = System(bb_eqs, t, sts, [par; zr], continuous_events = [ [y ~ zr] => (bb_affect!, [v], [], [], nothing) ]) diff --git a/test/input_output_handling.jl b/test/input_output_handling.jl index 1fd7732c50..1f14bc3814 100644 --- a/test/input_output_handling.jl +++ b/test/input_output_handling.jl @@ -447,7 +447,7 @@ end @constants c = 2.0 @variables x(t) eqs = [D(x) ~ c * x] - @mtkbuild sys = System(eqs, t, [x], []) + @mtkbuild sys = System(eqs, t, [x], [c]) f, dvs, ps, io_sys = ModelingToolkit.generate_control_function(sys) @test f[1]([0.5], nothing, MTKParameters(io_sys, []), 0.0) ≈ [1.0] diff --git a/test/jumpsystem.jl b/test/jumpsystem.jl index 50d59b3313..a42ba5ecb9 100644 --- a/test/jumpsystem.jl +++ b/test/jumpsystem.jl @@ -1,4 +1,5 @@ using ModelingToolkit, DiffEqBase, JumpProcesses, Test, LinearAlgebra +using SymbolicIndexingInterface using Random, StableRNGs, NonlinearSolve using OrdinaryDiffEq using ModelingToolkit: t_nounits as t, D_nounits as D @@ -17,7 +18,7 @@ rate₂ = γ * I + t 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], [β, γ]) +@named js = JumpSystem([j₁, j₂], t, [S, I, R], [β, γ, h]) unknowntoid = Dict(MT.value(unknown) => i for (i, unknown) in enumerate(unknowns(js))) mtjump1 = MT.assemble_crj(js, j₁, unknowntoid) mtjump2 = MT.assemble_vrj(js, j₂, unknowntoid) @@ -38,7 +39,7 @@ jump2 = VariableRateJump(rate2, affect2!) # test crjs u = [100, 9, 5] -p = (0.1 / 1000, 0.01) +p = (0.1 / 1000, 0.01, 1) tf = 1.0 mutable struct TestInt{U, V, T} u::U @@ -62,15 +63,15 @@ jump2.affect!(integrator) rate₃ = γ * I * h affect₃ = [I ~ Pre(I) * h - 1, R ~ Pre(R) + 1] j₃ = ConstantRateJump(rate₃, affect₃) -@named js2 = JumpSystem([j₁, j₃], t, [S, I, R], [β, γ]) +@named js2 = JumpSystem([j₁, j₃], t, [S, I, R], [β, γ, h]) js2 = complete(js2) u₀ = [999, 1, 0]; -p = (0.1 / 1000, 0.01); tspan = (0.0, 250.0); u₀map = [S => 999, I => 1, R => 0] parammap = [β => 0.1 / 1000, γ => 0.01] jprob = JumpProblem(js2, u₀map, tspan, parammap; aggregator = Direct(), save_positions = (false, false), rng) +p = parameter_values(jprob) @test jprob.prob isa DiscreteProblem Nsims = 30000 function getmean(jprob, Nsims; use_stepper = true) @@ -90,7 +91,7 @@ mb = getmean(jprobb, Nsims; use_stepper = false) @variables S2(t) obs = [S2 ~ 2 * S] -@named js2b = JumpSystem([j₁, j₃], t, [S, I, R], [β, γ], observed = obs) +@named js2b = JumpSystem([j₁, j₃], t, [S, I, R], [β, γ, h], observed = obs) js2b = complete(js2b) jprob = JumpProblem(js2b, u₀map, tspan, parammap; aggregator = Direct(), save_positions = (false, false), rng) @@ -110,6 +111,8 @@ jump2 = ConstantRateJump(rate2, affect2!) mtjumps = jprob.discrete_jump_aggregation @test abs(mtjumps.rates[1](u, p, tf) - jump1.rate(u, p, tf)) < 10 * eps() @test abs(mtjumps.rates[2](u, p, tf) - jump2.rate(u, p, tf)) < 10 * eps() + +ModelingToolkit.@set! mtintegrator.p = (mtintegrator.p, (1,)) mtjumps.affects![1](mtintegrator) jump1.affect!(integrator) @test all(integrator.u .== mtintegrator.u) diff --git a/test/model_parsing.jl b/test/model_parsing.jl index 705ec79816..88fa7faac7 100644 --- a/test/model_parsing.jl +++ b/test/model_parsing.jl @@ -511,7 +511,7 @@ using ModelingToolkit: getdefault, scalarize @test eval(ModelWithComponentArray.structure[:parameters][:r][:unit]) == eval(u"Ω") - @test lastindex(parameters(model_with_component_array)) == 3 + @test lastindex(parameters(model_with_component_array)) == 4 # Test the constant `k`. Manually k's value should be kept in sync here # and the ModelParsingPrecompile. diff --git a/test/nonlinearsystem.jl b/test/nonlinearsystem.jl index 3eb141ebc1..313866d1d8 100644 --- a/test/nonlinearsystem.jl +++ b/test/nonlinearsystem.jl @@ -26,13 +26,13 @@ end eqs = [0 ~ σ * (y - x) * h, 0 ~ x * (ρ - z) - y, 0 ~ x * y - β * z] -@named ns = System(eqs, [x, y, z], [σ, ρ, β], defaults = Dict(x => 2)) +@named ns = System(eqs, [x, y, z], [σ, ρ, β, h], defaults = Dict(x => 2)) @test eval(toexpr(ns)) == ns -test_nlsys_inference("standard", ns, (x, y, z), (σ, ρ, β)) +test_nlsys_inference("standard", ns, (x, y, z), (σ, ρ, β, h)) @test begin - f = generate_rhs(ns, [x, y, z], [σ, ρ, β], expression = Val{false})[2] + f = generate_rhs(ns, [x, y, z], [σ, ρ, β, h], expression = Val{false})[2] du = [0.0, 0.0, 0.0] - f(du, [1, 2, 3], [1, 2, 3]) + f(du, [1, 2, 3], [1, 2, 3, 1]) du ≈ [1, -3, -7] end @@ -64,9 +64,9 @@ a = y - x eqs = [0 ~ σ * a * h, 0 ~ x * (ρ - z) - y, 0 ~ x * y - β * z] -@named ns = System(eqs, [x, y, z], [σ, ρ, β]) +@named ns = System(eqs, [x, y, z], [σ, ρ, β, h]) ns = complete(ns) -nlsys_func = generate_rhs(ns, [x, y, z], [σ, ρ, β]) +nlsys_func = generate_rhs(ns, [x, y, z], [σ, ρ, β, h]) nf = NonlinearFunction(ns) jac = calculate_jacobian(ns) @@ -99,7 +99,7 @@ eqs1 = [ 0 ~ x + y - z - u ] -lorenz = name -> System(eqs1, [x, y, z, u, F], [σ, ρ, β], name = name) +lorenz = name -> System(eqs1, [x, y, z, u, F], [σ, ρ, β, h], name = name) lorenz1 = lorenz(:lorenz1) @test_throws ArgumentError NonlinearProblem(complete(lorenz1), zeros(5), zeros(3)) lorenz2 = lorenz(:lorenz2) @@ -132,7 +132,7 @@ sol = solve(prob, FBDF(), reltol = 1e-7, abstol = 1e-7) eqs = [0 ~ σ * (y - x), 0 ~ x * (ρ - z) - y, 0 ~ x * y - β * z * h] -@named ns = System(eqs, [x, y, z], [σ, ρ, β]) +@named ns = System(eqs, [x, y, z], [σ, ρ, β, h]) np = NonlinearProblem( complete(ns), [0, 0, 0], [σ => 1, ρ => 2, β => 3], jac = true, sparse = true) @test calculate_jacobian(ns, sparse = true) isa SparseMatrixCSC @@ -214,7 +214,7 @@ testdict = Dict([:test => 1]) eqs = [0 ~ a * (y - x) * h, 0 ~ x * (b - z) - y, 0 ~ x * y - c * z] - @named sys = System(eqs, [x, y, z], [a, b, c], defaults = Dict(x => 2.0)) + @named sys = System(eqs, [x, y, z], [a, b, c, h], defaults = Dict(x => 2.0)) sys = complete(sys) prob = NonlinearProblem(sys, ones(length(unknowns(sys)))) diff --git a/test/odesystem.jl b/test/odesystem.jl index 904ca58d1a..33267e8c78 100644 --- a/test/odesystem.jl +++ b/test/odesystem.jl @@ -27,7 +27,7 @@ ModelingToolkit.toexpr.(eqs)[1] @named de = System(eqs, t; defaults = Dict(x => 1)) subed = substitute(de, [σ => k]) ssort(eqs) = sort(eqs, by = string) -@test isequal(ssort(parameters(subed)), [k, β, ρ]) +@test isequal(ssort(parameters(subed)), [k, β, κ, ρ]) @test isequal(equations(subed), [D(x) ~ k * (y - x) D(y) ~ (ρ - z) * x - y @@ -47,7 +47,7 @@ function test_diffeq_inference(name, sys, iv, dvs, ps) end end -test_diffeq_inference("standard", de, t, [x, y, z], [ρ, σ, β]) +test_diffeq_inference("standard", de, t, [x, y, z], [ρ, σ, β, κ]) jac_expr = generate_jacobian(de) jac = calculate_jacobian(de) jacfun = eval(jac_expr[2]) @@ -138,11 +138,11 @@ tgrad_iip(du, u, p, t) eqs = [D(x) ~ σ(t - 1) * (y - x), D(y) ~ x * (ρ - z) - y, D(z) ~ x * y - β * z * κ] -@named de = System(eqs, t, [x, y, z], [σ, ρ, β]) -test_diffeq_inference("single internal iv-varying", de, t, (x, y, z), (σ, ρ, β)) +@named de = System(eqs, t, [x, y, z], [σ, ρ, β, κ]) +test_diffeq_inference("single internal iv-varying", de, t, (x, y, z), (σ, ρ, β, κ)) f = generate_rhs(de, expression = Val{false}, wrap_gfw = Val{true}) du = [0.0, 0.0, 0.0] -f(du, [1.0, 2.0, 3.0], [x -> x + 7, 2, 3], 5.0) +f(du, [1.0, 2.0, 3.0], [x -> x + 7, 2, 3, 1], 5.0) @test du ≈ [11, -3, -7] eqs = [D(x) ~ x + 10σ(t - 1) + 100σ(t - 2) + 1000σ(t^2)] @@ -1202,7 +1202,8 @@ end prob = ODEProblem(sys, u0, (0.0, 1.0), p) # evaluate - u0_v, p_v, _ = ModelingToolkit.get_u0_p(sys, u0, p) + u0_v = prob.u0 + p_v = prob.p @test prob.f(u0_v, p_v, 0.0) == [c_b, c_a] end diff --git a/test/parameter_dependencies.jl b/test/parameter_dependencies.jl index 624dbe8b6b..5498ecf4d8 100644 --- a/test/parameter_dependencies.jl +++ b/test/parameter_dependencies.jl @@ -296,9 +296,9 @@ end j₁ = ConstantRateJump(rate₁, affect₁) j₃ = ConstantRateJump(rate₃, affect₃) @named js2 = JumpSystem( - [j₃], t, [S, I, R], [γ]; parameter_dependencies = [β => 0.01γ]) - @test isequal(only(parameters(js2)), γ) - @test Set(full_parameters(js2)) == Set([γ, β]) + [j₃], t, [S, I, R], [γ, h]; parameter_dependencies = [β => 0.01γ]) + @test issetequal(parameters(js2), [γ, h]) + @test Set(full_parameters(js2)) == Set([γ, β, h]) js2 = complete(js2) tspan = (0.0, 250.0) u₀map = [S => 999, I => 1, R => 0] @@ -310,7 +310,7 @@ end @test_nowarn solve(jprob, SSAStepper()) @named js2 = JumpSystem( - [j₁, j₃], t, [S, I, R], [γ]; parameter_dependencies = [β => 0.01γ], + [j₁, j₃], t, [S, I, R], [γ, h]; parameter_dependencies = [β => 0.01γ], discrete_events = [SymbolicDiscreteCallback( [10.0] => [γ ~ 0.02], discrete_parameters = [γ])]) js2 = complete(js2) diff --git a/test/structural_transformation/tearing.jl b/test/structural_transformation/tearing.jl index e91f3fa988..f6c4fe5c44 100644 --- a/test/structural_transformation/tearing.jl +++ b/test/structural_transformation/tearing.jl @@ -18,7 +18,7 @@ eqs = [ 0 ~ u4 - hypot(u2, u3), 0 ~ u5 - hypot(u4, u1) ] -@named sys = System(eqs, [u1, u2, u3, u4, u5], []) +@named sys = System(eqs, [u1, u2, u3, u4, u5], [h]) state = TearingState(sys) StructuralTransformations.find_solvables!(state) @@ -149,19 +149,17 @@ eqs = [D(x) ~ z * h 0 ~ sin(z) + y - p * t] @named daesys = System(eqs, t) 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 equations(newdaesys) == [D(x) ~ h * z; 0 ~ y + sin(z) - p * t] +@test equations(tearing_substitution(newdaesys)) == [D(x) ~ h * z; 0 ~ x + sin(z) - p * t] @test isequal(unknowns(newdaesys), [x, z]) -@test isequal(unknowns(newdaesys), [x, z]) -@test_deprecated ODAEProblem(newdaesys, [x => 1.0, z => -0.5π], (0, 1.0), [p => 0.2]) prob = ODEProblem(newdaesys, [x => 1.0, z => -0.5π], (0, 1.0), [p => 0.2]) du = [0.0, 0.0]; u = [1.0, -0.5π]; -pr = 0.2; +pr = prob.p; tt = 0.1; @test (@ballocated $(prob.f)($du, $u, $pr, $tt)) == 0 prob.f(du, u, pr, tt) -@test du≈[u[2], u[1] + sin(u[2]) - pr * tt] atol=1e-5 +@test du≈[u[2], u[1] + sin(u[2]) - prob.ps[p] * tt] atol=1e-5 # test the initial guess is respected @named sys = System(eqs, t, defaults = Dict(z => NaN)) From 21baf12db5e129a6f401252cd566e7c85faf8b69 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Fri, 4 Apr 2025 13:58:01 +0530 Subject: [PATCH 217/235] fix: don't `toparam` inside `Initial` --- src/systems/abstractsystem.jl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/systems/abstractsystem.jl b/src/systems/abstractsystem.jl index 1a90faad92..248768b8d4 100644 --- a/src/systems/abstractsystem.jl +++ b/src/systems/abstractsystem.jl @@ -648,14 +648,14 @@ function (f::Initial)(x) iscall(x) && operation(x) isa Initial && return x result = if symbolic_type(x) == ArraySymbolic() # create an array for `Initial(array)` - Symbolics.array_term(f, toparam(x)) + Symbolics.array_term(f, x) elseif iscall(x) && operation(x) == getindex # instead of `Initial(x[1])` create `Initial(x)[1]` # which allows parameter indexing to handle this case automatically. arr = arguments(x)[1] - term(getindex, f(toparam(arr)), arguments(x)[2:end]...) + term(getindex, f(arr), arguments(x)[2:end]...) else - term(f, toparam(x)) + term(f, x) end # the result should be a parameter result = toparam(result) From 811196b16128b825ee53833d5903e0b4510a57cd Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Fri, 4 Apr 2025 13:58:22 +0530 Subject: [PATCH 218/235] feat: reduce reliance on metadata in `structural_simplify` --- src/inputoutput.jl | 2 +- src/systems/systemstructure.jl | 226 +++++++++++++++++++++------------ 2 files changed, 143 insertions(+), 85 deletions(-) diff --git a/src/inputoutput.jl b/src/inputoutput.jl index 19603b76cd..97376a2a44 100644 --- a/src/inputoutput.jl +++ b/src/inputoutput.jl @@ -319,7 +319,7 @@ function inputs_to_parameters!(state::TransformationState, inputsyms) @set! sys.ps = [ps; new_parameters] @set! state.sys = sys - @set! state.fullvars = new_fullvars + @set! state.fullvars = Vector{BasicSymbolic}(new_fullvars) @set! state.structure = structure return state end diff --git a/src/systems/systemstructure.jl b/src/systems/systemstructure.jl index c1b2c337ac..e8d81e0820 100644 --- a/src/systems/systemstructure.jl +++ b/src/systems/systemstructure.jl @@ -204,7 +204,7 @@ mutable struct TearingState{T <: AbstractSystem} <: AbstractTearingState{T} """The system of equations.""" sys::T """The set of variables of the system.""" - fullvars::Vector + fullvars::Vector{BasicSymbolic} structure::SystemStructure extra_eqs::Vector param_derivative_map::Dict{BasicSymbolic, Any} @@ -254,128 +254,164 @@ function Base.push!(ev::EquationsView, eq) push!(ev.ts.extra_eqs, eq) end -function is_time_dependent_parameter(p, iv) - return iv !== nothing && isparameter(p) && iscall(p) && - (operation(p) === getindex && is_time_dependent_parameter(arguments(p)[1], iv) || +function is_time_dependent_parameter(p, allps, iv) + return iv !== nothing && p in allps && iscall(p) && + (operation(p) === getindex && + is_time_dependent_parameter(arguments(p)[1], allps, iv) || (args = arguments(p); length(args)) == 1 && isequal(only(args), iv)) end +function symbolic_contains(var, set) + var in set || + symbolic_type(var) == ArraySymbolic() && + Symbolics.shape(var) != Symbolics.Unknown() && + all(x -> x in set, Symbolics.scalarize(var)) +end + function TearingState(sys; quick_cancel = false, check = true, sort_eqs = true) + # flatten system sys = flatten(sys) ivs = independent_variables(sys) iv = length(ivs) == 1 ? ivs[1] : nothing - # scalarize array equations, without scalarizing arguments to registered functions - eqs = flatten_equations(copy(equations(sys))) + # flatten array equations + eqs = flatten_equations(equations(sys)) neqs = length(eqs) - dervaridxs = OrderedSet{Int}() - var2idx = Dict{Any, Int}() - symbolic_incidence = [] - fullvars = [] param_derivative_map = Dict{BasicSymbolic, Any}() - var_counter = Ref(0) + # * Scalarize unknowns + dvs = Set{BasicSymbolic}() + fullvars = BasicSymbolic[] + for x in unknowns(sys) + push!(dvs, x) + xx = Symbolics.scalarize(x) + if xx isa AbstractArray + union!(dvs, xx) + end + end + ps = Set{Symbolic}() + for x in full_parameters(sys) + push!(ps, x) + if symbolic_type(x) == ArraySymbolic() && Symbolics.shape(x) != Symbolics.Unknown() + xx = Symbolics.scalarize(x) + union!(ps, xx) + end + end + browns = Set{BasicSymbolic}() + for x in brownians(sys) + push!(browns, x) + xx = Symbolics.scalarize(x) + if xx isa AbstractArray + union!(browns, xx) + end + end + var2idx = Dict{BasicSymbolic, Int}() var_types = VariableType[] - addvar! = let fullvars = fullvars, var_counter = var_counter, var_types = var_types - var -> get!(var2idx, var) do + addvar! = let fullvars = fullvars, dvs = dvs, var2idx = var2idx, var_types = var_types + (var, vtype) -> get!(var2idx, var) do + push!(dvs, var) push!(fullvars, var) - push!(var_types, getvariabletype(var)) - var_counter[] += 1 + push!(var_types, vtype) + return length(fullvars) end end - vars = OrderedSet() - varsvec = [] + # build symbolic incidence + symbolic_incidence = Vector{BasicSymbolic}[] + varsbuf = Set() eqs_to_retain = trues(length(eqs)) - for (i, eq′) in enumerate(eqs) - if eq′.lhs isa Connection - check ? error("$(nameof(sys)) has unexpanded `connect` statements") : - return nothing - end - if iscall(eq′.lhs) && (op = operation(eq′.lhs)) isa Differential && - isequal(op.x, iv) && is_time_dependent_parameter(only(arguments(eq′.lhs)), iv) + for (i, eq) in enumerate(eqs) + if iscall(eq.lhs) && (op = operation(eq.lhs)) isa Differential && + isequal(op.x, iv) && is_time_dependent_parameter(only(arguments(eq.lhs)), ps, iv) # parameter derivatives are opted out by specifying `D(p) ~ missing`, but # we want to store `nothing` in the map because that means `fast_substitute` # will ignore the rule. We will this identify the presence of `eq′.lhs` in # the differentiated expression and error. - param_derivative_map[eq′.lhs] = coalesce(eq′.rhs, nothing) + param_derivative_map[eq.lhs] = coalesce(eq.rhs, nothing) eqs_to_retain[i] = false # change the equation if the RHS is `missing` so the rest of this loop works - eq′ = eq′.lhs ~ coalesce(eq′.rhs, 0.0) + eq = 0.0 ~ coalesce(eq.rhs, 0.0) end - if _iszero(eq′.lhs) - rhs = quick_cancel ? quick_cancel_expr(eq′.rhs) : eq′.rhs - eq = eq′ - else - lhs = quick_cancel ? quick_cancel_expr(eq′.lhs) : eq′.lhs - rhs = quick_cancel ? quick_cancel_expr(eq′.rhs) : eq′.rhs + rhs = quick_cancel ? quick_cancel_expr(eq.rhs) : eq.rhs + if !_iszero(eq.lhs) + lhs = quick_cancel ? quick_cancel_expr(eq.lhs) : eq.lhs eq = 0 ~ rhs - lhs end - vars!(vars, eq.rhs, op = Symbolics.Operator) - for v in vars - _var, _ = var_from_nested_derivative(v) - any(isequal(_var), ivs) && continue - if isparameter(_var) || - (iscall(_var) && isparameter(operation(_var))) - if is_time_dependent_parameter(_var, iv) && - !haskey(param_derivative_map, Differential(iv)(_var)) + empty!(varsbuf) + vars!(varsbuf, eq; op = Symbolics.Operator) + incidence = Set{BasicSymbolic}() + isalgeq = true + for v in varsbuf + # additionally track brownians in fullvars + if v in browns + addvar!(v, BROWNIAN) + push!(incidence, v) + end + + # TODO: Can we handle this without `isparameter`? + if symbolic_contains(v, ps) || + getmetadata(v, SymScope, LocalScope()) isa GlobalScope && isparameter(v) + if is_time_dependent_parameter(v, ps, iv) && + !haskey(param_derivative_map, Differential(iv)(v)) # Parameter derivatives default to zero - they stay constant # between callbacks - param_derivative_map[Differential(iv)(_var)] = 0.0 + param_derivative_map[Differential(iv)(v)] = 0.0 end continue end - v = scalarize(v) - if v isa AbstractArray - append!(varsvec, v) - else - push!(varsvec, v) - end - end - isalgeq = true - unknownvars = [] - for var in varsvec - ModelingToolkit.isdelay(var, iv) && continue - set_incidence = true - @label ANOTHER_VAR - _var, _ = var_from_nested_derivative(var) - any(isequal(_var), ivs) && continue - if isparameter(_var) || - (iscall(_var) && isparameter(operation(_var))) - continue - end - varidx = addvar!(var) - set_incidence && push!(unknownvars, var) - - dvar = var - idx = varidx - while isdifferential(dvar) - if !(idx in dervaridxs) - push!(dervaridxs, idx) + + isequal(v, iv) && continue + isdelay(v, iv) && continue + + if !symbolic_contains(v, dvs) + isvalid = iscall(v) && operation(v) isa Union{Shift, Sample, Hold} + v′ = v + while !isvalid && iscall(v′) && operation(v′) isa Union{Differential, Shift} + v′ = arguments(v′)[1] + if v′ in dvs || getmetadata(v′, SymScope, LocalScope()) isa GlobalScope + isvalid = true + break + end + end + if !isvalid + throw(ArgumentError("$v is present in the system but $v′ is not an unknown.")) + end + + addvar!(v, VARIABLE) + if iscall(v) && operation(v) isa Symbolics.Operator && !isdifferential(v) && + (it = input_timedomain(v)) !== nothing + v′ = only(arguments(v)) + addvar!(setmetadata(v′, VariableTimeDomain, it), VARIABLE) end - isalgeq = false - dvar = arguments(dvar)[1] - idx = addvar!(dvar) end - dvar = var - idx = varidx + isalgeq &= !isdifferential(v) - if iscall(var) && operation(var) isa Symbolics.Operator && - !isdifferential(var) && (it = input_timedomain(var)) !== nothing - set_incidence = false - var = only(arguments(var)) - var = setmetadata(var, VariableTimeDomain, it) - @goto ANOTHER_VAR + if symbolic_type(v) == ArraySymbolic() + vv = collect(v) + union!(incidence, vv) + map(vv) do vi + addvar!(vi, VARIABLE) + end + else + push!(incidence, v) + addvar!(v, VARIABLE) end end - push!(symbolic_incidence, copy(unknownvars)) - empty!(unknownvars) - empty!(vars) - empty!(varsvec) + if isalgeq eqs[i] = eq else eqs[i] = eqs[i].lhs ~ rhs end + push!(symbolic_incidence, collect(incidence)) + end + + dervaridxs = OrderedSet{Int}() + for (i, v) in enumerate(fullvars) + while isdifferential(v) + push!(dervaridxs, i) + v = arguments(v)[1] + i = addvar!(v, VARIABLE) + end end eqs = eqs[eqs_to_retain] neqs = length(eqs) @@ -389,6 +425,7 @@ function TearingState(sys; quick_cancel = false, check = true, sort_eqs = true) symbolic_incidence = symbolic_incidence[sortidxs] end + # Handle shifts - find lowest shift and add intermediates with derivative edges ### Handle discrete variables lowest_shift = Dict() for var in fullvars @@ -422,12 +459,13 @@ function TearingState(sys; quick_cancel = false, check = true, sort_eqs = true) for s in (steps - 1):-1:(lshift + 1) sf = Shift(tt, s) dvar = sf(v) - idx = addvar!(dvar) + idx = addvar!(dvar, VARIABLE) if !(idx in dervaridxs) push!(dervaridxs, idx) end end end + # sort `fullvars` such that the mass matrix is as diagonal as possible. dervaridxs = collect(dervaridxs) sorted_fullvars = OrderedSet(fullvars[dervaridxs]) @@ -451,6 +489,7 @@ function TearingState(sys; quick_cancel = false, check = true, sort_eqs = true) var2idx = Dict(fullvars .=> eachindex(fullvars)) dervaridxs = 1:length(dervaridxs) + # build `var_to_diff` nvars = length(fullvars) diffvars = [] var_to_diff = DiffGraph(nvars, true) @@ -462,6 +501,7 @@ function TearingState(sys; quick_cancel = false, check = true, sort_eqs = true) var_to_diff[diffvaridx] = dervaridx end + # build incidence graph graph = BipartiteGraph(neqs, nvars, Val(false)) for (ie, vars) in enumerate(symbolic_incidence), v in vars jv = var2idx[v] @@ -731,3 +771,21 @@ function _structural_simplify!(state::TearingState; simplify = false, ModelingToolkit.invalidate_cache!(sys) end + +struct DifferentiatedVariableNotUnknownError <: Exception + differentiated::Any + undifferentiated::Any +end + +function Base.showerror(io::IO, err::DifferentiatedVariableNotUnknownError) + undiff = err.undifferentiated + diff = err.differentiated + print(io, + "Variable $undiff occurs differentiated as $diff but is not an unknown of the system.") + scope = getmetadata(undiff, SymScope, LocalScope()) + depth = expected_scope_depth(scope) + if depth > 0 + print(io, + "\nVariable $undiff expects $depth more levels in the hierarchy to be an unknown.") + end +end From 3c09dea926acd6f6fe0447be92d23f09fa6b7e6c Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Mon, 19 May 2025 18:02:43 +0530 Subject: [PATCH 219/235] fix: fix `linearization_function` with analysis points mutating the system --- src/systems/analysis_points.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/systems/analysis_points.jl b/src/systems/analysis_points.jl index 27a0204cb8..2ce6356aa7 100644 --- a/src/systems/analysis_points.jl +++ b/src/systems/analysis_points.jl @@ -950,7 +950,7 @@ function linearization_function(sys::AbstractSystem, if output isa AnalysisPoint sys, (output_var,) = apply_transformation(AddVariable(output), sys) sys, (input_var,) = apply_transformation(GetInput(output), sys) - push!(get_eqs(sys), output_var ~ input_var) + @set! sys.eqs = [get_eqs(sys); output_var ~ input_var] else output_var = output end From 348287f439182d7cf74577b899fec70e80b3bd07 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Mon, 19 May 2025 18:03:05 +0530 Subject: [PATCH 220/235] fix: properly handle array equations during variable discovery in `System` --- src/systems/system.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/systems/system.jl b/src/systems/system.jl index 940ca39350..c84187feaa 100644 --- a/src/systems/system.jl +++ b/src/systems/system.jl @@ -206,7 +206,7 @@ function System(eqs::Vector{Equation}, iv; kwargs...) diffeqs = Equation[] othereqs = Equation[] for eq in eqs - if !(eq.lhs isa Union{Symbolic, Number}) + if !(eq.lhs isa Union{Symbolic, Number, AbstractArray}) push!(othereqs, eq) continue end From 6d994205699a257f7627bb4f71e7d67f77ea3f90 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Mon, 19 May 2025 18:03:42 +0530 Subject: [PATCH 221/235] test: fix broken tearing test --- test/structural_transformation/tearing.jl | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/test/structural_transformation/tearing.jl b/test/structural_transformation/tearing.jl index f6c4fe5c44..5bac18a253 100644 --- a/test/structural_transformation/tearing.jl +++ b/test/structural_transformation/tearing.jl @@ -58,11 +58,9 @@ graph2vars(graph) = map(is -> Set(map(i -> int2var[i], is)), graph.fadjlist) Set([u4]) Set([u5])] -state = TearingState(tearing(sys)) -let sss = state.structure - @unpack graph = sss - @test graph2vars(graph) == [Set([u1, u2, u5])] -end +newsys = tearing(sys) +@test length(equations(newsys)) == 1 +@test issetequal(ModelingToolkit.vars(equations(newsys)), [u1, u4, u5]) # Before: # u1 u2 u3 u4 u5 From 9fc141a414c5ab841014fb4143c09e26b0b69ddf Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Mon, 19 May 2025 18:04:12 +0530 Subject: [PATCH 222/235] test: fix missing subcomponent in components test --- test/components.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/components.jl b/test/components.jl index 6102762d01..5160aad6da 100644 --- a/test/components.jl +++ b/test/components.jl @@ -197,7 +197,7 @@ end connect(resistor.heat_port, heat_capacitor.port)] compose(System(rc_eqs, t, name = Symbol(name, i)), - [resistor, capacitor, source, ground, heat_capacitor]) + [resistor, capacitor, source, ground, shape, heat_capacitor]) end V = 2.0 @named shape = Constant(k = V) From 59e5ad6e759173ef45bb3867caa72192910ed477 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Mon, 19 May 2025 18:04:19 +0530 Subject: [PATCH 223/235] fix: fix incorrect default in DDE test --- test/dde.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/dde.jl b/test/dde.jl index a5a0f6ee0d..cac207caa3 100644 --- a/test/dde.jl +++ b/test/dde.jl @@ -98,7 +98,7 @@ prob_sa = SDDEProblem( function oscillator(; name, k = 1.0, τ = 0.01) @parameters k=k τ=τ - @variables x(..)=0.1 y(t)=0.1 jcn(t)=0.0 delx(t) + @variables x(..)=0.1 y(t)=0.1 jcn(t) delx(t) eqs = [D(x(t)) ~ y, D(y) ~ -k * x(t - τ) + jcn, delx ~ x(t - τ)] From 678d98f48e5fea98ffcf06365cc3ec9bee21e3d3 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Mon, 19 May 2025 18:04:37 +0530 Subject: [PATCH 224/235] fix: fix parameter not passed to `System` in units test --- test/dq_units.jl | 2 +- test/units.jl | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/dq_units.jl b/test/dq_units.jl index f0dc2dbe23..5a635144f5 100644 --- a/test/dq_units.jl +++ b/test/dq_units.jl @@ -129,7 +129,7 @@ sys_simple = structural_simplify(sys) eqs = [L ~ v * t, V ~ L^3] -@named sys = System(eqs, [V, L], [t, r]) +@named sys = System(eqs, [V, L], [t, r, v]) sys_simple = structural_simplify(sys) #Jump System diff --git a/test/units.jl b/test/units.jl index b7d141f347..f15145ffa9 100644 --- a/test/units.jl +++ b/test/units.jl @@ -156,7 +156,7 @@ sys_simple = structural_simplify(sys) eqs = [L ~ v * t, V ~ L^3] -@named sys = System(eqs, [V, L], [t, r]) +@named sys = System(eqs, [V, L], [v, t, r]) sys_simple = structural_simplify(sys) #Jump System From bd15dfd2563e94c2f2996769c4429c9d3bdd8c1c Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Mon, 19 May 2025 18:04:52 +0530 Subject: [PATCH 225/235] fix: fix parameter not passed to system in error handling test --- test/error_handling.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/error_handling.jl b/test/error_handling.jl index d6c0fa8caa..6b416d9a6e 100644 --- a/test/error_handling.jl +++ b/test/error_handling.jl @@ -27,7 +27,7 @@ function OverdefinedConstantVoltage(; name, V = 1.0, I = 1.0) # Overdefine p.i and n.i n.i ~ I p.i ~ I] - System(eqs, t, [], [V], systems = [p, n], defaults = Dict(V => val, I => val2), + System(eqs, t, [], [V, I], systems = [p, n], defaults = Dict(V => val, I => val2), name = name) end From 9bd3a06dfd26b26649c62e5d76e4741e5df7ebc4 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Mon, 19 May 2025 18:05:05 +0530 Subject: [PATCH 226/235] test: update test with new simplification result --- test/nonlinearsystem.jl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/nonlinearsystem.jl b/test/nonlinearsystem.jl index 313866d1d8..375c8ba503 100644 --- a/test/nonlinearsystem.jl +++ b/test/nonlinearsystem.jl @@ -296,9 +296,9 @@ sys = structural_simplify(ns; conservative = true) # system that contains a chain of observed variables when simplified @variables x y z eqs = [0 ~ x^2 + 2z + y, z ~ y, y ~ x] # analytical solution x = y = z = 0 or -3 - @mtkbuild ns = System(eqs) # solve for y with observed chain z -> x -> y - @test isequal(expand.(calculate_jacobian(ns)), [3 // 2 + y;;]) - @test isequal(calculate_hessian(ns), [[1;;]]) + @mtkbuild ns = System(eqs) # solve for y with observed chain z -> y -> x + @test isequal(expand.(calculate_jacobian(ns)), [-3 // 2 - x;;]) + @test isequal(calculate_hessian(ns), [[-1;;]]) prob = NonlinearProblem(ns, unknowns(ns) .=> -4.0) # give guess < -3 to reach -3 sol = solve(prob, NewtonRaphson()) @test sol[x] ≈ sol[y] ≈ sol[z] ≈ -3 From abfb41b7709062a2c8ac5857f87bc7ecae0db130 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Mon, 19 May 2025 18:05:18 +0530 Subject: [PATCH 227/235] test: fix variable not passed to `System` in odesystem test --- test/odesystem.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/odesystem.jl b/test/odesystem.jl index 33267e8c78..57948c517d 100644 --- a/test/odesystem.jl +++ b/test/odesystem.jl @@ -1390,7 +1390,7 @@ end @parameters c(t) @mtkbuild sys = System([D(x) ~ c * cos(x), obs ~ c], t, - [x], + [x, obs], [c]; discrete_events = [SymbolicDiscreteCallback( 1.0 => [c ~ Pre(c) + 1], discrete_parameters = [c])]) From 0dedfe9849bbbdb3da955170a938ac4f12d26cb7 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Tue, 20 May 2025 12:14:38 +0530 Subject: [PATCH 228/235] refactor: don't check `isparameter` in `check_variables` and `check_parameters` --- src/utils.jl | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/utils.jl b/src/utils.jl index e1e8c50af4..13ebb28b6a 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -124,8 +124,6 @@ function check_parameters(ps, iv) for p in ps isequal(iv, p) && throw(ArgumentError("Independent variable $iv not allowed in parameters.")) - isparameter(p) || - throw(ArgumentError("$p is not a parameter.")) end end @@ -153,8 +151,6 @@ function check_variables(dvs, iv) throw(ArgumentError("Independent variable $iv not allowed in dependent variables.")) (is_delay_var(iv, dv) || occursin(iv, dv)) || throw(ArgumentError("Variable $dv is not a function of independent variable $iv.")) - isparameter(dv) && - throw(ArgumentError("$dv is not an unknown. It is a parameter.")) end end From 501dc7faabb2a691c82e2caaa4eacbec5e653077 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Tue, 20 May 2025 12:15:05 +0530 Subject: [PATCH 229/235] fix: don't use `isparameter` in `find_eq_solvables!` --- .../StructuralTransformations.jl | 1 + src/structural_transformation/utils.jl | 12 +++++++++--- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/structural_transformation/StructuralTransformations.jl b/src/structural_transformation/StructuralTransformations.jl index 2ba469e26a..15f1f7d2db 100644 --- a/src/structural_transformation/StructuralTransformations.jl +++ b/src/structural_transformation/StructuralTransformations.jl @@ -40,6 +40,7 @@ using ModelingToolkit: algeqs, EquationsView, dervars_range, diffvars_range, algvars_range, DiffGraph, complete!, get_fullvars, system_subset +using SymbolicIndexingInterface: symbolic_type, ArraySymbolic using ModelingToolkit.DiffEqBase using ModelingToolkit.StaticArrays diff --git a/src/structural_transformation/utils.jl b/src/structural_transformation/utils.jl index 2bf316cfa6..e1c8a74eca 100644 --- a/src/structural_transformation/utils.jl +++ b/src/structural_transformation/utils.jl @@ -228,9 +228,15 @@ function find_eq_solvables!(state::TearingState, ieq, to_rm = Int[], coeffs = no all_int_vars = false if !allow_symbolic if allow_parameter - all( - x -> ModelingToolkit.isparameter(x), - vars(a)) || continue + # if any of the variables in `a` are present in fullvars (taking into account arrays) + if any( + v -> any(isequal(v), fullvars) || + symbolic_type(v) == ArraySymbolic() && + Symbolics.shape(v) != Symbolics.Unknown() && + any(x -> any(isequal(x), fullvars), collect(v)), + vars(a)) + continue + end else continue end From 21d9cb896f159b41d18456fe79ea7c38289652fb Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Tue, 20 May 2025 12:26:52 +0530 Subject: [PATCH 230/235] fix: don't use `isparameter` in `generate_initializesystem` --- src/systems/nonlinear/initializesystem.jl | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/systems/nonlinear/initializesystem.jl b/src/systems/nonlinear/initializesystem.jl index a550f9d740..a364caaa18 100644 --- a/src/systems/nonlinear/initializesystem.jl +++ b/src/systems/nonlinear/initializesystem.jl @@ -214,7 +214,15 @@ function generate_initializesystem_timeindependent(sys::AbstractSystem; initialization_eqs = filter(initialization_eqs) do eq empty!(vs) vars!(vs, eq; op = Initial) - non_params = filter(!isparameter, vs) + allpars = full_parameters(sys) + for p in allpars + if symbolic_type(p) == ArraySymbolic() && + Symbolics.shape(p) != Symbolics.Unknown() + append!(allpars, Symbolics.scalarize(p)) + end + end + allpars = Set(allpars) + non_params = filter(!in(allpars), vs) # error if non-parameters are present in the initialization equations if !isempty(non_params) throw(UnknownsInTimeIndependentInitializationError(eq, non_params)) From 77a3f510d03dd840f2d80705b64f364a21dc47df Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Tue, 20 May 2025 12:27:08 +0530 Subject: [PATCH 231/235] test: test simplification independent of metadata --- test/structural_transformation/utils.jl | 50 +++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/test/structural_transformation/utils.jl b/test/structural_transformation/utils.jl index 7611ab5d33..b228cdbbf7 100644 --- a/test/structural_transformation/utils.jl +++ b/test/structural_transformation/utils.jl @@ -6,6 +6,7 @@ using UnPack using ModelingToolkit: t_nounits as t, D_nounits as D, default_toterm using Symbolics: unwrap using DataInterpolations +using OrdinaryDiffEq, NonlinearSolve, StochasticDiffEq const ST = StructuralTransformations # Define some variables @@ -386,3 +387,52 @@ end @test D(sys.k(t)) in vs end end + +@testset "Don't rely on metadata" begin + @testset "ODESystem" begin + @variables x(t) p + @parameters y(t) q + @mtkbuild sys = System([D(x) ~ x * q, x^2 + y^2 ~ p], t, [x, y], + [p, q]; initialization_eqs = [p + q ~ 3], + defaults = [p => missing], guesses = [p => 1.0, y => 1.0]) + @test length(equations(sys)) == 2 + @test length(parameters(sys)) == 2 + prob = ODEProblem(sys, [x => 1.0], (0.0, 1.0), [q => 2.0]) + integ = init(prob, Rodas5P(); abstol = 1e-10, reltol = 1e-8) + @test integ.ps[p]≈1.0 atol=1e-6 + @test integ[y]≈0.0 atol=1e-5 + end + + @testset "NonlinearSystem" begin + @variables x p + @parameters y q + @mtkbuild sys = System([0 ~ p * x + y, x^3 + y^3 ~ q], [x, y], + [p, q]; initialization_eqs = [p ~ q + 1], + guesses = [p => 1.0], defaults = [p => missing]) + @test length(equations(sys)) == length(unknowns(sys)) == 1 + @test length(observed(sys)) == 1 + @test observed(sys)[1].lhs in Set([x, y]) + @test length(parameters(sys)) == 2 + prob = NonlinearProblem(sys, [x => 1.0, y => 1.0], [q => 1.0]) + integ = init(prob, NewtonRaphson()) + @test prob.ps[p] ≈ 2.0 + end + + @testset "SDESystem" begin + @variables x(t) p a + @parameters y(t) q b + @brownian c + @mtkbuild sys = System([D(x) ~ x + q * a, D(y) ~ y + p * b + c], t, [x, y], + [p, q], [a, b, c]; initialization_eqs = [p + q ~ 4], + guesses = [p => 1.0], defaults = [p => missing]) + @test length(equations(sys)) == 2 + @test issetequal(unknowns(sys), [x, y]) + @test issetequal(parameters(sys), [p, q]) + @test isempty(brownians(sys)) + neqs = ModelingToolkit.get_noise_eqs(sys) + @test issetequal(sum.(eachrow(neqs)), [q, 1 + p]) + prob = SDEProblem(sys, [x => 1.0, y => 1.0], (0.0, 1.0), [q => 1.0]) + integ = init(prob, ImplicitEM()) + @test integ.ps[p] ≈ 3.0 + end +end From 6aa6039e19bdcd461261382212dcbc5c500ca62a Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Wed, 21 May 2025 14:05:45 +0530 Subject: [PATCH 232/235] fix: unwrap brownians in `noise_to_brownians` --- src/systems/diffeqs/basic_transformations.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/systems/diffeqs/basic_transformations.jl b/src/systems/diffeqs/basic_transformations.jl index 4bf3b40177..6046e5d4be 100644 --- a/src/systems/diffeqs/basic_transformations.jl +++ b/src/systems/diffeqs/basic_transformations.jl @@ -482,7 +482,7 @@ function noise_to_brownians(sys::System; names::Union{Symbol, Vector{Symbol}} = """)) end brownvars = map(names) do name - only(@brownian $name) + unwrap(only(@brownian $name)) end terms = if ndims(neqs) == 1 From 0723b71563d9ed7fb9f17ca318351c37dc451a0a Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Wed, 21 May 2025 14:06:28 +0530 Subject: [PATCH 233/235] test: pass `@constants` parameter to `System` constructor --- test/nonlinearsystem.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/nonlinearsystem.jl b/test/nonlinearsystem.jl index 375c8ba503..72de46bad0 100644 --- a/test/nonlinearsystem.jl +++ b/test/nonlinearsystem.jl @@ -108,7 +108,7 @@ lorenz2 = lorenz(:lorenz2) lorenz2.y ~ s * h lorenz1.F ~ lorenz2.u lorenz2.F ~ lorenz1.u], - [s, a], [], + [s, a], [h], systems = [lorenz1, lorenz2]) @test_nowarn alias_elimination(connected) From 92e3686eb8081a65c419aa420bdf77e932d9547b Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Wed, 21 May 2025 14:06:40 +0530 Subject: [PATCH 234/235] test: pass brownians to `System` constructor --- test/sdesystem.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/sdesystem.jl b/test/sdesystem.jl index 5bdc033498..a3e0ff00d0 100644 --- a/test/sdesystem.jl +++ b/test/sdesystem.jl @@ -792,12 +792,12 @@ end [input = true] end ps = @parameters a = 2 - @brownian η + browns = @brownian η eqs = [D(x) ~ -a * x + (input + 1) * η input ~ 0.0] - sys = System(eqs, t, sts, ps; name = :name) + sys = System(eqs, t, sts, ps, browns; name = :name) sys = structural_simplify(sys) @test ModelingToolkit.get_noise_eqs(sys) ≈ [1.0] prob = SDEProblem(sys, [], (0.0, 1.0), []) From 7bceb7ac70509958bb14abf55b7ccb1ec1bbc698 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Wed, 21 May 2025 19:00:31 +0530 Subject: [PATCH 235/235] fix: unhack observed when compiling callbacks --- src/systems/callbacks.jl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/systems/callbacks.jl b/src/systems/callbacks.jl index 59ff0e0d98..412c4e9bc7 100644 --- a/src/systems/callbacks.jl +++ b/src/systems/callbacks.jl @@ -902,9 +902,10 @@ function compile_equational_affect( aff_map = aff_to_sys(aff) sys_map = Dict([v => k for (k, v) in aff_map]) + obseqs, eqs = unhack_observed(observed(affsys), equations(affsys)) if isempty(equations(affsys)) update_eqs = Symbolics.fast_substitute( - observed(affsys), Dict([p => unPre(p) for p in parameters(affsys)])) + obseqs, 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]