From 66c29ef8546d0536ed819a711b9939e9c1cb28ee Mon Sep 17 00:00:00 2001 From: Christopher Rackauckas Date: Thu, 29 Feb 2024 05:16:05 -0600 Subject: [PATCH 01/27] WIP: Initialization on non-DAE models https://github.com/SciML/ModelingToolkit.jl/issues/2508 pointed out that the heuristic of `implicit_dae || calculate_massmatrix(sys) !== I`, i.e. "only do initialization on DAEs", while sensible for the DAE model, doesn't quite fit in the MTK context. Instead, what we need to do is check whether the varmap is correct and consistent for the chosen ODE. If it's not consistent for the selected ODE, then we still need to run an initialization step for the resulting ODE solve. This needs a few changes in the ODE solver as though, since currently only DAE solvers will run the initialization step. Thus the test case: ```julia using ModelingToolkit, OrdinaryDiffEq, Test using ModelingToolkit: t_nounits as t, D_nounits as D function System(;name) vars = @variables begin dx(t), [guess=0] ddx(t), [guess=0] end eqs = [ D(dx) ~ ddx 0 ~ ddx + dx + 1 ] return ODESystem(eqs, t, vars, []; name) end @mtkbuild sys = System() prob = ODEProblem(sys, [sys.dx => 1], (0,1)) # OK prob = ODEProblem(sys, [sys.ddx => -2], (0,1), guesses = [sys.dx => 1]) sol = solve(prob, Rodas5P()) sol = solve(prob, Tsit5()) ``` gives an erroneous success as it skips initialization, using `prob.u0 == [0.0]`, the sentinel value since initialization is only run on DAEs. We will need to override that so that initialization is always run on any system that provides an initializeprob. --- src/systems/diffeqs/abstractodesystem.jl | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/systems/diffeqs/abstractodesystem.jl b/src/systems/diffeqs/abstractodesystem.jl index 3ad6bff0ff..d3508f8642 100644 --- a/src/systems/diffeqs/abstractodesystem.jl +++ b/src/systems/diffeqs/abstractodesystem.jl @@ -862,6 +862,10 @@ function process_DEProblem(constructor, sys::AbstractODESystem, u0map, parammap; ps = full_parameters(sys) iv = get_iv(sys) + varmap = merge(defaults, todict(u0map)) + varlist = collect(map(unwrap, dvs)) + missingvars = setdiff(varlist, collect(keys(varmap))) + # Append zeros to the variables which are determined by the initialization system # This essentially bypasses the check for if initial conditions are defined for DAEs # since they will be checked in the initialization problem's construction @@ -869,7 +873,7 @@ function process_DEProblem(constructor, sys::AbstractODESystem, u0map, parammap; ci = infer_clocks!(ClockInference(TearingState(sys))) # TODO: make it work with clocks # ModelingToolkit.get_tearing_state(sys) !== nothing => Requires structural_simplify first - if (implicit_dae || calculate_massmatrix(sys) !== I) && + if (implicit_dae || !isempty(missingvars)) && all(isequal(Continuous()), ci.var_domain) && ModelingToolkit.get_tearing_state(sys) !== nothing if eltype(u0map) <: Number From e61a0f95200d99ed5628db244b2e0575aa5e62ca Mon Sep 17 00:00:00 2001 From: Christopher Rackauckas Date: Thu, 29 Feb 2024 06:50:23 -0600 Subject: [PATCH 02/27] use new ODE solver changes --- Project.toml | 2 +- src/systems/diffeqs/abstractodesystem.jl | 2 ++ test/initializationsystem.jl | 25 ++++++++++++++++++++++++ 3 files changed, 28 insertions(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index a72c4036b3..df7d6234e1 100644 --- a/Project.toml +++ b/Project.toml @@ -89,7 +89,7 @@ Libdl = "1" LinearAlgebra = "1" MLStyle = "0.4.17" NaNMath = "0.3, 1" -OrdinaryDiffEq = "6.72.0" +OrdinaryDiffEq = "6.73.0" PrecompileTools = "1" RecursiveArrayTools = "2.3, 3" Reexport = "0.2, 1" diff --git a/src/systems/diffeqs/abstractodesystem.jl b/src/systems/diffeqs/abstractodesystem.jl index d3508f8642..1eb34ad95c 100644 --- a/src/systems/diffeqs/abstractodesystem.jl +++ b/src/systems/diffeqs/abstractodesystem.jl @@ -862,6 +862,8 @@ function process_DEProblem(constructor, sys::AbstractODESystem, u0map, parammap; ps = full_parameters(sys) iv = get_iv(sys) + # TODO: Pass already computed information to varmap_to_vars call + # in process_u0? That would just be a small optimization varmap = merge(defaults, todict(u0map)) varlist = collect(map(unwrap, dvs)) missingvars = setdiff(varlist, collect(keys(varmap))) diff --git a/test/initializationsystem.jl b/test/initializationsystem.jl index 69032eeb3b..b4192bb757 100644 --- a/test/initializationsystem.jl +++ b/test/initializationsystem.jl @@ -332,3 +332,28 @@ p = [σ => 28.0, tspan = (0.0, 100.0) @test_throws ArgumentError prob=ODEProblem(sys, u0, tspan, p, jac = true) + +# DAE Initialization on ODE with nonlinear system for initial conditions +# https://github.com/SciML/ModelingToolkit.jl/issues/2508 + +using ModelingToolkit, OrdinaryDiffEq, Test +using ModelingToolkit: t_nounits as t, D_nounits as D + +function System(;name) + vars = @variables begin + dx(t), [guess=0] + ddx(t), [guess=0] + end + eqs = [ + D(dx) ~ ddx + 0 ~ ddx + dx + 1 + ] + return ODESystem(eqs, t, vars, []; name) +end + +@mtkbuild sys = System() +prob = ODEProblem(sys, [sys.dx => 1], (0,1)) # OK +prob = ODEProblem(sys, [sys.ddx => -2], (0,1), guesses = [sys.dx => 1]) +sol = solve(prob, Tsit5()) +@test SciMLBase.successful_retcode(sol) +@test sol[1] == [1.0] \ No newline at end of file From fbd3e1fdf80f54675224533a974af15682e80cd8 Mon Sep 17 00:00:00 2001 From: Christopher Rackauckas Date: Thu, 29 Feb 2024 07:16:28 -0600 Subject: [PATCH 03/27] format --- test/initializationsystem.jl | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/test/initializationsystem.jl b/test/initializationsystem.jl index b4192bb757..d2ebc86de8 100644 --- a/test/initializationsystem.jl +++ b/test/initializationsystem.jl @@ -339,21 +339,19 @@ tspan = (0.0, 100.0) using ModelingToolkit, OrdinaryDiffEq, Test using ModelingToolkit: t_nounits as t, D_nounits as D -function System(;name) +function System(; name) vars = @variables begin - dx(t), [guess=0] - ddx(t), [guess=0] + dx(t), [guess = 0] + ddx(t), [guess = 0] end - eqs = [ - D(dx) ~ ddx - 0 ~ ddx + dx + 1 - ] + eqs = [D(dx) ~ ddx + 0 ~ ddx + dx + 1] return ODESystem(eqs, t, vars, []; name) end @mtkbuild sys = System() -prob = ODEProblem(sys, [sys.dx => 1], (0,1)) # OK -prob = ODEProblem(sys, [sys.ddx => -2], (0,1), guesses = [sys.dx => 1]) +prob = ODEProblem(sys, [sys.dx => 1], (0, 1)) # OK +prob = ODEProblem(sys, [sys.ddx => -2], (0, 1), guesses = [sys.dx => 1]) sol = solve(prob, Tsit5()) @test SciMLBase.successful_retcode(sol) -@test sol[1] == [1.0] \ No newline at end of file +@test sol[1] == [1.0] From a905587258865a65964cfcca20d785f8388c035c Mon Sep 17 00:00:00 2001 From: Christopher Rackauckas Date: Thu, 29 Feb 2024 07:34:10 -0600 Subject: [PATCH 04/27] Handle empty u0map --- src/systems/diffeqs/abstractodesystem.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/systems/diffeqs/abstractodesystem.jl b/src/systems/diffeqs/abstractodesystem.jl index 1eb34ad95c..f0f05ebf15 100644 --- a/src/systems/diffeqs/abstractodesystem.jl +++ b/src/systems/diffeqs/abstractodesystem.jl @@ -864,7 +864,7 @@ function process_DEProblem(constructor, sys::AbstractODESystem, u0map, parammap; # TODO: Pass already computed information to varmap_to_vars call # in process_u0? That would just be a small optimization - varmap = merge(defaults, todict(u0map)) + varmap = isempty(u0map) ? defaults : merge(defaults, todict(u0map)) varlist = collect(map(unwrap, dvs)) missingvars = setdiff(varlist, collect(keys(varmap))) From 186302f199a1a2f8acef5383453c1dfd19468d88 Mon Sep 17 00:00:00 2001 From: Christopher Rackauckas Date: Thu, 29 Feb 2024 07:53:45 -0600 Subject: [PATCH 05/27] late binding initialization_eqs --- src/systems/abstractsystem.jl | 1 + src/systems/diffeqs/abstractodesystem.jl | 2 +- src/systems/diffeqs/odesystem.jl | 15 +++++++++++---- src/systems/nonlinear/initializesystem.jl | 2 +- test/initializationsystem.jl | 21 +++++++++++++++++++++ 5 files changed, 35 insertions(+), 6 deletions(-) diff --git a/src/systems/abstractsystem.jl b/src/systems/abstractsystem.jl index a62aa4bce8..e37cb874c2 100644 --- a/src/systems/abstractsystem.jl +++ b/src/systems/abstractsystem.jl @@ -576,6 +576,7 @@ for prop in [:eqs :preface :torn_matching :initializesystem + :initialization_eqs :schedule :tearing_state :substitutions diff --git a/src/systems/diffeqs/abstractodesystem.jl b/src/systems/diffeqs/abstractodesystem.jl index f0f05ebf15..6ef5cb5e09 100644 --- a/src/systems/diffeqs/abstractodesystem.jl +++ b/src/systems/diffeqs/abstractodesystem.jl @@ -864,7 +864,7 @@ function process_DEProblem(constructor, sys::AbstractODESystem, u0map, parammap; # TODO: Pass already computed information to varmap_to_vars call # in process_u0? That would just be a small optimization - varmap = isempty(u0map) ? defaults : merge(defaults, todict(u0map)) + varmap = isempty(u0map) ? defaults(sys) : merge(defaults(sys), todict(u0map)) varlist = collect(map(unwrap, dvs)) missingvars = setdiff(varlist, collect(keys(varmap))) diff --git a/src/systems/diffeqs/odesystem.jl b/src/systems/diffeqs/odesystem.jl index da973a6f46..526d83d48b 100644 --- a/src/systems/diffeqs/odesystem.jl +++ b/src/systems/diffeqs/odesystem.jl @@ -101,6 +101,10 @@ struct ODESystem <: AbstractODESystem """ 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 @@ -171,7 +175,8 @@ struct ODESystem <: AbstractODESystem function ODESystem(tag, deqs, iv, dvs, ps, tspan, var_to_name, ctrls, observed, tgrad, jac, ctrl_jac, Wfact, Wfact_t, name, systems, defaults, guesses, - torn_matching, initializesystem, schedule, connector_type, preface, cevents, + torn_matching, initializesystem, initialization_eqs, schedule, + connector_type, preface, cevents, devents, parameter_dependencies, metadata = nothing, gui_metadata = nothing, tearing_state = nothing, @@ -190,8 +195,8 @@ struct ODESystem <: AbstractODESystem end new(tag, deqs, iv, dvs, ps, tspan, var_to_name, ctrls, observed, tgrad, jac, ctrl_jac, Wfact, Wfact_t, name, systems, defaults, guesses, torn_matching, - initializesystem, schedule, connector_type, preface, cevents, devents, parameter_dependencies, - metadata, + initializesystem, initialization_eqs, schedule, connector_type, preface, + cevents, devents, parameter_dependencies, metadata, gui_metadata, tearing_state, substitutions, complete, index_cache, discrete_subsystems, solved_unknowns, split_idxs, parent) end @@ -208,6 +213,7 @@ function ODESystem(deqs::AbstractVector{<:Equation}, iv, dvs, ps; defaults = _merge(Dict(default_u0), Dict(default_p)), guesses = Dict(), initializesystem = nothing, + initialization_eqs = Equation[], schedule = nothing, connector_type = nothing, preface = nothing, @@ -260,7 +266,8 @@ function ODESystem(deqs::AbstractVector{<:Equation}, iv, dvs, ps; ODESystem(Threads.atomic_add!(SYSTEM_COUNT, UInt(1)), deqs, iv′, dvs′, ps′, tspan, var_to_name, ctrl′, observed, tgrad, jac, ctrl_jac, Wfact, Wfact_t, name, systems, defaults, guesses, nothing, initializesystem, - schedule, connector_type, preface, cont_callbacks, disc_callbacks, parameter_dependencies, + initialization_eqs, schedule, connector_type, preface, cont_callbacks, + disc_callbacks, parameter_dependencies, metadata, gui_metadata, checks = checks) end diff --git a/src/systems/nonlinear/initializesystem.jl b/src/systems/nonlinear/initializesystem.jl index 827d5ca2e1..67e0f316ef 100644 --- a/src/systems/nonlinear/initializesystem.jl +++ b/src/systems/nonlinear/initializesystem.jl @@ -55,7 +55,7 @@ function generate_initializesystem(sys::ODESystem; end pars = [parameters(sys); get_iv(sys)] - nleqs = [eqs_ics; observed(sys)] + nleqs = [eqs_ics; get_initialization_eqs(sys); observed(sys)] sys_nl = NonlinearSystem(nleqs, full_states, diff --git a/test/initializationsystem.jl b/test/initializationsystem.jl index d2ebc86de8..ee640686d1 100644 --- a/test/initializationsystem.jl +++ b/test/initializationsystem.jl @@ -355,3 +355,24 @@ prob = ODEProblem(sys, [sys.ddx => -2], (0, 1), guesses = [sys.dx => 1]) sol = solve(prob, Tsit5()) @test SciMLBase.successful_retcode(sol) @test sol[1] == [1.0] + +## Late binding initialization_eqs + +function System(; name) + vars = @variables begin + dx(t), [guess = 0] + ddx(t), [guess = 0] + end + eqs = [D(dx) ~ ddx + 0 ~ ddx + dx + 1] + initialization_eqs = [ + ddx ~ -2 + ] + return ODESystem(eqs, t, vars, []; name, initialization_eqs) +end + +@mtkbuild sys = System() +prob = ODEProblem(sys, [], (0, 1), guesses = [sys.dx => 1]) +sol = solve(prob, Tsit5()) +@test SciMLBase.successful_retcode(sol) +@test sol[1] == [1.0] From fad548f6132c99882e4c2c0fea9a8301b8947409 Mon Sep 17 00:00:00 2001 From: Christopher Rackauckas Date: Thu, 29 Feb 2024 08:21:15 -0600 Subject: [PATCH 06/27] make sure u0map isn't a vector of numbers --- src/systems/diffeqs/abstractodesystem.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/systems/diffeqs/abstractodesystem.jl b/src/systems/diffeqs/abstractodesystem.jl index 6ef5cb5e09..0aaa2b5269 100644 --- a/src/systems/diffeqs/abstractodesystem.jl +++ b/src/systems/diffeqs/abstractodesystem.jl @@ -864,7 +864,7 @@ function process_DEProblem(constructor, sys::AbstractODESystem, u0map, parammap; # TODO: Pass already computed information to varmap_to_vars call # in process_u0? That would just be a small optimization - varmap = isempty(u0map) ? defaults(sys) : merge(defaults(sys), todict(u0map)) + varmap = isempty(u0map) || eltype(u0map) <: Number ? defaults(sys) : merge(defaults(sys), todict(u0map)) varlist = collect(map(unwrap, dvs)) missingvars = setdiff(varlist, collect(keys(varmap))) From 82cb87c186a2f4d3dd44234c836fe815a9d2ae4c Mon Sep 17 00:00:00 2001 From: Christopher Rackauckas Date: Thu, 29 Feb 2024 08:36:04 -0600 Subject: [PATCH 07/27] format --- src/systems/diffeqs/abstractodesystem.jl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/systems/diffeqs/abstractodesystem.jl b/src/systems/diffeqs/abstractodesystem.jl index 0aaa2b5269..e2d5ab2300 100644 --- a/src/systems/diffeqs/abstractodesystem.jl +++ b/src/systems/diffeqs/abstractodesystem.jl @@ -864,7 +864,8 @@ function process_DEProblem(constructor, sys::AbstractODESystem, u0map, parammap; # TODO: Pass already computed information to varmap_to_vars call # in process_u0? That would just be a small optimization - varmap = isempty(u0map) || eltype(u0map) <: Number ? defaults(sys) : merge(defaults(sys), todict(u0map)) + varmap = isempty(u0map) || eltype(u0map) <: Number ? defaults(sys) : + merge(defaults(sys), todict(u0map)) varlist = collect(map(unwrap, dvs)) missingvars = setdiff(varlist, collect(keys(varmap))) From 7fb84b36b0bb4b62f3ff7377822dac0954a89b7c Mon Sep 17 00:00:00 2001 From: Christopher Rackauckas Date: Thu, 29 Feb 2024 10:07:53 -0600 Subject: [PATCH 08/27] fix a few tests --- src/systems/diffeqs/abstractodesystem.jl | 2 +- test/initializationsystem.jl | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/systems/diffeqs/abstractodesystem.jl b/src/systems/diffeqs/abstractodesystem.jl index e2d5ab2300..603ea0ff46 100644 --- a/src/systems/diffeqs/abstractodesystem.jl +++ b/src/systems/diffeqs/abstractodesystem.jl @@ -864,7 +864,7 @@ function process_DEProblem(constructor, sys::AbstractODESystem, u0map, parammap; # TODO: Pass already computed information to varmap_to_vars call # in process_u0? That would just be a small optimization - varmap = isempty(u0map) || eltype(u0map) <: Number ? defaults(sys) : + varmap = (u0map !== nothing && isempty(u0map)) || eltype(u0map) <: Number ? defaults(sys) : merge(defaults(sys), todict(u0map)) varlist = collect(map(unwrap, dvs)) missingvars = setdiff(varlist, collect(keys(varmap))) diff --git a/test/initializationsystem.jl b/test/initializationsystem.jl index ee640686d1..3df907ca98 100644 --- a/test/initializationsystem.jl +++ b/test/initializationsystem.jl @@ -358,7 +358,7 @@ sol = solve(prob, Tsit5()) ## Late binding initialization_eqs -function System(; name) +function System2(; name) vars = @variables begin dx(t), [guess = 0] ddx(t), [guess = 0] @@ -371,7 +371,7 @@ function System(; name) return ODESystem(eqs, t, vars, []; name, initialization_eqs) end -@mtkbuild sys = System() +@mtkbuild sys = System2() prob = ODEProblem(sys, [], (0, 1), guesses = [sys.dx => 1]) sol = solve(prob, Tsit5()) @test SciMLBase.successful_retcode(sol) From e7511d72065e5d49ce939dcced20482b9d655c3e Mon Sep 17 00:00:00 2001 From: Christopher Rackauckas Date: Thu, 29 Feb 2024 10:23:15 -0600 Subject: [PATCH 09/27] format --- src/systems/diffeqs/abstractodesystem.jl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/systems/diffeqs/abstractodesystem.jl b/src/systems/diffeqs/abstractodesystem.jl index 603ea0ff46..c7beca1258 100644 --- a/src/systems/diffeqs/abstractodesystem.jl +++ b/src/systems/diffeqs/abstractodesystem.jl @@ -864,7 +864,8 @@ function process_DEProblem(constructor, sys::AbstractODESystem, u0map, parammap; # TODO: Pass already computed information to varmap_to_vars call # in process_u0? That would just be a small optimization - varmap = (u0map !== nothing && isempty(u0map)) || eltype(u0map) <: Number ? defaults(sys) : + varmap = (u0map !== nothing && isempty(u0map)) || eltype(u0map) <: Number ? + defaults(sys) : merge(defaults(sys), todict(u0map)) varlist = collect(map(unwrap, dvs)) missingvars = setdiff(varlist, collect(keys(varmap))) From d08fefe1bc7b41b1c725d44aa4a0c36be8f331f1 Mon Sep 17 00:00:00 2001 From: Christopher Rackauckas Date: Thu, 29 Feb 2024 11:02:40 -0600 Subject: [PATCH 10/27] fix condition --- src/systems/diffeqs/abstractodesystem.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/systems/diffeqs/abstractodesystem.jl b/src/systems/diffeqs/abstractodesystem.jl index c7beca1258..13286ffd68 100644 --- a/src/systems/diffeqs/abstractodesystem.jl +++ b/src/systems/diffeqs/abstractodesystem.jl @@ -864,7 +864,7 @@ function process_DEProblem(constructor, sys::AbstractODESystem, u0map, parammap; # TODO: Pass already computed information to varmap_to_vars call # in process_u0? That would just be a small optimization - varmap = (u0map !== nothing && isempty(u0map)) || eltype(u0map) <: Number ? + varmap = u0map === nothing || isempty(u0map) || eltype(u0map) <: Number ? defaults(sys) : merge(defaults(sys), todict(u0map)) varlist = collect(map(unwrap, dvs)) From 59eae13c72fcb6eed701a88eaaeaf26328a37231 Mon Sep 17 00:00:00 2001 From: Christopher Rackauckas Date: Thu, 29 Feb 2024 11:26:21 -0600 Subject: [PATCH 11/27] don't initialize SDEs --- src/systems/diffeqs/abstractodesystem.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/systems/diffeqs/abstractodesystem.jl b/src/systems/diffeqs/abstractodesystem.jl index 13286ffd68..25503880d5 100644 --- a/src/systems/diffeqs/abstractodesystem.jl +++ b/src/systems/diffeqs/abstractodesystem.jl @@ -877,7 +877,7 @@ function process_DEProblem(constructor, sys::AbstractODESystem, u0map, parammap; ci = infer_clocks!(ClockInference(TearingState(sys))) # TODO: make it work with clocks # ModelingToolkit.get_tearing_state(sys) !== nothing => Requires structural_simplify first - if (implicit_dae || !isempty(missingvars)) && + if sys isa ODESystem && (implicit_dae || !isempty(missingvars)) && all(isequal(Continuous()), ci.var_domain) && ModelingToolkit.get_tearing_state(sys) !== nothing if eltype(u0map) <: Number From 3938441ae91baea629ace4fa64b8874fa9311005 Mon Sep 17 00:00:00 2001 From: Christopher Rackauckas Date: Thu, 29 Feb 2024 11:41:51 -0600 Subject: [PATCH 12/27] fix a typo from tests --- test/initializationsystem.jl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/initializationsystem.jl b/test/initializationsystem.jl index 3df907ca98..ada11d0169 100644 --- a/test/initializationsystem.jl +++ b/test/initializationsystem.jl @@ -339,7 +339,7 @@ tspan = (0.0, 100.0) using ModelingToolkit, OrdinaryDiffEq, Test using ModelingToolkit: t_nounits as t, D_nounits as D -function System(; name) +function System2(; name) vars = @variables begin dx(t), [guess = 0] ddx(t), [guess = 0] @@ -349,7 +349,7 @@ function System(; name) return ODESystem(eqs, t, vars, []; name) end -@mtkbuild sys = System() +@mtkbuild sys = System2() prob = ODEProblem(sys, [sys.dx => 1], (0, 1)) # OK prob = ODEProblem(sys, [sys.ddx => -2], (0, 1), guesses = [sys.dx => 1]) sol = solve(prob, Tsit5()) @@ -358,7 +358,7 @@ sol = solve(prob, Tsit5()) ## Late binding initialization_eqs -function System2(; name) +function System3(; name) vars = @variables begin dx(t), [guess = 0] ddx(t), [guess = 0] @@ -371,7 +371,7 @@ function System2(; name) return ODESystem(eqs, t, vars, []; name, initialization_eqs) end -@mtkbuild sys = System2() +@mtkbuild sys = System3() prob = ODEProblem(sys, [], (0, 1), guesses = [sys.dx => 1]) sol = solve(prob, Tsit5()) @test SciMLBase.successful_retcode(sol) From d20095a94bd070683796cbc97d59d3cd397bb044 Mon Sep 17 00:00:00 2001 From: Christopher Rackauckas Date: Thu, 29 Feb 2024 11:56:23 -0600 Subject: [PATCH 13/27] handle static arrays --- src/systems/diffeqs/abstractodesystem.jl | 1 + 1 file changed, 1 insertion(+) diff --git a/src/systems/diffeqs/abstractodesystem.jl b/src/systems/diffeqs/abstractodesystem.jl index 25503880d5..cabd81c5f1 100644 --- a/src/systems/diffeqs/abstractodesystem.jl +++ b/src/systems/diffeqs/abstractodesystem.jl @@ -889,6 +889,7 @@ function process_DEProblem(constructor, sys::AbstractODESystem, u0map, parammap; zerovars = setdiff(unknowns(sys), keys(defaults(sys))) .=> 0.0 trueinit = identity.([zerovars; u0map]) + u0map isa StaticArraysCore.StaticArray && (trueinit = SVector{length(trueinit)}(trueinit)) else initializeprob = nothing initializeprobmap = nothing From 752f9e30644fb5fe4c0bf4c32e13e4151a4e58c8 Mon Sep 17 00:00:00 2001 From: Christopher Rackauckas Date: Thu, 29 Feb 2024 13:47:04 -0600 Subject: [PATCH 14/27] format --- src/systems/diffeqs/abstractodesystem.jl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/systems/diffeqs/abstractodesystem.jl b/src/systems/diffeqs/abstractodesystem.jl index cabd81c5f1..b82a4b8651 100644 --- a/src/systems/diffeqs/abstractodesystem.jl +++ b/src/systems/diffeqs/abstractodesystem.jl @@ -889,7 +889,8 @@ function process_DEProblem(constructor, sys::AbstractODESystem, u0map, parammap; zerovars = setdiff(unknowns(sys), keys(defaults(sys))) .=> 0.0 trueinit = identity.([zerovars; u0map]) - u0map isa StaticArraysCore.StaticArray && (trueinit = SVector{length(trueinit)}(trueinit)) + u0map isa StaticArraysCore.StaticArray && + (trueinit = SVector{length(trueinit)}(trueinit)) else initializeprob = nothing initializeprobmap = nothing From 6d5b6dd47653629c08db82003f129049ac216233 Mon Sep 17 00:00:00 2001 From: Christopher Rackauckas Date: Thu, 29 Feb 2024 14:35:38 -0600 Subject: [PATCH 15/27] Fix up a few tests / examples --- examples/electrical_components.jl | 6 +++--- test/components.jl | 2 +- test/odesystem.jl | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/examples/electrical_components.jl b/examples/electrical_components.jl index a814d23733..9feafe42a6 100644 --- a/examples/electrical_components.jl +++ b/examples/electrical_components.jl @@ -3,7 +3,7 @@ using ModelingToolkit, OrdinaryDiffEq using ModelingToolkit: t_nounits as t, D_nounits as D @connector function Pin(; name) - sts = @variables v(t)=1.0 i(t)=1.0 [connect = Flow] + sts = @variables v(t) [guess=1.0] i(t) [guess=1.0,connect = Flow] ODESystem(Equation[], t, sts, []; name = name) end @@ -16,7 +16,7 @@ end @component function OnePort(; name) @named p = Pin() @named n = Pin() - sts = @variables v(t)=1.0 i(t)=1.0 + sts = @variables v(t) [guess=1.0] i(t) [guess=1.0] eqs = [v ~ p.v - n.v 0 ~ p.i + n.i i ~ p.i] @@ -64,7 +64,7 @@ end end @connector function HeatPort(; name) - @variables T(t)=293.15 Q_flow(t)=0.0 [connect = Flow] + @variables T(t) [guess=293.15] Q_flow(t) [guess=0.0, connect = Flow] ODESystem(Equation[], t, [T, Q_flow], [], name = name) end diff --git a/test/components.jl b/test/components.jl index 76f456887e..9be1a17650 100644 --- a/test/components.jl +++ b/test/components.jl @@ -157,7 +157,7 @@ sys = structural_simplify(ll_model) u0 = unknowns(sys) .=> 0 @test_nowarn ODEProblem( sys, [], (0, 10.0), guesses = u0, warn_initialize_determined = false) -prob = DAEProblem(sys, D.(unknowns(sys)) .=> 0, u0, (0, 0.5)) +prob = DAEProblem(sys, D.(unknowns(sys)) .=> 0, [], (0, 0.5), guesses = u0) sol = solve(prob, DFBDF()) @test sol.retcode == SciMLBase.ReturnCode.Success diff --git a/test/odesystem.jl b/test/odesystem.jl index efea02b628..08f595c694 100644 --- a/test/odesystem.jl +++ b/test/odesystem.jl @@ -520,12 +520,12 @@ using SymbolicUtils.Code using Symbolics: unwrap, wrap, @register_symbolic foo(a, ms::AbstractVector) = a + sum(ms) @register_symbolic foo(a, ms::AbstractVector) -@variables x(t) ms(t)[1:3] +@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) @mtkbuild outersys = compose(emptysys, sys) -prob = ODEProblem(outersys, [sys.x => 1.0, sys.ms => 1:3], (0, 1.0)) +prob = ODEProblem(outersys, [outersys.sys.x => 1.0; collect(outersys.sys.ms .=> 1:3)], (0, 1.0)) @test_nowarn solve(prob, Tsit5()) # array equations From 11d096fdc0b1fd6ed2721959369ba647cb4ddeb2 Mon Sep 17 00:00:00 2001 From: Christopher Rackauckas Date: Thu, 29 Feb 2024 14:37:00 -0600 Subject: [PATCH 16/27] format --- examples/electrical_components.jl | 6 +++--- test/odesystem.jl | 5 +++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/examples/electrical_components.jl b/examples/electrical_components.jl index 9feafe42a6..1f4f151c21 100644 --- a/examples/electrical_components.jl +++ b/examples/electrical_components.jl @@ -3,7 +3,7 @@ using ModelingToolkit, OrdinaryDiffEq using ModelingToolkit: t_nounits as t, D_nounits as D @connector function Pin(; name) - sts = @variables v(t) [guess=1.0] i(t) [guess=1.0,connect = Flow] + sts = @variables v(t) [guess = 1.0] i(t) [guess = 1.0, connect = Flow] ODESystem(Equation[], t, sts, []; name = name) end @@ -16,7 +16,7 @@ end @component function OnePort(; name) @named p = Pin() @named n = Pin() - sts = @variables v(t) [guess=1.0] i(t) [guess=1.0] + sts = @variables v(t) [guess = 1.0] i(t) [guess = 1.0] eqs = [v ~ p.v - n.v 0 ~ p.i + n.i i ~ p.i] @@ -64,7 +64,7 @@ end end @connector function HeatPort(; name) - @variables T(t) [guess=293.15] Q_flow(t) [guess=0.0, connect = Flow] + @variables T(t) [guess = 293.15] Q_flow(t) [guess = 0.0, connect = Flow] ODESystem(Equation[], t, [T, Q_flow], [], name = name) end diff --git a/test/odesystem.jl b/test/odesystem.jl index 08f595c694..708fbdd7f4 100644 --- a/test/odesystem.jl +++ b/test/odesystem.jl @@ -520,12 +520,13 @@ using SymbolicUtils.Code using Symbolics: unwrap, wrap, @register_symbolic foo(a, ms::AbstractVector) = a + sum(ms) @register_symbolic foo(a, ms::AbstractVector) -@variables x(t) ms(t)[1:3] +@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) @mtkbuild outersys = compose(emptysys, sys) -prob = ODEProblem(outersys, [outersys.sys.x => 1.0; collect(outersys.sys.ms .=> 1:3)], (0, 1.0)) +prob = ODEProblem( + outersys, [outersys.sys.x => 1.0; collect(outersys.sys.ms .=> 1:3)], (0, 1.0)) @test_nowarn solve(prob, Tsit5()) # array equations From 6542bba74fdaef1d492b8aeaff4a41622ef5e010 Mon Sep 17 00:00:00 2001 From: Christopher Rackauckas Date: Thu, 29 Feb 2024 15:21:38 -0600 Subject: [PATCH 17/27] Handle dummy derivative u0's and throw custom incomplete init error --- src/systems/diffeqs/abstractodesystem.jl | 20 ++++++++++++++++++++ src/systems/nonlinear/initializesystem.jl | 12 +++++++----- test/initializationsystem.jl | 2 +- 3 files changed, 28 insertions(+), 6 deletions(-) diff --git a/src/systems/diffeqs/abstractodesystem.jl b/src/systems/diffeqs/abstractodesystem.jl index b82a4b8651..0ff08edfea 100644 --- a/src/systems/diffeqs/abstractodesystem.jl +++ b/src/systems/diffeqs/abstractodesystem.jl @@ -1540,6 +1540,21 @@ function InitializationProblem{false}(sys::AbstractODESystem, 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 +end + +function Base.showerror(io::IO, e::IncompleteInitializationError) + println(io, INCOMPLETE_INITIALIZATION_MESSAGE) + println(io, e.uninit) +end + function InitializationProblem{iip, specialize}(sys::AbstractODESystem, t::Number, u0map = [], parammap = DiffEqBase.NullParameters(); @@ -1560,6 +1575,11 @@ function InitializationProblem{iip, specialize}(sys::AbstractODESystem, generate_initializesystem(sys; u0map); fully_determined = false) end + uninit = setdiff(unknowns(sys),[unknowns(isys); getfield.(observed(isys),:lhs)]) + if !isempty(uninit) + throw(IncompleteInitializationError(uninit)) + end + neqs = length(equations(isys)) nunknown = length(unknowns(isys)) diff --git a/src/systems/nonlinear/initializesystem.jl b/src/systems/nonlinear/initializesystem.jl index 67e0f316ef..d55af76a78 100644 --- a/src/systems/nonlinear/initializesystem.jl +++ b/src/systems/nonlinear/initializesystem.jl @@ -17,21 +17,23 @@ function generate_initializesystem(sys::ODESystem; # Start the equations list with algebraic equations eqs_ics = eqs[idxs_alge] u0 = Vector{Pair}(undef, 0) - defs = merge(defaults(sys), todict(u0map)) full_states = [sts; getfield.((observed(sys)), :lhs)] set_full_states = Set(full_states) guesses = todict(guesses) schedule = getfield(sys, :schedule) - - dd_guess = if schedule !== nothing + + if schedule !== nothing guessmap = [x[2] => get(guesses, x[1], default_dd_value) for x in schedule.dummy_sub] - Dict(filter(x -> !isnothing(x[1]), guessmap)) + dd_guess = Dict(filter(x -> !isnothing(x[1]), guessmap)) + filtered_u0 = todict([get(schedule.dummy_sub, x[1], x[1]) => x[2] for x in u0map]) else - Dict() + dd_guess = Dict() + filtered_u0 = u0map end + defs = merge(defaults(sys), filtered_u0) guesses = merge(get_guesses(sys), todict(guesses), dd_guess) for st in full_states diff --git a/test/initializationsystem.jl b/test/initializationsystem.jl index ada11d0169..baf3fdd8c9 100644 --- a/test/initializationsystem.jl +++ b/test/initializationsystem.jl @@ -331,7 +331,7 @@ p = [σ => 28.0, β => 8 / 3] tspan = (0.0, 100.0) -@test_throws ArgumentError prob=ODEProblem(sys, u0, tspan, p, jac = true) +@test_throws ModelingToolkit.IncompleteInitializationError prob=ODEProblem(sys, u0, tspan, p, jac = true) # DAE Initialization on ODE with nonlinear system for initial conditions # https://github.com/SciML/ModelingToolkit.jl/issues/2508 From 746386c4273c5070bc3ea7ef2782cf86e1b8f0d9 Mon Sep 17 00:00:00 2001 From: Christopher Rackauckas Date: Thu, 29 Feb 2024 15:22:15 -0600 Subject: [PATCH 18/27] format --- src/systems/diffeqs/abstractodesystem.jl | 6 +++--- src/systems/nonlinear/initializesystem.jl | 2 +- test/initializationsystem.jl | 3 ++- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/systems/diffeqs/abstractodesystem.jl b/src/systems/diffeqs/abstractodesystem.jl index 0ff08edfea..504424159f 100644 --- a/src/systems/diffeqs/abstractodesystem.jl +++ b/src/systems/diffeqs/abstractodesystem.jl @@ -1546,8 +1546,8 @@ const INCOMPLETE_INITIALIZATION_MESSAGE = """ variables: """ -struct IncompleteInitializationError <: Exception - uninit +struct IncompleteInitializationError <: Exception + uninit::Any end function Base.showerror(io::IO, e::IncompleteInitializationError) @@ -1575,7 +1575,7 @@ function InitializationProblem{iip, specialize}(sys::AbstractODESystem, generate_initializesystem(sys; u0map); fully_determined = false) end - uninit = setdiff(unknowns(sys),[unknowns(isys); getfield.(observed(isys),:lhs)]) + uninit = setdiff(unknowns(sys), [unknowns(isys); getfield.(observed(isys), :lhs)]) if !isempty(uninit) throw(IncompleteInitializationError(uninit)) end diff --git a/src/systems/nonlinear/initializesystem.jl b/src/systems/nonlinear/initializesystem.jl index d55af76a78..0c120daf3b 100644 --- a/src/systems/nonlinear/initializesystem.jl +++ b/src/systems/nonlinear/initializesystem.jl @@ -22,7 +22,7 @@ function generate_initializesystem(sys::ODESystem; set_full_states = Set(full_states) guesses = todict(guesses) schedule = getfield(sys, :schedule) - + if schedule !== nothing guessmap = [x[2] => get(guesses, x[1], default_dd_value) for x in schedule.dummy_sub] diff --git a/test/initializationsystem.jl b/test/initializationsystem.jl index baf3fdd8c9..b984017c0b 100644 --- a/test/initializationsystem.jl +++ b/test/initializationsystem.jl @@ -331,7 +331,8 @@ p = [σ => 28.0, β => 8 / 3] tspan = (0.0, 100.0) -@test_throws ModelingToolkit.IncompleteInitializationError prob=ODEProblem(sys, u0, tspan, p, jac = true) +@test_throws ModelingToolkit.IncompleteInitializationError prob=ODEProblem( + sys, u0, tspan, p, jac = true) # DAE Initialization on ODE with nonlinear system for initial conditions # https://github.com/SciML/ModelingToolkit.jl/issues/2508 From 7002310de2cf1dde25551b86c242d502569d0de2 Mon Sep 17 00:00:00 2001 From: Christopher Rackauckas Date: Thu, 29 Feb 2024 15:46:21 -0600 Subject: [PATCH 19/27] don't filter empty u0maps --- src/systems/nonlinear/initializesystem.jl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/systems/nonlinear/initializesystem.jl b/src/systems/nonlinear/initializesystem.jl index 0c120daf3b..72c7812cda 100644 --- a/src/systems/nonlinear/initializesystem.jl +++ b/src/systems/nonlinear/initializesystem.jl @@ -27,7 +27,8 @@ function generate_initializesystem(sys::ODESystem; guessmap = [x[2] => get(guesses, x[1], default_dd_value) for x in schedule.dummy_sub] dd_guess = Dict(filter(x -> !isnothing(x[1]), guessmap)) - filtered_u0 = todict([get(schedule.dummy_sub, x[1], x[1]) => x[2] for x in u0map]) + filtered_u0 = u0map === nothing || isempty(u0map) ? u0map : + todict([get(schedule.dummy_sub, x[1], x[1]) => x[2] for x in u0map]) else dd_guess = Dict() filtered_u0 = u0map From 4e5e723eef67127c1813e28499a79d4df589a4b3 Mon Sep 17 00:00:00 2001 From: Christopher Rackauckas Date: Thu, 29 Feb 2024 17:04:08 -0600 Subject: [PATCH 20/27] handle arrays --- src/systems/diffeqs/abstractodesystem.jl | 3 +++ src/systems/nonlinear/initializesystem.jl | 12 ++++++++++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/systems/diffeqs/abstractodesystem.jl b/src/systems/diffeqs/abstractodesystem.jl index 504424159f..37d8f8381e 100644 --- a/src/systems/diffeqs/abstractodesystem.jl +++ b/src/systems/diffeqs/abstractodesystem.jl @@ -1576,6 +1576,9 @@ function InitializationProblem{iip, specialize}(sys::AbstractODESystem, end uninit = setdiff(unknowns(sys), [unknowns(isys); getfield.(observed(isys), :lhs)]) + + # TODO: throw on uninitialized arrays + filter!(x -> x isa Symbolics.Arr, uninit) if !isempty(uninit) throw(IncompleteInitializationError(uninit)) end diff --git a/src/systems/nonlinear/initializesystem.jl b/src/systems/nonlinear/initializesystem.jl index 72c7812cda..9a79afc6c4 100644 --- a/src/systems/nonlinear/initializesystem.jl +++ b/src/systems/nonlinear/initializesystem.jl @@ -27,8 +27,16 @@ function generate_initializesystem(sys::ODESystem; guessmap = [x[2] => get(guesses, x[1], default_dd_value) for x in schedule.dummy_sub] dd_guess = Dict(filter(x -> !isnothing(x[1]), guessmap)) - filtered_u0 = u0map === nothing || isempty(u0map) ? u0map : - todict([get(schedule.dummy_sub, x[1], x[1]) => x[2] for x in u0map]) + if u0map === nothing || isempty(u0map) + filtered_u0 = u0map + else + # TODO: Don't scalarize arrays + filtered_u0 = map(u0map) do x + y = get(schedule.dummy_sub, x[1], x[1]) + y isa Symbolics.Arr ? collect(x[1]) .=> x[2] : x[1] => x[2] + end + filtered_u0 = todict(reduce(vcat, filtered_u0)) + end else dd_guess = Dict() filtered_u0 = u0map From 2147dc6bef4ad7d9b154a92c652720ffaa6a8bd9 Mon Sep 17 00:00:00 2001 From: Christopher Rackauckas Date: Thu, 29 Feb 2024 18:22:42 -0600 Subject: [PATCH 21/27] Handle the scalar u0map case --- src/systems/nonlinear/initializesystem.jl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/systems/nonlinear/initializesystem.jl b/src/systems/nonlinear/initializesystem.jl index 9a79afc6c4..34586b9f8b 100644 --- a/src/systems/nonlinear/initializesystem.jl +++ b/src/systems/nonlinear/initializesystem.jl @@ -35,7 +35,8 @@ function generate_initializesystem(sys::ODESystem; y = get(schedule.dummy_sub, x[1], x[1]) y isa Symbolics.Arr ? collect(x[1]) .=> x[2] : x[1] => x[2] end - filtered_u0 = todict(reduce(vcat, filtered_u0)) + filtered_u0 = reduce(vcat, filtered_u0) + filtered_u0 = filtered_u0 isa Pair ? todict([filtered_u0]) : todict(filtered_u0) end else dd_guess = Dict() From 674b8c44092afd4c40b0d7fff367a908fdba131e Mon Sep 17 00:00:00 2001 From: Christopher Rackauckas Date: Fri, 1 Mar 2024 00:33:41 -0600 Subject: [PATCH 22/27] fix filter --- src/systems/diffeqs/abstractodesystem.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/systems/diffeqs/abstractodesystem.jl b/src/systems/diffeqs/abstractodesystem.jl index 37d8f8381e..fce0d7d8bb 100644 --- a/src/systems/diffeqs/abstractodesystem.jl +++ b/src/systems/diffeqs/abstractodesystem.jl @@ -1578,7 +1578,7 @@ function InitializationProblem{iip, specialize}(sys::AbstractODESystem, uninit = setdiff(unknowns(sys), [unknowns(isys); getfield.(observed(isys), :lhs)]) # TODO: throw on uninitialized arrays - filter!(x -> x isa Symbolics.Arr, uninit) + filter!(x -> !(x isa Symbolics.Arr), uninit) if !isempty(uninit) throw(IncompleteInitializationError(uninit)) end From 1274908828692c5e4a830ae9ddce1ef0fc5731b9 Mon Sep 17 00:00:00 2001 From: Christopher Rackauckas Date: Fri, 1 Mar 2024 00:53:05 -0600 Subject: [PATCH 23/27] fix components test See https://github.com/SciML/ModelingToolkit.jl/issues/2515 --- test/components.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/components.jl b/test/components.jl index 9be1a17650..c58ac7b3d0 100644 --- a/test/components.jl +++ b/test/components.jl @@ -98,9 +98,9 @@ let @named rc_model2 = compose(_rc_model2, [resistor, resistor2, capacitor, source, ground]) sys2 = structural_simplify(rc_model2) - prob2 = ODEProblem(sys2, [], (0, 10.0), guesses = u0) + prob2 = ODEProblem(sys2, [source.p.i => 0.0], (0, 10.0), guesses = u0) sol2 = solve(prob2, Rosenbrock23()) - @test sol2[source.p.i] ≈ sol2[rc_model2.source.p.i] ≈ sol2[capacitor.i] + @test sol2[source.p.i] ≈ sol2[rc_model2.source.p.i] ≈ -sol2[capacitor.i] end # Outer/inner connections From 49d811981e4729925427e1442102225654f44593 Mon Sep 17 00:00:00 2001 From: Christopher Rackauckas Date: Fri, 1 Mar 2024 02:21:04 -0600 Subject: [PATCH 24/27] Handle steady state initializations --- src/systems/nonlinear/initializesystem.jl | 27 ++++++++++++--- src/variables.jl | 19 +++++++++-- test/initializationsystem.jl | 41 +++++++++++++++++++++++ test/serialization.jl | 8 ++--- 4 files changed, 85 insertions(+), 10 deletions(-) diff --git a/src/systems/nonlinear/initializesystem.jl b/src/systems/nonlinear/initializesystem.jl index 34586b9f8b..6352efcfc2 100644 --- a/src/systems/nonlinear/initializesystem.jl +++ b/src/systems/nonlinear/initializesystem.jl @@ -18,7 +18,10 @@ function generate_initializesystem(sys::ODESystem; eqs_ics = eqs[idxs_alge] u0 = Vector{Pair}(undef, 0) - full_states = [sts; getfield.((observed(sys)), :lhs)] + eqs_diff = eqs[idxs_diff] + diffmap = Dict(getfield.(eqs_diff,:lhs) .=> getfield.(eqs_diff,:rhs)) + + full_states = unique([sts; getfield.((observed(sys)), :lhs)]) set_full_states = Set(full_states) guesses = todict(guesses) schedule = getfield(sys, :schedule) @@ -30,10 +33,26 @@ function generate_initializesystem(sys::ODESystem; if u0map === nothing || isempty(u0map) filtered_u0 = u0map else - # TODO: Don't scalarize arrays - filtered_u0 = map(u0map) do x + filtered_u0 = [] + for x in u0map y = get(schedule.dummy_sub, x[1], x[1]) - y isa Symbolics.Arr ? collect(x[1]) .=> x[2] : x[1] => x[2] + y = get(diffmap, y, y) + if y isa Symbolics.Arr + _y = collect(y) + + # TODO: Don't scalarize arrays + for i in 1:length(_y) + push!(filtered_u0, _y[i] => x[2][i]) + end + elseif y isa ModelingToolkit.BasicSymbolic + # y is a derivative expression expanded + # add to the initialization equations + push!(eqs_ics, y ~ x[2]) + elseif y ∈ set_full_states + push!(filtered_u0, y => x[2]) + else + error("Unreachable. Open an issue") + end end filtered_u0 = reduce(vcat, filtered_u0) filtered_u0 = filtered_u0 isa Pair ? todict([filtered_u0]) : todict(filtered_u0) diff --git a/src/variables.jl b/src/variables.jl index 9a9dbe4364..e7f4441eac 100644 --- a/src/variables.jl +++ b/src/variables.jl @@ -170,8 +170,23 @@ function varmap_to_vars(varmap, varlist; defaults = Dict(), check = true, end end +const MISSING_VARIABLES_MESSAGE = """ + Initial condition underdefined. Some are missing from the variable map. + Please provide a default (`u0`), initialization equation, or guess + for the following variables: + """ + +struct MissingVariablesError <: Exception + vars::Any +end + +function Base.showerror(io::IO, e::MissingVariablesError) + println(io, MISSING_VARIABLES_MESSAGE) + println(io, e.vars) +end + function _varmap_to_vars(varmap::Dict, varlist; defaults = Dict(), check = false, - toterm = Symbolics.diff2term) + toterm = Symbolics.diff2term, initialization_phase = false) varmap = merge(defaults, varmap) # prefers the `varmap` varmap = Dict(toterm(value(k)) => value(varmap[k]) for k in keys(varmap)) # resolve symbolic parameter expressions @@ -180,7 +195,7 @@ function _varmap_to_vars(varmap::Dict, varlist; defaults = Dict(), check = false end missingvars = setdiff(varlist, collect(keys(varmap))) - check && (isempty(missingvars) || throw_missingvars(missingvars)) + check && (isempty(missingvars) || throw(MissingVariablesError(missingvars))) out = [varmap[var] for var in varlist] end diff --git a/test/initializationsystem.jl b/test/initializationsystem.jl index b984017c0b..cc7e4eaff1 100644 --- a/test/initializationsystem.jl +++ b/test/initializationsystem.jl @@ -377,3 +377,44 @@ prob = ODEProblem(sys, [], (0, 1), guesses = [sys.dx => 1]) sol = solve(prob, Tsit5()) @test SciMLBase.successful_retcode(sol) @test sol[1] == [1.0] + +# Steady state initialization + +@parameters σ ρ β +@variables x(t) y(t) z(t) + +eqs = [D(D(x)) ~ σ * (y - x), + D(y) ~ x * (ρ - z) - y, + D(z) ~ x * y - β * z] + +@named sys = ODESystem(eqs, t) +sys = structural_simplify(sys) + +u0 = [D(x) => 2.0, + x => 1.0, + D(y) => 0.0, + z => 0.0] + +p = [σ => 28.0, + ρ => 10.0, + β => 8 / 3] + +tspan = (0.0, 0.2) +prob_mtk = ODEProblem(sys, u0, tspan, p) +sol = solve(prob_mtk, Tsit5()) +@test sol[x * (ρ - z) - y][1] == 0.0 + +@variables x(t) y(t) z(t) +@parameters α=1.5 β=1.0 γ=3.0 δ=1.0 + +eqs = [D(x) ~ α * x - β * x * y + D(y) ~ -γ * y + δ * x * y + z ~ x + y] + +@named sys = ODESystem(eqs, t) +simpsys = structural_simplify(sys) +tspan = (0.0, 10.0) + +prob = ODEProblem(simpsys, [D(x) => 0.0, y => 0.0], tspan, guesses = [x => 0.0]) +sol = solve(prob, Tsit5()) +@test sol[1] == [0.0,0.0] \ No newline at end of file diff --git a/test/serialization.jl b/test/serialization.jl index 2bdef64123..a86f24400e 100644 --- a/test/serialization.jl +++ b/test/serialization.jl @@ -32,13 +32,13 @@ sys = include_string(@__MODULE__, str) # check answer ss = structural_simplify(rc_model) all_obs = [o.lhs for o in observed(ss)] -prob = ODEProblem(ss, [], (0, 0.1)) +prob = ODEProblem(ss, [capacitor.v => 0.0], (0, 0.1)) sol = solve(prob, ImplicitEuler()) ## Check ODESystem with Observables ---------- ss_exp = ModelingToolkit.toexpr(ss) ss_ = complete(eval(ss_exp)) -prob_ = ODEProblem(ss_, [], (0, 0.1)) +prob_ = ODEProblem(ss_, [capacitor.v => 0.0], (0, 0.1)) sol_ = solve(prob_, ImplicitEuler()) @test sol[all_obs] == sol_[all_obs] @@ -61,8 +61,8 @@ observedfun_exp = :(function (var, u0, p, t) end) # ODEProblemExpr with observedfun_exp included -probexpr = ODEProblemExpr{true}(ss, [], (0, 0.1); observedfun_exp); +probexpr = ODEProblemExpr{true}(ss, [capacitor.v => 0.0], (0, 0.1); observedfun_exp); prob_obs = eval(probexpr) sol_obs = solve(prob_obs, ImplicitEuler()) @show all_obs -@test sol_obs[all_obs] == sol[all_obs] +@test_broken sol_obs[all_obs] == sol[all_obs] From 96bfc35beb26ff6ae4049b6e79b9cc6543da4824 Mon Sep 17 00:00:00 2001 From: Christopher Rackauckas Date: Fri, 1 Mar 2024 02:21:44 -0600 Subject: [PATCH 25/27] format --- src/systems/nonlinear/initializesystem.jl | 2 +- test/initializationsystem.jl | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/systems/nonlinear/initializesystem.jl b/src/systems/nonlinear/initializesystem.jl index 6352efcfc2..360f64bd4d 100644 --- a/src/systems/nonlinear/initializesystem.jl +++ b/src/systems/nonlinear/initializesystem.jl @@ -19,7 +19,7 @@ function generate_initializesystem(sys::ODESystem; u0 = Vector{Pair}(undef, 0) eqs_diff = eqs[idxs_diff] - diffmap = Dict(getfield.(eqs_diff,:lhs) .=> getfield.(eqs_diff,:rhs)) + diffmap = Dict(getfield.(eqs_diff, :lhs) .=> getfield.(eqs_diff, :rhs)) full_states = unique([sts; getfield.((observed(sys)), :lhs)]) set_full_states = Set(full_states) diff --git a/test/initializationsystem.jl b/test/initializationsystem.jl index cc7e4eaff1..7fe581a193 100644 --- a/test/initializationsystem.jl +++ b/test/initializationsystem.jl @@ -391,9 +391,9 @@ eqs = [D(D(x)) ~ σ * (y - x), sys = structural_simplify(sys) u0 = [D(x) => 2.0, - x => 1.0, - D(y) => 0.0, - z => 0.0] + x => 1.0, + D(y) => 0.0, + z => 0.0] p = [σ => 28.0, ρ => 10.0, @@ -417,4 +417,4 @@ tspan = (0.0, 10.0) prob = ODEProblem(simpsys, [D(x) => 0.0, y => 0.0], tspan, guesses = [x => 0.0]) sol = solve(prob, Tsit5()) -@test sol[1] == [0.0,0.0] \ No newline at end of file +@test sol[1] == [0.0, 0.0] From 8f2c78054b577202fb92809159c421ebee454f3c Mon Sep 17 00:00:00 2001 From: Christopher Rackauckas Date: Fri, 1 Mar 2024 02:50:32 -0600 Subject: [PATCH 26/27] Fix some odd test choices --- src/systems/nonlinear/initializesystem.jl | 5 ++--- test/odesystem.jl | 3 ++- test/structural_transformation/index_reduction.jl | 4 ---- 3 files changed, 4 insertions(+), 8 deletions(-) diff --git a/src/systems/nonlinear/initializesystem.jl b/src/systems/nonlinear/initializesystem.jl index 360f64bd4d..e05456be6a 100644 --- a/src/systems/nonlinear/initializesystem.jl +++ b/src/systems/nonlinear/initializesystem.jl @@ -33,7 +33,7 @@ function generate_initializesystem(sys::ODESystem; if u0map === nothing || isempty(u0map) filtered_u0 = u0map else - filtered_u0 = [] + filtered_u0 = Pair[] for x in u0map y = get(schedule.dummy_sub, x[1], x[1]) y = get(diffmap, y, y) @@ -51,10 +51,9 @@ function generate_initializesystem(sys::ODESystem; elseif y ∈ set_full_states push!(filtered_u0, y => x[2]) else - error("Unreachable. Open an issue") + error("Initialization expression $y is currently not supported. If its a higher order derivative expression, then only the dummy derivative expressions are supported.") end end - filtered_u0 = reduce(vcat, filtered_u0) filtered_u0 = filtered_u0 isa Pair ? todict([filtered_u0]) : todict(filtered_u0) end else diff --git a/test/odesystem.jl b/test/odesystem.jl index 708fbdd7f4..628b2ec3ea 100644 --- a/test/odesystem.jl +++ b/test/odesystem.jl @@ -671,7 +671,8 @@ let @test isapprox(sol[x[1]][end], 2, atol = 1e-3) # no initial conditions for D(x[1]) and D(x[2]) provided - @test_throws ArgumentError prob=DAEProblem(sys, Pair[], Pair[], (0, 50)) + @test_throws ModelingToolkit.MissingVariablesError prob=DAEProblem( + sys, Pair[], Pair[], (0, 50)) prob = ODEProblem(sys, Pair[x[1] => 0], (0, 50)) sol = solve(prob, Rosenbrock23()) diff --git a/test/structural_transformation/index_reduction.jl b/test/structural_transformation/index_reduction.jl index 6d7c153775..e32e04bc58 100644 --- a/test/structural_transformation/index_reduction.jl +++ b/test/structural_transformation/index_reduction.jl @@ -138,10 +138,6 @@ let sys = structural_simplify(pendulum2) @test length(unknowns(sys)) == 5 u0 = [ - D(x) => 0.0, - D(D(x)) => 0.0, - D(y) => 0.0, - D(D(y)) => 0.0, x => sqrt(2) / 2, y => sqrt(2) / 2 ] From a477f4313c469331330235199580a5e242c11f72 Mon Sep 17 00:00:00 2001 From: Christopher Rackauckas Date: Fri, 1 Mar 2024 03:50:08 -0600 Subject: [PATCH 27/27] fix a few last tests --- test/input_output_handling.jl | 2 +- test/state_selection.jl | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/test/input_output_handling.jl b/test/input_output_handling.jl index 7ad7c1d384..00e3b4006f 100644 --- a/test/input_output_handling.jl +++ b/test/input_output_handling.jl @@ -131,7 +131,7 @@ model = ODESystem(eqs, t; systems = [torque, inertia1, inertia2, spring, damper] name = :name) model_outputs = [inertia1.w, inertia2.w, inertia1.phi, inertia2.phi] model_inputs = [torque.tau.u] -matrices, ssys = linearize(model, model_inputs, model_outputs) +matrices, ssys = linearize(model, model_inputs, model_outputs); @test length(ModelingToolkit.outputs(ssys)) == 4 if VERSION >= v"1.8" # :opaque_closure not supported before diff --git a/test/state_selection.jl b/test/state_selection.jl index c847955a85..b8404d1f26 100644 --- a/test/state_selection.jl +++ b/test/state_selection.jl @@ -120,12 +120,12 @@ let @unpack supply_pipe, return_pipe = system sys = structural_simplify(system) u0 = [ - system.supply_pipe.v => 0.1, system.return_pipe.v => 0.1, D(supply_pipe.v) => 0.0, + sys.supply_pipe.v => 0.1, sys.return_pipe.v => 0.1, D(supply_pipe.v) => 0.0, D(return_pipe.fluid_port_a.m) => 0.0, D(supply_pipe.fluid_port_a.m) => 0.0] prob1 = ODEProblem(sys, [], (0.0, 10.0), [], guesses = u0) prob2 = ODEProblem(sys, [], (0.0, 10.0), [], guesses = u0) - prob3 = DAEProblem(sys, D.(unknowns(sys)) .=> 0.0, u0, (0.0, 10.0), []) + prob3 = DAEProblem(sys, D.(unknowns(sys)) .=> 0.0, [], (0.0, 10.0), guesses = u0) @test solve(prob1, FBDF()).retcode == ReturnCode.Success #@test solve(prob2, FBDF()).retcode == ReturnCode.Success @test solve(prob3, DFBDF()).retcode == ReturnCode.Success