From 2ddc6e83010e6beb1c0846d9e410b19f1028b447 Mon Sep 17 00:00:00 2001 From: Mason Protter Date: Sat, 23 Mar 2024 13:43:36 +0100 Subject: [PATCH 001/316] document and export continuous_events and discrete_events --- src/ModelingToolkit.jl | 2 +- src/systems/callbacks.jl | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/ModelingToolkit.jl b/src/ModelingToolkit.jl index 979021e2f0..22b0442b83 100644 --- a/src/ModelingToolkit.jl +++ b/src/ModelingToolkit.jl @@ -35,7 +35,7 @@ using PrecompileTools, Reexport using RecursiveArrayTools using SymbolicIndexingInterface - export independent_variables, unknowns, parameters, full_parameters + export independent_variables, unknowns, parameters, full_parameters, continuous_events, discrete_events import SymbolicUtils import SymbolicUtils: istree, arguments, operation, similarterm, promote_symtype, Symbolic, isadd, ismul, ispow, issym, FnType, diff --git a/src/systems/callbacks.jl b/src/systems/callbacks.jl index 15d09a9f3d..64cb3be24b 100644 --- a/src/systems/callbacks.jl +++ b/src/systems/callbacks.jl @@ -138,6 +138,11 @@ function namespace_callback(cb::SymbolicContinuousCallback, s)::SymbolicContinuo namespace_affects(affects(cb), s)) end +""" + continuous_events(sys::AbstractSystem) + +Returns a vector of all the `continuous_events` in an abstract system and its component subsystems. +""" function continuous_events(sys::AbstractSystem) obs = get_continuous_events(sys) filter(!isempty, obs) @@ -234,6 +239,11 @@ SymbolicDiscreteCallbacks(cb::SymbolicDiscreteCallback) = [cb] SymbolicDiscreteCallbacks(cbs::Vector{<:SymbolicDiscreteCallback}) = cbs SymbolicDiscreteCallbacks(::Nothing) = SymbolicDiscreteCallback[] +""" + discrete_events(sys::AbstractSystem) + +Returns a vector of all the `discrete_events` in an abstract system and its component subsystems. +""" function discrete_events(sys::AbstractSystem) obs = get_discrete_events(sys) systems = get_systems(sys) From 2ee9c23aae0e4f541d96e7388a3e464247761e5f Mon Sep 17 00:00:00 2001 From: Mason Protter Date: Sun, 24 Mar 2024 12:25:05 +0100 Subject: [PATCH 002/316] mention `continuous_events` and `disrete_events` in the documentaion --- docs/src/basics/Events.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/src/basics/Events.md b/docs/src/basics/Events.md index 33ce84d31e..4023d2d893 100644 --- a/docs/src/basics/Events.md +++ b/docs/src/basics/Events.md @@ -58,6 +58,10 @@ be handled via more [general functional affects](@ref func_affects). Finally, multiple events can be encoded via a `Vector{Pair{Vector{Equation}, Vector{Equation}}}`. +Given an `AbstractSystem`, one can fetch its continuous events, and the continuous events of any +subsystem inside of it using the `continuous_events(::AbstractSystem)` method, returning a vector +of `ModelingToolkit.SymbolicContinuousCallback` objects. + ### Example: Friction The system below illustrates how continuous events can be used to model Coulomb @@ -221,6 +225,10 @@ equations can either all change unknowns (i.e. variables) or all change parameters, but one cannot currently mix unknown variable and parameter changes within one individual event. +Given an `AbstractSystem`, one can fetch its discrete events, and the discrete events of any +subsystem inside of it using the `discrete_events(::AbstractSystem)` method, returning a vector +of `ModelingToolkit.SymbolicDiscreteCallback` objects. + ### Example: Injecting cells into a population Suppose we have a population of `N(t)` cells that can grow and die, and at time From 838a53bc66cba1eec315a08621824db32b929271 Mon Sep 17 00:00:00 2001 From: Mason Protter Date: Sun, 24 Mar 2024 12:39:32 +0100 Subject: [PATCH 003/316] mention events in `System` docs --- docs/src/systems/JumpSystem.md | 1 + docs/src/systems/ODESystem.md | 2 ++ docs/src/systems/SDESystem.md | 2 ++ 3 files changed, 5 insertions(+) diff --git a/docs/src/systems/JumpSystem.md b/docs/src/systems/JumpSystem.md index a83f741eb9..5bd0d50602 100644 --- a/docs/src/systems/JumpSystem.md +++ b/docs/src/systems/JumpSystem.md @@ -12,6 +12,7 @@ JumpSystem - `get_unknowns(sys)` or `unknowns(sys)`: The set of unknowns in the jump system. - `get_ps(sys)` or `parameters(sys)`: The parameters of the jump system. - `get_iv(sys)`: The independent variable of the jump system. + - `discrete_events(sys)`: The set of discrete events in the jump system. ## Transformations diff --git a/docs/src/systems/ODESystem.md b/docs/src/systems/ODESystem.md index 241b68b2b6..81a8f6f07c 100644 --- a/docs/src/systems/ODESystem.md +++ b/docs/src/systems/ODESystem.md @@ -13,6 +13,8 @@ ODESystem - `get_ps(sys)` or `parameters(sys)`: The parameters of the ODE. - `get_iv(sys)`: The independent variable of the ODE. - `get_u0_p(sys, u0map, parammap)` Numeric arrays for the initial condition and parameters given `var => value` maps. + - `continuous_events(sys)`: The set of continuous events in the ODE + - `discrete_events(sys)`: The set of discrete events in the ODE ## Transformations diff --git a/docs/src/systems/SDESystem.md b/docs/src/systems/SDESystem.md index 455f79689e..ad4a0fb59a 100644 --- a/docs/src/systems/SDESystem.md +++ b/docs/src/systems/SDESystem.md @@ -19,6 +19,8 @@ sde = SDESystem(ode, noiseeqs) - `get_unknowns(sys)` or `unknowns(sys)`: The set of unknowns in the SDE. - `get_ps(sys)` or `parameters(sys)`: The parameters of the SDE. - `get_iv(sys)`: The independent variable of the SDE. + - `continuous_events(sys)`: The set of continuous events in the SDE + - `discrete_events(sys)`: The set of discrete events in the SDE ## Transformations From fdcad918ea9e73a1f2887254294ba226454e7f92 Mon Sep 17 00:00:00 2001 From: Mason Protter Date: Mon, 25 Mar 2024 15:12:50 +0100 Subject: [PATCH 004/316] undo addition to `Events` docs --- docs/src/basics/Events.md | 8 -------- 1 file changed, 8 deletions(-) diff --git a/docs/src/basics/Events.md b/docs/src/basics/Events.md index 4023d2d893..33ce84d31e 100644 --- a/docs/src/basics/Events.md +++ b/docs/src/basics/Events.md @@ -58,10 +58,6 @@ be handled via more [general functional affects](@ref func_affects). Finally, multiple events can be encoded via a `Vector{Pair{Vector{Equation}, Vector{Equation}}}`. -Given an `AbstractSystem`, one can fetch its continuous events, and the continuous events of any -subsystem inside of it using the `continuous_events(::AbstractSystem)` method, returning a vector -of `ModelingToolkit.SymbolicContinuousCallback` objects. - ### Example: Friction The system below illustrates how continuous events can be used to model Coulomb @@ -225,10 +221,6 @@ equations can either all change unknowns (i.e. variables) or all change parameters, but one cannot currently mix unknown variable and parameter changes within one individual event. -Given an `AbstractSystem`, one can fetch its discrete events, and the discrete events of any -subsystem inside of it using the `discrete_events(::AbstractSystem)` method, returning a vector -of `ModelingToolkit.SymbolicDiscreteCallback` objects. - ### Example: Injecting cells into a population Suppose we have a population of `N(t)` cells that can grow and die, and at time From 7c2c81ed735c949a4c9c3849c56485fc918367e5 Mon Sep 17 00:00:00 2001 From: Mason Protter Date: Wed, 27 Mar 2024 13:35:59 +0100 Subject: [PATCH 005/316] fix formatting, expand docstring --- src/ModelingToolkit.jl | 3 ++- src/systems/callbacks.jl | 14 ++++++++++---- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/src/ModelingToolkit.jl b/src/ModelingToolkit.jl index 22b0442b83..dd46de31b7 100644 --- a/src/ModelingToolkit.jl +++ b/src/ModelingToolkit.jl @@ -35,7 +35,8 @@ using PrecompileTools, Reexport using RecursiveArrayTools using SymbolicIndexingInterface - export independent_variables, unknowns, parameters, full_parameters, continuous_events, discrete_events + export independent_variables, unknowns, parameters, full_parameters, continuous_events, + discrete_events import SymbolicUtils import SymbolicUtils: istree, arguments, operation, similarterm, promote_symtype, Symbolic, isadd, ismul, ispow, issym, FnType, diff --git a/src/systems/callbacks.jl b/src/systems/callbacks.jl index 64cb3be24b..db4fdbbdcf 100644 --- a/src/systems/callbacks.jl +++ b/src/systems/callbacks.jl @@ -139,9 +139,12 @@ function namespace_callback(cb::SymbolicContinuousCallback, s)::SymbolicContinuo end """ - continuous_events(sys::AbstractSystem) + continuous_events(sys::AbstractSystem)::Vector{SymbolicContinuousCallback} -Returns a vector of all the `continuous_events` in an abstract system and its component subsystems. +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) obs = get_continuous_events(sys) @@ -240,9 +243,12 @@ SymbolicDiscreteCallbacks(cbs::Vector{<:SymbolicDiscreteCallback}) = cbs SymbolicDiscreteCallbacks(::Nothing) = SymbolicDiscreteCallback[] """ - discrete_events(sys::AbstractSystem) + discrete_events(sys::AbstractSystem) :: Vector{SymbolicDiscreteCallback} -Returns a vector of all the `discrete_events` in an abstract system and its component subsystems. +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) obs = get_discrete_events(sys) From e476efa9bf776593896a35d0bf26deeb8a58e68a Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Mon, 25 Mar 2024 18:45:28 +0530 Subject: [PATCH 006/316] feat: implement SII.remake_buffer for MTKParameters --- Project.toml | 2 +- src/systems/parameter_buffer.jl | 40 +++++++++++++++++++++++++++++++++ test/mtkparameters.jl | 31 +++++++++++++++++++++++++ 3 files changed, 72 insertions(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index b6fc8d58bc..b613d85f4d 100644 --- a/Project.toml +++ b/Project.toml @@ -102,7 +102,7 @@ SimpleNonlinearSolve = "0.1.0, 1" SparseArrays = "1" SpecialFunctions = "0.7, 0.8, 0.9, 0.10, 1.0, 2" StaticArrays = "0.10, 0.11, 0.12, 1.0" -SymbolicIndexingInterface = "0.3.11" +SymbolicIndexingInterface = "0.3.12" SymbolicUtils = "1.0" Symbolics = "5.26" URIs = "1" diff --git a/src/systems/parameter_buffer.jl b/src/systems/parameter_buffer.jl index 7a37fac5c6..b511a5a764 100644 --- a/src/systems/parameter_buffer.jl +++ b/src/systems/parameter_buffer.jl @@ -318,6 +318,46 @@ function _set_parameter_unchecked!( p.dependent_update_iip(ArrayPartition(p.dependent), p...) end +function SymbolicIndexingInterface.remake_buffer(sys, oldbuf::MTKParameters, vals::Dict) + buftypes = Dict{Tuple{Any, Int}, Any}() + for (p, val) in vals + (idx = parameter_index(sys, p)) isa ParameterIndex || continue + k = (idx.portion, idx.idx[1]) + buftypes[k] = Union{get(buftypes, k, Union{}), typeof(val)} + end + + newbufs = [] + for (portion, old) in [(SciMLStructures.Tunable(), oldbuf.tunable) + (SciMLStructures.Discrete(), oldbuf.discrete) + (SciMLStructures.Constants(), oldbuf.constant) + (NONNUMERIC_PORTION, oldbuf.nonnumeric)] + if isempty(old) + push!(newbufs, old) + continue + end + new = Any[copy(i) for i in old] + for i in eachindex(new) + buftype = get(buftypes, (portion, i), eltype(new[i])) + new[i] = similar(new[i], buftype) + end + push!(newbufs, Tuple(new)) + end + tmpbuf = MTKParameters( + newbufs[1], newbufs[2], newbufs[3], oldbuf.dependent, newbufs[4], nothing, nothing) + for (p, val) in vals + _set_parameter_unchecked!( + tmpbuf, val, parameter_index(sys, p); update_dependent = false) + end + if oldbuf.dependent_update_oop !== nothing + dependent = oldbuf.dependent_update_oop(tmpbuf...) + else + dependent = () + end + newbuf = MTKParameters(newbufs[1], newbufs[2], newbufs[3], dependent, newbufs[4], + oldbuf.dependent_update_iip, oldbuf.dependent_update_oop) + return newbuf +end + _subarrays(v::AbstractVector) = isempty(v) ? () : (v,) _subarrays(v::ArrayPartition) = v.x _subarrays(v::Tuple) = v diff --git a/test/mtkparameters.jl b/test/mtkparameters.jl index d669b4cdac..688ccba84a 100644 --- a/test/mtkparameters.jl +++ b/test/mtkparameters.jl @@ -2,6 +2,7 @@ using ModelingToolkit using ModelingToolkit: t_nounits as t, D_nounits as D, MTKParameters using SymbolicIndexingInterface using SciMLStructures: SciMLStructures, canonicalize, Tunable, Discrete, Constants +using ForwardDiff @parameters a b c d::Integer e[1:3] f[1:3, 1:3]::Int g::Vector{AbstractFloat} h::String @named sys = ODESystem( @@ -70,3 +71,33 @@ setp(sys, f[2, 2])(ps, 42) # with a sub-index setp(sys, h)(ps, "bar") # with a non-numeric @test getp(sys, h)(ps) == "bar" + +newps = remake_buffer(sys, + ps, + Dict(a => 1.0f0, b => 5.0f0, c => 2.0, d => 0x5, e => [0.4, 0.5, 0.6], + f => 3ones(UInt, 3, 3), g => ones(Float32, 4), h => "bar")) + +for fname in (:tunable, :discrete, :constant, :dependent) + # ensure same number of sub-buffers + @test length(getfield(ps, fname)) == length(getfield(newps, fname)) +end +@test ps.dependent_update_iip === newps.dependent_update_iip +@test ps.dependent_update_oop === newps.dependent_update_oop + +@test getp(sys, a)(newps) isa Float32 +@test getp(sys, b)(newps) == 2.0f0 # ensure dependent update still happened, despite explicit value +@test getp(sys, c)(newps) isa Float64 +@test getp(sys, d)(newps) isa UInt8 +@test getp(sys, f)(newps) isa Matrix{UInt} +# SII bug +@test_broken getp(sys, g)(newps) isa Vector{Float32} + +ps = MTKParameters(sys, ivs) +function loss(value, sys, ps) + @test value isa ForwardDiff.Dual + vals = merge(Dict(parameters(sys) .=> getp(sys, parameters(sys))(ps)), Dict(a => value)) + ps = remake_buffer(sys, ps, vals) + getp(sys, a)(ps) + getp(sys, b)(ps) +end + +@test ForwardDiff.derivative(x -> loss(x, sys, ps), 1.5) == 3.0 From 43b50717c76c48b76ca8d02842d7adf6f98c93c4 Mon Sep 17 00:00:00 2001 From: Christopher Rackauckas Date: Thu, 28 Mar 2024 06:14:57 -0400 Subject: [PATCH 007/316] Update Project.toml --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index b613d85f4d..200a3b896d 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.8.0" +version = "9.9.0" [deps] AbstractTrees = "1520ce14-60c1-5f80-bbc7-55ef81b5835c" From 9ac0d0ff3355cbae8d65ef303105f47ab2077895 Mon Sep 17 00:00:00 2001 From: Mason Protter Date: Thu, 28 Mar 2024 18:59:12 +0100 Subject: [PATCH 008/316] fix _varmap_to_vars, re-enable dde.jl test (#2545) fix useage of `h`. update project re-trigger CI clean whitespace don't store self in `values` use `@mtkbuild` --- Project.toml | 3 ++- src/systems/diffeqs/abstractodesystem.jl | 3 ++- src/systems/parameter_buffer.jl | 2 +- src/variables.jl | 2 +- test/dde.jl | 17 +++++++---------- test/runtests.jl | 1 + 6 files changed, 14 insertions(+), 14 deletions(-) diff --git a/Project.toml b/Project.toml index 200a3b896d..b2992259e8 100644 --- a/Project.toml +++ b/Project.toml @@ -115,6 +115,7 @@ AmplNLWriter = "7c4d4715-977e-5154-bfe0-e096adeac482" BenchmarkTools = "6e4b80f9-dd63-53aa-95a3-0cdb28fa8baf" ControlSystemsBase = "aaaaaaaa-a6ca-5380-bf3e-84a91bcd477e" DeepDiffs = "ab62b9b5-e342-54a8-a765-a90f495de1a6" +DelayDiffEq = "bcd4f6db-9728-5f36-b5f7-82caef46ccdb" ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210" Ipopt = "b6b21f68-93f8-5de0-b562-5493be1d77c9" Ipopt_jll = "9cc047cb-c261-5740-88fc-0cf96f7bdcc7" @@ -136,4 +137,4 @@ Sundials = "c3572dad-4567-51f8-b174-8c6c989267f4" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" [targets] -test = ["AmplNLWriter", "BenchmarkTools", "ControlSystemsBase", "NonlinearSolve", "ForwardDiff", "Ipopt", "Ipopt_jll", "ModelingToolkitStandardLibrary", "Optimization", "OptimizationOptimJL", "OptimizationMOI", "Random", "ReferenceTests", "SafeTestsets", "StableRNGs", "Statistics", "SteadyStateDiffEq", "Test", "StochasticDiffEq", "Sundials", "StochasticDelayDiffEq", "Pkg"] +test = ["AmplNLWriter", "BenchmarkTools", "ControlSystemsBase", "DelayDiffEq", "NonlinearSolve", "ForwardDiff", "Ipopt", "Ipopt_jll", "ModelingToolkitStandardLibrary", "Optimization", "OptimizationOptimJL", "OptimizationMOI", "Random", "ReferenceTests", "SafeTestsets", "StableRNGs", "Statistics", "SteadyStateDiffEq", "Test", "StochasticDiffEq", "Sundials", "StochasticDelayDiffEq", "Pkg"] diff --git a/src/systems/diffeqs/abstractodesystem.jl b/src/systems/diffeqs/abstractodesystem.jl index a1fc4ee496..1feffb42be 100644 --- a/src/systems/diffeqs/abstractodesystem.jl +++ b/src/systems/diffeqs/abstractodesystem.jl @@ -957,7 +957,6 @@ function process_DEProblem(constructor, sys::AbstractODESystem, u0map, parammap; ddvs = nothing end check_eqs_u0(eqs, dvs, u0; kwargs...) - f = constructor(sys, dvs, ps, u0; ddvs = ddvs, tgrad = tgrad, jac = jac, checkbounds = checkbounds, p = p, linenumbers = linenumbers, parallel = parallel, simplify = simplify, @@ -1243,6 +1242,8 @@ function DiffEqBase.SDDEProblem{iip}(sys::AbstractODESystem, u0map = [], h_oop, h_iip = generate_history(sys, u0) h(out, p, t) = h_iip(out, p, t) h(p, t) = h_oop(p, t) + h(p::MTKParameters, t) = h_oop(p..., t) + h(out, p::MTKParameters, t) = h_iip(out, p..., t) u0 = h(p, tspan[1]) cbs = process_events(sys; callback, kwargs...) if has_discrete_subsystems(sys) && (dss = get_discrete_subsystems(sys)) !== nothing diff --git a/src/systems/parameter_buffer.jl b/src/systems/parameter_buffer.jl index b511a5a764..b8fa6a3b12 100644 --- a/src/systems/parameter_buffer.jl +++ b/src/systems/parameter_buffer.jl @@ -90,7 +90,7 @@ function MTKParameters( for (sym, val) in p sym = unwrap(sym) ctype = concrete_symtype(sym) - val = symconvert(ctype, fixpoint_sub(val, p)) + val = symconvert(ctype, unwrap(fixpoint_sub(val, p))) done = set_value(sym, val) if !done && Symbolics.isarraysymbolic(sym) done = all(set_value.(collect(sym), val)) diff --git a/src/variables.jl b/src/variables.jl index 3b3583cc5a..b2bc730648 100644 --- a/src/variables.jl +++ b/src/variables.jl @@ -197,7 +197,7 @@ function _varmap_to_vars(varmap::Dict, varlist; defaults = Dict(), check = false for var in varlist var = unwrap(var) val = unwrap(fixpoint_sub(var, varmap; operator = Symbolics.Operator)) - if symbolic_type(val) === NotSymbolic() + if !isequal(val, var) values[var] = val end end diff --git a/test/dde.jl b/test/dde.jl index 72db795362..3b303b8b7f 100644 --- a/test/dde.jl +++ b/test/dde.jl @@ -1,4 +1,6 @@ using ModelingToolkit, DelayDiffEq, Test +using ModelingToolkit: t_nounits as t, D_nounits as D + p0 = 0.2; q0 = 0.3; v0 = 1; @@ -29,14 +31,13 @@ prob2 = DDEProblem(bc_model, u0, h2, tspan, constant_lags = lags) sol2 = solve(prob2, alg, reltol = 1e-7, abstol = 1e-10) @parameters p0=0.2 p1=0.2 q0=0.3 q1=0.3 v0=1 v1=1 d0=5 d1=1 d2=1 beta0=1 beta1=1 -@variables t x₀(t) x₁(t) x₂(..) +@variables x₀(t) x₁(t) x₂(..) tau = 1 -D = Differential(t) eqs = [D(x₀) ~ (v0 / (1 + beta0 * (x₂(t - tau)^2))) * (p0 - q0) * x₀ - d0 * x₀ D(x₁) ~ (v0 / (1 + beta0 * (x₂(t - tau)^2))) * (1 - p0 + q0) * x₀ + (v1 / (1 + beta1 * (x₂(t - tau)^2))) * (p1 - q1) * x₁ - d1 * x₁ D(x₂(t)) ~ (v1 / (1 + beta1 * (x₂(t - tau)^2))) * (1 - p1 + q1) * x₁ - d2 * x₂(t)] -@named sys = System(eqs) +@mtkbuild sys = System(eqs, t) prob = DDEProblem(sys, [x₀ => 1.0, x₁ => 1.0, x₂(t) => 1.0], tspan, @@ -70,21 +71,17 @@ prob = SDDEProblem(hayes_modelf, hayes_modelg, [1.0], h, tspan, pmul; constant_lags = (pmul[1],)); sol = solve(prob, RKMil()) -@variables t x(..) +@variables x(..) @parameters a=-4.0 b=-2.0 c=10.0 α=-1.3 β=-1.2 γ=1.1 -D = Differential(t) @brownian η τ = 1.0 eqs = [D(x(t)) ~ a * x(t) + b * x(t - τ) + c + (α * x(t) + γ) * η] -@named sys = System(eqs) -sys = structural_simplify(sys) +@mtkbuild sys = System(eqs, t) @test equations(sys) == [D(x(t)) ~ a * x(t) + b * x(t - τ) + c] @test isequal(ModelingToolkit.get_noiseeqs(sys), [α * x(t) + γ;;]) prob_mtk = SDDEProblem(sys, [x(t) => 1.0 + t], tspan; constant_lags = (τ,)); @test_nowarn sol_mtk = solve(prob_mtk, RKMil()) -@variables t -D = Differential(t) @parameters x(..) a function oscillator(; name, k = 1.0, τ = 0.01) @@ -93,7 +90,7 @@ function oscillator(; name, k = 1.0, τ = 0.01) eqs = [D(x(t)) ~ y, D(y) ~ -k * x(t - τ) + jcn, delx ~ x(t - τ)] - return System(eqs; name = name) + return System(eqs, t; name = name) end systems = @named begin diff --git a/test/runtests.jl b/test/runtests.jl index e029a4bdcc..ae337ab822 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -34,6 +34,7 @@ end @safetestset "Mass Matrix Test" include("mass_matrix.jl") @safetestset "SteadyStateSystem Test" include("steadystatesystems.jl") @safetestset "SDESystem Test" include("sdesystem.jl") + @safetestset "DDESystem Test" include("dde.jl") @safetestset "NonlinearSystem Test" include("nonlinearsystem.jl") @safetestset "InitializationSystem Test" include("initializationsystem.jl") @safetestset "PDE Construction Test" include("pde.jl") From a5112e5369108f95dc1d8820a7b0bbd76127b154 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Thu, 28 Mar 2024 23:30:12 +0530 Subject: [PATCH 009/316] fix: fix varmap_to_vars for variables with unspecified size (#2586) --- src/variables.jl | 6 ++++-- test/initial_values.jl | 7 +++++++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/variables.jl b/src/variables.jl index b2bc730648..7a32cfc9b5 100644 --- a/src/variables.jl +++ b/src/variables.jl @@ -209,8 +209,10 @@ end function canonicalize_varmap(varmap; toterm = Symbolics.diff2term) new_varmap = Dict() for (k, v) in varmap - new_varmap[unwrap(k)] = unwrap(v) - new_varmap[toterm(unwrap(k))] = unwrap(v) + k = unwrap(k) + v = unwrap(v) + new_varmap[k] = v + new_varmap[toterm(k)] = v if Symbolics.isarraysymbolic(k) && Symbolics.shape(k) !== Symbolics.Unknown() for i in eachindex(k) new_varmap[k[i]] = v[i] diff --git a/test/initial_values.jl b/test/initial_values.jl index dcbb57675d..4a72bc72ef 100644 --- a/test/initial_values.jl +++ b/test/initial_values.jl @@ -60,3 +60,10 @@ u0 = [X1 => 1.0, X2 => 2.0] tspan = (0.0, 1.0) ps = [k1 => 1.0, k2 => 5.0] @test_nowarn oprob = ODEProblem(osys_m, u0, tspan, ps) + +# Make sure it doesn't error on array variables with unspecified size +@parameters p::Vector{Real} q[1:3] +varmap = Dict(p => ones(3), q => 2ones(3)) +cvarmap = ModelingToolkit.canonicalize_varmap(varmap) +target_varmap = Dict(p => ones(3), q => 2ones(3), q[1] => 2.0, q[2] => 2.0, q[3] => 2.0) +@test cvarmap == target_varmap From 8b6c83bf8d150cac2c06e2cb1fc1ce41f80daf50 Mon Sep 17 00:00:00 2001 From: Yingbo Ma Date: Fri, 29 Mar 2024 15:27:50 -0400 Subject: [PATCH 010/316] Fix `tearing_reassemble` for under-determined systems (#2587) * Fix `tearing_reassemble` for under-determined systems * Fix minor bug * Add tests * Update tests --- .../symbolics_tearing.jl | 44 ++++++++++--------- test/components.jl | 4 ++ test/structural_transformation/tearing.jl | 2 +- 3 files changed, 29 insertions(+), 21 deletions(-) diff --git a/src/structural_transformation/symbolics_tearing.jl b/src/structural_transformation/symbolics_tearing.jl index 0c510eef75..4908140fb9 100644 --- a/src/structural_transformation/symbolics_tearing.jl +++ b/src/structural_transformation/symbolics_tearing.jl @@ -217,10 +217,18 @@ function check_diff_graph(var_to_diff, fullvars) end =# -function tearing_reassemble(state::TearingState, var_eq_matching; - simplify = false, mm = nothing) +function tearing_reassemble(state::TearingState, var_eq_matching, + full_var_eq_matching = nothing; simplify = false, mm = nothing) @unpack fullvars, sys, structure = state @unpack solvable_graph, var_to_diff, eq_to_diff, graph = structure + extra_vars = Int[] + if full_var_eq_matching !== nothing + for v in 𝑑vertices(state.structure.graph) + eq = full_var_eq_matching[v] + eq isa Int && continue + push!(extra_vars, v) + end + end neweqs = collect(equations(state)) # Terminology and Definition: @@ -532,6 +540,7 @@ function tearing_reassemble(state::TearingState, var_eq_matching; eq_to_diff = new_eq_to_diff diff_to_var = invview(var_to_diff) + old_fullvars = fullvars @set! state.structure.graph = complete(graph) @set! state.structure.var_to_diff = var_to_diff @set! state.structure.eq_to_diff = eq_to_diff @@ -543,9 +552,15 @@ function tearing_reassemble(state::TearingState, var_eq_matching; sys = state.sys @set! sys.eqs = neweqs - @set! sys.unknowns = Any[v - for (i, v) in enumerate(fullvars) - if diff_to_var[i] === nothing && ispresent(i)] + unknowns = Any[v + for (i, v) in enumerate(fullvars) + if diff_to_var[i] === nothing && ispresent(i)] + if !isempty(extra_vars) + for v in extra_vars + push!(unknowns, old_fullvars[v]) + end + end + @set! sys.unknowns = unknowns @set! sys.substitutions = Substitutions(subeqs, deps) obs_sub = dummy_sub @@ -570,19 +585,7 @@ end function tearing(state::TearingState; kwargs...) state.structure.solvable_graph === nothing && find_solvables!(state; kwargs...) complete!(state.structure) - @unpack graph = state.structure - algvars = BitSet(findall(v -> isalgvar(state.structure, v), 1:ndsts(graph))) - aeqs = algeqs(state.structure) - var_eq_matching′, = tear_graph_modia(state.structure; - varfilter = var -> var in algvars, - eqfilter = eq -> eq in aeqs) - var_eq_matching = Matching{Union{Unassigned, SelectedState}}(var_eq_matching′) - for var in 1:ndsts(graph) - if isdiffvar(state.structure, var) - var_eq_matching[var] = SelectedState() - end - end - var_eq_matching + tearing_with_dummy_derivatives(state.structure, ()) end """ @@ -594,8 +597,9 @@ instead, which calls this function internally. """ function tearing(sys::AbstractSystem, state = TearingState(sys); mm = nothing, simplify = false, kwargs...) - var_eq_matching = tearing(state) - invalidate_cache!(tearing_reassemble(state, var_eq_matching; mm, simplify)) + var_eq_matching, full_var_eq_matching = tearing(state) + invalidate_cache!(tearing_reassemble( + state, var_eq_matching, full_var_eq_matching; mm, simplify)) end """ diff --git a/test/components.jl b/test/components.jl index c58ac7b3d0..d4bedb8eb7 100644 --- a/test/components.jl +++ b/test/components.jl @@ -101,6 +101,10 @@ let 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] + + prob3 = ODEProblem(sys2, [], (0, 10.0), guesses = u0) + sol3 = solve(prob2, Rosenbrock23()) + @test sol3[unknowns(rc_model2), end] ≈ sol2[unknowns(rc_model2), end] end # Outer/inner connections diff --git a/test/structural_transformation/tearing.jl b/test/structural_transformation/tearing.jl index 8bf09a480b..dea6af0b11 100644 --- a/test/structural_transformation/tearing.jl +++ b/test/structural_transformation/tearing.jl @@ -98,7 +98,7 @@ end # e5 [ 1 1 | 1 ] let state = TearingState(sys) - torn_matching = tearing(state) + torn_matching, = tearing(state) S = StructuralTransformations.reordered_matrix(sys, torn_matching) @test S == [1 0 0 0 1 1 1 0 0 0 From f77c355e9f558d1c8260996dff519ff52a50042d Mon Sep 17 00:00:00 2001 From: Sathvik Bhagavan <35105271+sathvikbhagavan@users.noreply.github.com> Date: Sat, 30 Mar 2024 00:58:40 +0530 Subject: [PATCH 011/316] refactor: remove undefined export `initializesystem` (#2588) --- src/ModelingToolkit.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ModelingToolkit.jl b/src/ModelingToolkit.jl index c5d5dae2d5..9188e61a3d 100644 --- a/src/ModelingToolkit.jl +++ b/src/ModelingToolkit.jl @@ -256,7 +256,7 @@ export toexpr, get_variables export simplify, substitute export build_function export modelingtoolkitize -export initializesystem, generate_initializesystem +export generate_initializesystem export @variables, @parameters, @constants, @brownian export @named, @nonamespace, @namespace, extend, compose, complete From 723d58ca9c1b8be023f4c6f4e3141155282c8f97 Mon Sep 17 00:00:00 2001 From: Christopher Rackauckas Date: Fri, 29 Mar 2024 15:29:53 -0400 Subject: [PATCH 012/316] Update Project.toml --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index b2992259e8..ea2ce017a7 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.9.0" +version = "9.10.0" [deps] AbstractTrees = "1520ce14-60c1-5f80-bbc7-55ef81b5835c" From 7fb1d99a73925bdcc2e0022449bcf43859c5acb9 Mon Sep 17 00:00:00 2001 From: Christopher Rackauckas Date: Sat, 30 Mar 2024 00:02:07 -0400 Subject: [PATCH 013/316] Update Project.toml --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index ea2ce017a7..b2992259e8 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.10.0" +version = "9.9.0" [deps] AbstractTrees = "1520ce14-60c1-5f80-bbc7-55ef81b5835c" From e26075f51c78d8cb7983585ebd7f5f26017a97f3 Mon Sep 17 00:00:00 2001 From: Venkateshprasad <32921645+ven-k@users.noreply.github.com> Date: Mon, 25 Mar 2024 15:16:05 +0530 Subject: [PATCH 014/316] refactor: use the `t` defined in the module `t` is now available in three flavours. Thus, allow a downstream package to import the appropriate one, and use that to define the mtk-models. --- src/systems/model_parsing.jl | 23 +++++++++++++++---- .../precompile_test/ModelParsingPrecompile.jl | 1 + 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/src/systems/model_parsing.jl b/src/systems/model_parsing.jl index a98ef52c57..21cf0e29ea 100644 --- a/src/systems/model_parsing.jl +++ b/src/systems/model_parsing.jl @@ -92,7 +92,7 @@ function _model_macro(mod, name, expr, isconnector) iv = get(dict, :independent_variable, nothing) if iv === nothing - iv = dict[:independent_variable] = variable(:t) + iv = dict[:independent_variable] = get_t(mod, :t) end push!(exprs.args, :(push!(equations, $(eqs...)))) @@ -199,7 +199,7 @@ function parse_variable_def!(dict, mod, arg, varclass, kwargs, where_types; parse_variable_def!(dict, mod, a, varclass, kwargs, where_types; def, type) end Expr(:call, a, b) => begin - var = generate_var!(dict, a, b, varclass; indices, type) + var = generate_var!(dict, a, b, varclass, mod; indices, type) update_kwargs_and_metadata!(dict, kwargs, a, def, indices, type, var, varclass, where_types) (var, def) @@ -280,11 +280,10 @@ function generate_var!(dict, a, varclass; generate_var(a, varclass; indices, type) end -function generate_var!(dict, a, b, varclass; +function generate_var!(dict, a, b, varclass, mod; indices::Union{Vector{UnitRange{Int}}, Nothing} = nothing, type = Real) - # (type isa Nothing && type = Real) - iv = generate_var(b, :variables) + iv = b == :t ? get_t(mod, b) : generate_var(b, :variables) prev_iv = get!(dict, :independent_variable) do iv end @@ -306,6 +305,20 @@ function generate_var!(dict, a, b, varclass; var end +# Use the `t` defined in the `mod`. When it is unavailable, generate a new `t` with a warning. +function get_t(mod, t) + try + get_var(mod, t) + catch e + if e isa UndefVarError + @warn("Could not find a predefined `t` in `$mod`; generating a new one within this model.\nConsider defining it or importing `t` (or `t_nounits`, `t_unitful` as `t`) from ModelingToolkit.") + variable(:t) + else + throw(e) + end + end +end + function parse_default(mod, a) a = Base.remove_linenums!(deepcopy(a)) MLStyle.@match a begin diff --git a/test/precompile_test/ModelParsingPrecompile.jl b/test/precompile_test/ModelParsingPrecompile.jl index 744e3c2954..87177d519f 100644 --- a/test/precompile_test/ModelParsingPrecompile.jl +++ b/test/precompile_test/ModelParsingPrecompile.jl @@ -1,6 +1,7 @@ module ModelParsingPrecompile using ModelingToolkit, Unitful +using ModelingToolkit: t @mtkmodel ModelWithComponentArray begin @constants begin From 3264db8a33b7cb7a817fe1b281e24732c65f5041 Mon Sep 17 00:00:00 2001 From: Venkateshprasad <32921645+ven-k@users.noreply.github.com> Date: Mon, 25 Mar 2024 21:06:58 +0530 Subject: [PATCH 015/316] docs: import `t` in mtkmodel examples. --- docs/src/basics/MTKModel_Connector.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/src/basics/MTKModel_Connector.md b/docs/src/basics/MTKModel_Connector.md index 639f212653..5282b7db18 100644 --- a/docs/src/basics/MTKModel_Connector.md +++ b/docs/src/basics/MTKModel_Connector.md @@ -39,6 +39,7 @@ Let's explore these in more detail with the following example: ```@example mtkmodel-example using ModelingToolkit +using ModelingToolkit: t @mtkmodel ModelA begin @parameters begin @@ -191,6 +192,7 @@ getdefault(model_c3.model_a.k_array[2]) ```@example mtkmodel-example using ModelingToolkit +using ModelingToolkit: t @mtkmodel M begin @parameters begin @@ -262,6 +264,7 @@ A simple connector can be defined with syntax similar to following example: ```@example connector using ModelingToolkit +using ModelingToolkit: t @connector Pin begin v(t) = 0.0, [description = "Voltage"] @@ -344,6 +347,7 @@ The if-elseif-else statements can be used inside `@equations`, `@parameters`, ```@example branches-in-components using ModelingToolkit +using ModelingToolkit: t @mtkmodel C begin end From 0ccb8c9f6102641eea5eea738c20a0069c6da7a6 Mon Sep 17 00:00:00 2001 From: Venkateshprasad <32921645+ven-k@users.noreply.github.com> Date: Mon, 25 Mar 2024 21:10:38 +0530 Subject: [PATCH 016/316] test: remove extraneous model-parsing test set --- test/model_parsing.jl | 9 +++++---- test/runtests.jl | 2 -- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/test/model_parsing.jl b/test/model_parsing.jl index 411a5e398f..1d7e1a222c 100644 --- a/test/model_parsing.jl +++ b/test/model_parsing.jl @@ -47,7 +47,7 @@ end output = RealOutput() end @parameters begin - k, [description = "Constant output value of block"] + k, [description = "Constant output value of block", unit = u"V"] end @equations begin output.u ~ k @@ -426,6 +426,7 @@ end @test A.structure[:components] == [[:cc, :C]] end +using ModelingToolkit: D_nounits @testset "Event handling in MTKModel" begin @mtkmodel M begin @variables begin @@ -434,9 +435,9 @@ end z(t) end @equations begin - x ~ -D(x) - D(y) ~ 0 - D(z) ~ 0 + x ~ -D_nounits(x) + D_nounits(y) ~ 0 + D_nounits(z) ~ 0 end @continuous_events begin [x ~ 1.5] => [x ~ 5, y ~ 1] diff --git a/test/runtests.jl b/test/runtests.jl index 80c3aa4309..46c836c294 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -94,5 +94,3 @@ end @safetestset "BifurcationKit Extension Test" include("extensions/bifurcationkit.jl") end end - -@safetestset "Model Parsing Test" include("model_parsing.jl") From ab61554b677ff73b080195cdaa5a5780169be161 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Mon, 1 Apr 2024 16:53:29 +0530 Subject: [PATCH 017/316] fix: fix hierarchical discrete systems (#2593) --- .../discrete_system/discrete_system.jl | 16 ++++++++++ test/discrete_system.jl | 31 +++++++++++++++++++ 2 files changed, 47 insertions(+) diff --git a/src/systems/discrete_system/discrete_system.jl b/src/systems/discrete_system/discrete_system.jl index 4f32632c22..bd72c533d0 100644 --- a/src/systems/discrete_system/discrete_system.jl +++ b/src/systems/discrete_system/discrete_system.jl @@ -199,6 +199,22 @@ function DiscreteSystem(eqs, iv; kwargs...) collect(allunknowns), collect(new_ps); kwargs...) end +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), + name = nameof(sys), + checks = false) + end +end + function generate_function( sys::DiscreteSystem, dvs = unknowns(sys), ps = full_parameters(sys); kwargs...) generate_custom_function(sys, [eq.rhs for eq in equations(sys)], dvs, ps; kwargs...) diff --git a/test/discrete_system.jl b/test/discrete_system.jl index 3c2238bca3..ebbb80770e 100644 --- a/test/discrete_system.jl +++ b/test/discrete_system.jl @@ -234,3 +234,34 @@ prob = DiscreteProblem(de, [], (0, 10)) prob = DiscreteProblem(de, [x(k - 1) => 2.0], (0, 10)) @test prob[x] == 3.0 @test prob[x(k - 1)] == 2.0 + +# Issue#2585 +getdata(buffer, t) = buffer[mod1(Int(t), length(buffer))] +@register_symbolic getdata(buffer::Vector, t) +k = ShiftIndex(t) +function SampledData(; name, buffer) + L = length(buffer) + pars = @parameters begin + buffer[1:L] = buffer + end + @variables output(t) time(t) + eqs = [time ~ time(k - 1) + 1 + output ~ getdata(buffer, time)] + return DiscreteSystem(eqs, t; name) +end +function System(; name, buffer) + @named y_sys = SampledData(; buffer = buffer) + pars = @parameters begin + α = 0.5, [description = "alpha"] + β = 0.5, [description = "beta"] + end + vars = @variables y(t)=0.0 y_shk(t)=0.0 + + eqs = [y_shk ~ y_sys.output + # 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) +end + +@test_nowarn @mtkbuild sys = System(; buffer = ones(10)) From 4ae195b9ccce121085914f36004a375b5223f815 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Mon, 1 Apr 2024 17:14:20 +0530 Subject: [PATCH 018/316] fix: fix namespacing of defaults and equations (#2594) --- src/systems/abstractsystem.jl | 22 ++++++++----- test/odesystem.jl | 58 +++++++++++++++++++++++++++++++++++ 2 files changed, 72 insertions(+), 8 deletions(-) diff --git a/src/systems/abstractsystem.jl b/src/systems/abstractsystem.jl index c808aa4057..8753b30656 100644 --- a/src/systems/abstractsystem.jl +++ b/src/systems/abstractsystem.jl @@ -819,22 +819,24 @@ namespace_controls(sys::AbstractSystem) = controls(sys, controls(sys)) function namespace_defaults(sys) defs = defaults(sys) - Dict((isparameter(k) ? parameters(sys, k) : unknowns(sys, k)) => namespace_expr(v, sys) + Dict((isparameter(k) ? parameters(sys, k) : unknowns(sys, k)) => namespace_expr( + v, sys; check = true) for (k, v) in pairs(defs)) end function namespace_equations(sys::AbstractSystem, ivs = independent_variables(sys)) eqs = equations(sys) isempty(eqs) && return Equation[] - map(eq -> namespace_equation(eq, sys; ivs), eqs) + map(eq -> namespace_equation(eq, sys; ivs, check = true), eqs) end function namespace_equation(eq::Equation, sys, n = nameof(sys); - ivs = independent_variables(sys)) - _lhs = namespace_expr(eq.lhs, sys, n; ivs) - _rhs = namespace_expr(eq.rhs, sys, n; ivs) + ivs = independent_variables(sys), + check = false) + _lhs = namespace_expr(eq.lhs, sys, n; ivs, check) + _rhs = namespace_expr(eq.rhs, sys, n; ivs, check) _lhs ~ _rhs end @@ -844,32 +846,36 @@ function namespace_assignment(eq::Assignment, sys) Assignment(_lhs, _rhs) end -function namespace_expr(O, sys, n = nameof(sys); ivs = independent_variables(sys)) +function namespace_expr( + O, sys, n = nameof(sys); ivs = independent_variables(sys), check = false) O = unwrap(O) if any(isequal(O), ivs) return O elseif istree(O) T = typeof(O) renamed = let sys = sys, n = n, T = T - map(a -> namespace_expr(a, sys, n; ivs)::Any, arguments(O)) + map(a -> namespace_expr(a, sys, n; ivs, check)::Any, arguments(O)) end if isvariable(O) + check && !is_variable(sys, O) && !is_parameter(sys, O) && return O # Use renamespace so the scope is correct, and make sure to use the # metadata from the rescoped variable rescoped = renamespace(n, O) similarterm(O, operation(rescoped), renamed, metadata = metadata(rescoped)) elseif Symbolics.isarraysymbolic(O) + check && !is_variable(sys, O) && !is_parameter(sys, O) && return O # promote_symtype doesn't work for array symbolics similarterm(O, operation(O), renamed, symtype(O), metadata = metadata(O)) else similarterm(O, operation(O), renamed, metadata = metadata(O)) end elseif isvariable(O) + check && !is_variable(sys, O) && !is_parameter(sys, O) && return O renamespace(n, O) elseif O isa Array let sys = sys, n = n - map(o -> namespace_expr(o, sys, n; ivs), O) + map(o -> namespace_expr(o, sys, n; ivs, check), O) end else O diff --git a/test/odesystem.jl b/test/odesystem.jl index f45987cdc5..ee4b22f5fc 100644 --- a/test/odesystem.jl +++ b/test/odesystem.jl @@ -995,3 +995,61 @@ let # Issue https://github.com/SciML/ModelingToolkit.jl/issues/2322 sol = solve(prob, Rodas4()) @test sol(1)[]≈0.6065307685451087 rtol=1e-4 end + +# Issue#2344 +function FML2(; name) + @parameters begin + k2[1:1] = [1.0] + end + systems = @named begin + constant = Constant(k = k2[1]) + end + @variables begin + x(t) = 0 + end + eqs = [ + D(x) ~ constant.output.u + k2[1] + ] + ODESystem(eqs, t; systems, name) +end + +@mtkbuild model = FML2() + +@test isequal(ModelingToolkit.defaults(model)[model.constant.k], model.k2[1]) +@test_nowarn ODEProblem(model, [], (0.0, 10.0)) + +# Issue#2477 +function RealExpression(; name, y) + vars = @variables begin + u(t) + end + eqns = [ + u ~ y + ] + sys = ODESystem(eqns, t, vars, []; name) +end + +function sys1(; name) + vars = @variables begin + x(t) + z(t)[1:1] + end # doing a collect on z doesn't work either. + @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) +end + +@named sys = sys1() +sys = complete(sys) +@test Set(equations(sys)) == Set([sys.e1.u ~ sys.x, sys.e2.u ~ sys.z[1]]) + +# Issue#2522 +@parameters a[1:2]=[1, 2] b=4 c=1 +@variables x(t)=ParentScope(a[1]) y(t)=ParentScope(b) +@named level0 = ODESystem([D(x) ~ ParentScope(a[2]), + D(y) ~ ParentScope(c)], t, [x, y], []) +level1 = ODESystem(Equation[], t, [], [a..., b, c]; name = :level1) ∘ level0 +level1 = structural_simplify(level1) +@test isequal(ModelingToolkit.defaults(level1)[level1.level0.x], level1.a[1]) +@test_nowarn ODEProblem(level1, [], (0, 1)) From 7ff933af16ca3cc8de7ea9d892abece95a06878e Mon Sep 17 00:00:00 2001 From: Christopher Rackauckas Date: Mon, 1 Apr 2024 07:53:45 -0400 Subject: [PATCH 019/316] Revert "fix: fix namespacing of defaults and equations (#2594)" (#2595) This reverts commit 4ae195b9ccce121085914f36004a375b5223f815. --- src/systems/abstractsystem.jl | 22 +++++-------- test/odesystem.jl | 58 ----------------------------------- 2 files changed, 8 insertions(+), 72 deletions(-) diff --git a/src/systems/abstractsystem.jl b/src/systems/abstractsystem.jl index 8753b30656..c808aa4057 100644 --- a/src/systems/abstractsystem.jl +++ b/src/systems/abstractsystem.jl @@ -819,24 +819,22 @@ namespace_controls(sys::AbstractSystem) = controls(sys, controls(sys)) function namespace_defaults(sys) defs = defaults(sys) - Dict((isparameter(k) ? parameters(sys, k) : unknowns(sys, k)) => namespace_expr( - v, sys; check = true) + Dict((isparameter(k) ? parameters(sys, k) : unknowns(sys, k)) => namespace_expr(v, sys) for (k, v) in pairs(defs)) end function namespace_equations(sys::AbstractSystem, ivs = independent_variables(sys)) eqs = equations(sys) isempty(eqs) && return Equation[] - map(eq -> namespace_equation(eq, sys; ivs, check = true), eqs) + map(eq -> namespace_equation(eq, sys; ivs), eqs) end function namespace_equation(eq::Equation, sys, n = nameof(sys); - ivs = independent_variables(sys), - check = false) - _lhs = namespace_expr(eq.lhs, sys, n; ivs, check) - _rhs = namespace_expr(eq.rhs, sys, n; ivs, check) + ivs = independent_variables(sys)) + _lhs = namespace_expr(eq.lhs, sys, n; ivs) + _rhs = namespace_expr(eq.rhs, sys, n; ivs) _lhs ~ _rhs end @@ -846,36 +844,32 @@ function namespace_assignment(eq::Assignment, sys) Assignment(_lhs, _rhs) end -function namespace_expr( - O, sys, n = nameof(sys); ivs = independent_variables(sys), check = false) +function namespace_expr(O, sys, n = nameof(sys); ivs = independent_variables(sys)) O = unwrap(O) if any(isequal(O), ivs) return O elseif istree(O) T = typeof(O) renamed = let sys = sys, n = n, T = T - map(a -> namespace_expr(a, sys, n; ivs, check)::Any, arguments(O)) + map(a -> namespace_expr(a, sys, n; ivs)::Any, arguments(O)) end if isvariable(O) - check && !is_variable(sys, O) && !is_parameter(sys, O) && return O # Use renamespace so the scope is correct, and make sure to use the # metadata from the rescoped variable rescoped = renamespace(n, O) similarterm(O, operation(rescoped), renamed, metadata = metadata(rescoped)) elseif Symbolics.isarraysymbolic(O) - check && !is_variable(sys, O) && !is_parameter(sys, O) && return O # promote_symtype doesn't work for array symbolics similarterm(O, operation(O), renamed, symtype(O), metadata = metadata(O)) else similarterm(O, operation(O), renamed, metadata = metadata(O)) end elseif isvariable(O) - check && !is_variable(sys, O) && !is_parameter(sys, O) && return O renamespace(n, O) elseif O isa Array let sys = sys, n = n - map(o -> namespace_expr(o, sys, n; ivs, check), O) + map(o -> namespace_expr(o, sys, n; ivs), O) end else O diff --git a/test/odesystem.jl b/test/odesystem.jl index ee4b22f5fc..f45987cdc5 100644 --- a/test/odesystem.jl +++ b/test/odesystem.jl @@ -995,61 +995,3 @@ let # Issue https://github.com/SciML/ModelingToolkit.jl/issues/2322 sol = solve(prob, Rodas4()) @test sol(1)[]≈0.6065307685451087 rtol=1e-4 end - -# Issue#2344 -function FML2(; name) - @parameters begin - k2[1:1] = [1.0] - end - systems = @named begin - constant = Constant(k = k2[1]) - end - @variables begin - x(t) = 0 - end - eqs = [ - D(x) ~ constant.output.u + k2[1] - ] - ODESystem(eqs, t; systems, name) -end - -@mtkbuild model = FML2() - -@test isequal(ModelingToolkit.defaults(model)[model.constant.k], model.k2[1]) -@test_nowarn ODEProblem(model, [], (0.0, 10.0)) - -# Issue#2477 -function RealExpression(; name, y) - vars = @variables begin - u(t) - end - eqns = [ - u ~ y - ] - sys = ODESystem(eqns, t, vars, []; name) -end - -function sys1(; name) - vars = @variables begin - x(t) - z(t)[1:1] - end # doing a collect on z doesn't work either. - @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) -end - -@named sys = sys1() -sys = complete(sys) -@test Set(equations(sys)) == Set([sys.e1.u ~ sys.x, sys.e2.u ~ sys.z[1]]) - -# Issue#2522 -@parameters a[1:2]=[1, 2] b=4 c=1 -@variables x(t)=ParentScope(a[1]) y(t)=ParentScope(b) -@named level0 = ODESystem([D(x) ~ ParentScope(a[2]), - D(y) ~ ParentScope(c)], t, [x, y], []) -level1 = ODESystem(Equation[], t, [], [a..., b, c]; name = :level1) ∘ level0 -level1 = structural_simplify(level1) -@test isequal(ModelingToolkit.defaults(level1)[level1.level0.x], level1.a[1]) -@test_nowarn ODEProblem(level1, [], (0, 1)) From 09f8e50f7424ce89f486b99c0e5fac606b61a5da Mon Sep 17 00:00:00 2001 From: Christopher Rackauckas Date: Mon, 25 Mar 2024 16:44:13 -0400 Subject: [PATCH 020/316] Handle derivatives of observed variables If you have a variable `x` which is not a state and substitute it out because some equation `y ~ x`, you would hope that `D(x) => 1` effectively means `D(y) => 1`. This PR does a fixed point substitution on any derivative of observed variables in order to rephrase it into the chosen variable. This fixes the case of aliasing but not the general case of if you are doing the derivative of some observed expression, i.e. `x ~ y + z` where `x` is factored out to be an observed variable, then you want to set `D(x) => 1`. --- src/systems/nonlinear/initializesystem.jl | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/systems/nonlinear/initializesystem.jl b/src/systems/nonlinear/initializesystem.jl index e05456be6a..d0c24f2f99 100644 --- a/src/systems/nonlinear/initializesystem.jl +++ b/src/systems/nonlinear/initializesystem.jl @@ -20,6 +20,7 @@ function generate_initializesystem(sys::ODESystem; eqs_diff = eqs[idxs_diff] diffmap = Dict(getfield.(eqs_diff, :lhs) .=> getfield.(eqs_diff, :rhs)) + observed_diffmap = Dict(Differential(get_iv(sys)).(getfield.((observed(sys)), :lhs)) .=> Differential(get_iv(sys)).(getfield.((observed(sys)), :rhs))) full_states = unique([sts; getfield.((observed(sys)), :lhs)]) set_full_states = Set(full_states) @@ -36,7 +37,9 @@ function generate_initializesystem(sys::ODESystem; filtered_u0 = Pair[] for x in u0map y = get(schedule.dummy_sub, x[1], x[1]) + y = ModelingToolkit.fixpoint_sub(y, observed_diffmap) y = get(diffmap, y, y) + if y isa Symbolics.Arr _y = collect(y) From 307b52d4eca66a3bd874eebd969a83786d720d6d Mon Sep 17 00:00:00 2001 From: Christopher Rackauckas Date: Mon, 1 Apr 2024 10:04:22 -0400 Subject: [PATCH 021/316] format --- 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 d0c24f2f99..dadc66e262 100644 --- a/src/systems/nonlinear/initializesystem.jl +++ b/src/systems/nonlinear/initializesystem.jl @@ -20,7 +20,8 @@ function generate_initializesystem(sys::ODESystem; eqs_diff = eqs[idxs_diff] diffmap = Dict(getfield.(eqs_diff, :lhs) .=> getfield.(eqs_diff, :rhs)) - observed_diffmap = Dict(Differential(get_iv(sys)).(getfield.((observed(sys)), :lhs)) .=> Differential(get_iv(sys)).(getfield.((observed(sys)), :rhs))) + observed_diffmap = Dict(Differential(get_iv(sys)).(getfield.((observed(sys)), :lhs)) .=> + Differential(get_iv(sys)).(getfield.((observed(sys)), :rhs))) full_states = unique([sts; getfield.((observed(sys)), :lhs)]) set_full_states = Set(full_states) From 043abf362e2187ff0dc15f706e68527e8d7bd3f1 Mon Sep 17 00:00:00 2001 From: Fredrik Bagge Carlson Date: Mon, 1 Apr 2024 16:29:23 +0200 Subject: [PATCH 022/316] Change wording from initial guess to initial condition (#2598) Since the text was talking about the "default value" and not the "guess metadata" --- docs/src/tutorials/ode_modeling.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/src/tutorials/ode_modeling.md b/docs/src/tutorials/ode_modeling.md index ef354071d9..2448f88ac6 100644 --- a/docs/src/tutorials/ode_modeling.md +++ b/docs/src/tutorials/ode_modeling.md @@ -315,9 +315,9 @@ plot(solve(prob)) More on this topic may be found in [Composing Models and Building Reusable Components](@ref acausal). -## Initial Guess +## Default Initial Condition -It is often a good idea to specify reasonable values for the initial unknown and the +It is often a good idea to specify reasonable values for the initial value of unknowns and the parameters of a model component. Then, these do not have to be explicitly specified when constructing the `ODEProblem`. ```@example ode2 @@ -334,15 +334,15 @@ parameters of a model component. Then, these do not have to be explicitly specif end ``` -While defining the model `UnitstepFOLFactory`, an initial guess of 0.0 is assigned to `x(t)` and 1.0 to `τ`. -Additionally, these initial guesses can be modified while creating instances of `UnitstepFOLFactory` by passing arguments. +While defining the model `UnitstepFOLFactory`, an initial condition of 0.0 is assigned to `x(t)` and 1.0 to `τ`. +Additionally, these initial conditions can be modified while creating instances of `UnitstepFOLFactory` by passing arguments. ```@example ode2 @mtkbuild fol = UnitstepFOLFactory(; x = 0.1) sol = ODEProblem(fol, [], (0.0, 5.0), []) |> solve ``` -In non-DSL definitions, one can pass `defaults` dictionary to set the initial guess of the symbolic variables. +In non-DSL definitions, one can pass `defaults` dictionary to set the initial conditions of the symbolic variables. ```@example ode3 using ModelingToolkit From f75d225ef1384696c5d87ff83ff9051ede9e7002 Mon Sep 17 00:00:00 2001 From: Venkateshprasad <32921645+ven-k@users.noreply.github.com> Date: Tue, 2 Apr 2024 01:12:05 +0530 Subject: [PATCH 023/316] refactor: define constants in mtkmodel crisply + Adds an indirect test which validates units and check that metadata is correctly set. --- src/systems/model_parsing.jl | 15 +++++++++------ test/model_parsing.jl | 7 ++++++- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/src/systems/model_parsing.jl b/src/systems/model_parsing.jl index 21cf0e29ea..58ccea8e82 100644 --- a/src/systems/model_parsing.jl +++ b/src/systems/model_parsing.jl @@ -406,8 +406,9 @@ function parse_constants!(exprs, dict, body, mod) 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) - constant = first(@constants $a::type = b) - push!(exprs, :($a = $constant)) + 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 @@ -416,16 +417,18 @@ function parse_constants!(exprs, dict, body, mod) end end Expr(:(=), a, Expr(:tuple, b, metadata)) => begin - constant = first(@constants $a = b) - push!(exprs, :($a = $constant)) + 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 - constant = first(@constants $a = b) - push!(exprs, :($a = $constant)) + 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: diff --git a/test/model_parsing.jl b/test/model_parsing.jl index 1d7e1a222c..578eb60c74 100644 --- a/test/model_parsing.jl +++ b/test/model_parsing.jl @@ -184,10 +184,15 @@ resistor = getproperty(rc, :resistor; namespace = false) @testset "Constants" begin @mtkmodel PiModel begin @constants begin - _p::Irrational = π, [description = "Value of Pi."] + _p::Irrational = π, [description = "Value of Pi.", unit = u"V"] end @parameters begin p = _p, [description = "Assign constant `_p` value."] + e, [unit = u"V"] + end + @equations begin + # This validates units; indirectly verifies that metadata was correctly passed. + e ~ _p end end From 6b498dccd481cd34fbb48b656dcb93af4f62858f Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Tue, 2 Apr 2024 11:21:09 +0530 Subject: [PATCH 024/316] docs: add tutorial for optimizing ODE solve and remake --- docs/Project.toml | 2 + docs/pages.jl | 3 +- docs/src/examples/remake.md | 161 ++++++++++++++++++++++++++++++++++++ 3 files changed, 165 insertions(+), 1 deletion(-) create mode 100644 docs/src/examples/remake.md diff --git a/docs/Project.toml b/docs/Project.toml index 5d39558eb3..2cb3964373 100644 --- a/docs/Project.toml +++ b/docs/Project.toml @@ -13,6 +13,7 @@ Optimization = "7f7a1694-90dd-40f0-9382-eb1efda571ba" OptimizationOptimJL = "36348300-93cb-4f02-beb5-3c3902f8871e" OrdinaryDiffEq = "1dea7af3-3e70-54e6-95c3-0bf5283fa5ed" Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80" +SciMLStructures = "53ae85a6-f571-4167-b2af-e1d143709226" StochasticDiffEq = "789caeaf-c7a9-5a7d-9973-96adeb23e2a0" SymbolicIndexingInterface = "2efcf032-c050-4f8e-a9bb-153293bab1f5" SymbolicUtils = "d1185830-fcd6-423d-90d6-eec64667417b" @@ -33,6 +34,7 @@ Optimization = "3.9" OptimizationOptimJL = "0.1" OrdinaryDiffEq = "6.31" Plots = "1.36" +SciMLStructures = "1.1" StochasticDiffEq = "6" SymbolicIndexingInterface = "0.3.1" SymbolicUtils = "1" diff --git a/docs/pages.jl b/docs/pages.jl index c3c4adfda6..ca265ccef5 100644 --- a/docs/pages.jl +++ b/docs/pages.jl @@ -17,7 +17,8 @@ pages = [ "Basic Examples" => Any["examples/higher_order.md", "examples/spring_mass.md", "examples/modelingtoolkitize_index_reduction.md", - "examples/parsing.md"], + "examples/parsing.md", + "examples/remake.md"], "Advanced Examples" => Any["examples/tearing_parallelism.md", "examples/sparse_jacobians.md", "examples/perturbation.md"]], diff --git a/docs/src/examples/remake.md b/docs/src/examples/remake.md new file mode 100644 index 0000000000..8cd1f9b81c --- /dev/null +++ b/docs/src/examples/remake.md @@ -0,0 +1,161 @@ +# Optimizing through an ODE solve and re-creating MTK Problems + +Solving an ODE as part of an `OptimizationProblem`'s loss function is a common scenario. +In this example, we will go through an efficient way to model such scenarios using +ModelingToolkit.jl. + +First, we build the ODE to be solved. For this example, we will use a Lotka-Volterra model: + +```@example Remake +using ModelingToolkit +using ModelingToolkit: t_nounits as t, D_nounits as D + +@parameters α β γ δ +@variables x(t) y(t) +eqs = [D(x) ~ (α - β * y) * x + D(y) ~ (δ * x - γ) * y] +@mtkbuild odesys = ODESystem(eqs, t) +``` + +To create the "data" for optimization, we will solve the system with a known set of +parameters. + +```@example Remake +using OrdinaryDiffEq + +odeprob = ODEProblem( + odesys, [x => 1.0, y => 1.0], (0.0, 10.0), [α => 1.5, β => 1.0, γ => 3.0, δ => 1.0]) +timesteps = 0.0:0.1:10.0 +sol = solve(odeprob, Tsit5(); saveat = timesteps) +data = Array(sol) +# add some random noise +data = data + 0.01 * randn(size(data)) +``` + +Now we will create the loss function for the Optimization solve. This will require creating +an `ODEProblem` with the parameter values passed to the loss function. Creating a new +`ODEProblem` is expensive and requires differentiating through the code generation process. +This can be bug-prone and is unnecessary. Instead, we will leverage the `remake` function. +This allows creating a copy of an existing problem with updating state/parameter values. It +should be noted that the types of the values passed to the loss function may not agree with +the types stored in the existing `ODEProblem`. Thus, we cannot use `setp` to modify the +problem in-place. Here, we will use the `replace` function from SciMLStructures.jl since +it allows updating the entire `Tunable` portion of the parameter object which contains the +parameters to optimize. + +```@example Remake +using SymbolicIndexingInterface: parameter_values, state_values +using SciMLStructures: Tunable, replace, replace! + +function loss(x, p) + odeprob = p[1] # ODEProblem stored as parameters to avoid using global variables + ps = parameter_values(odeprob) # obtain the parameter object from the problem + ps = replace(Tunable(), ps, x) # create a copy with the values passed to the loss function + T = eltype(x) + # we also have to convert the `u0` vector + u0 = T.(state_values(odeprob)) + # remake the problem, passing in our new parameter object + newprob = remake(odeprob; u0 = u0, p = ps) + timesteps = p[2] + sol = solve(newprob, AutoTsit5(Rosenbrock23()); saveat = timesteps) + truth = p[3] + data = Array(sol) + return sum((truth .- data) .^ 2) / length(truth) +end +``` + +Note how the problem, timesteps and true data are stored as model parameters. This helps +avoid referencing global variables in the function, which would slow it down significantly. + +We could have done the same thing by passing `remake` a map of parameter values. For example, +let us enforce that the order of ODE parameters in `x` is `[α β γ δ]`. Then, we could have +done: + +```julia +remake(odeprob; p = [α => x[1], β => x[2], γ => x[3], δ => x[4]]) +``` + +However, passing a symbolic map to `remake` is significantly slower than passing it a +parameter object directly. Thus, we use `replace` to speed up the process. In general, +`remake` is the most flexible method, but the flexibility comes at a cost of performance. + +We can perform the optimization as below: + +```@example Remake +using Optimization +using OptimizationOptimJL + +# manually create an OptimizationFunction to ensure usage of `ForwardDiff`, which will +# require changing the types of parameters from `Float64` to `ForwardDiff.Dual` +optfn = OptimizationFunction(loss, Optimization.AutoForwardDiff()) +# parameter object is a tuple, to store differently typed objects together +optprob = OptimizationProblem( + optfn, rand(4), (odeprob, timesteps, data), lb = 0.1zeros(4), ub = 3ones(4)) +sol = solve(optprob, BFGS()) +``` + +To identify which values correspond to which parameters, we can `replace!` them into the +`ODEProblem`: + +```@example Remake +replace!(Tunable(), parameter_values(odeprob), sol.u) +odeprob.ps[[α, β, γ, δ]] +``` + +`replace!` operates in-place, so the values being replaced must be of the same type as those +stored in the parameter object, or convertible to that type. For demonstration purposes, we +can construct a loss function that uses `replace!`, and calculate gradients using +`AutoFiniteDiff` rather than `AutoForwardDiff`. + +```@example Remake +function loss2(x, p) + odeprob = p[1] # ODEProblem stored as parameters to avoid using global variables + newprob = remake(odeprob) # copy the problem with `remake` + # update the parameter values in-place + replace!(Tunable(), parameter_values(newprob), x) + timesteps = p[2] + sol = solve(newprob, AutoTsit5(Rosenbrock23()); saveat = timesteps) + truth = p[3] + data = Array(sol) + return sum((truth .- data) .^ 2) / length(truth) +end + +# use finite-differencing to calculate derivatives +optfn2 = OptimizationFunction(loss2, Optimization.AutoFiniteDiff()) +optprob2 = OptimizationProblem( + optfn2, rand(4), (odeprob, timesteps, data), lb = 0.1zeros(4), ub = 3ones(4)) +sol = solve(optprob2, BFGS()) +``` + +# Re-creating the problem + +There are multiple ways to re-create a problem with new state/parameter values. We will go +over the various methods, listing their use cases. + +## Pure `remake` + +This method is the most generic. It can handle symbolic maps, initializations of +parameters/states dependent on each other and partial updates. However, this comes at the +cost of performance. `remake` is also not always inferrable. + +## `remake` and `setp`/`setu` + +Calling `remake(prob)` creates a copy of the existing problem. This new problem has the +exact same types as the original one, and the `remake` call is fully inferred. +State/parameter values can be modified after the copy by using `setp` and/or `setu`. This +is most appropriate when the types of state/parameter values does not need to be changed, +only their values. + +## `replace` and `remake` + +`replace` returns a copy of a parameter object, with the appropriate portion replaced by new +values. This is useful for changing the type of an entire portion, such as during the +optimization process described above. `remake` is used in this case to create a copy of the +problem with updated state/unknown values. + +## `remake` and `replace!` + +`replace!` is similar to `replace`, except that it operates in-place. This means that the +parameter values must be of the same types. This is useful for cases where bulk parameter +replacement is required without needing to change types. For example, optimization methods +where the gradient is not computed using dual numbers (as demonstrated above). From 7a66a4f150e45086f6bb81ac2d99cc1b7d36ce20 Mon Sep 17 00:00:00 2001 From: Torkel Date: Tue, 2 Apr 2024 08:33:28 -0400 Subject: [PATCH 025/316] init --- src/ModelingToolkit.jl | 3 + src/systems/abstractsystem.jl | 236 ++++++++++++++++++++++++++++++++ test/equation_type_accessors.jl | 189 +++++++++++++++++++++++++ test/runtests.jl | 53 +------ 4 files changed, 429 insertions(+), 52 deletions(-) create mode 100644 test/equation_type_accessors.jl diff --git a/src/ModelingToolkit.jl b/src/ModelingToolkit.jl index 9188e61a3d..cde7630073 100644 --- a/src/ModelingToolkit.jl +++ b/src/ModelingToolkit.jl @@ -258,6 +258,9 @@ export build_function export modelingtoolkitize export generate_initializesystem +export alg_equations, diff_equations, has_alg_equations, has_diff_equations +export get_alg_eqs, get_diff_eqs, has_alg_eqs,has_diff_eqs + export @variables, @parameters, @constants, @brownian export @named, @nonamespace, @namespace, extend, compose, complete export debug_system diff --git a/src/systems/abstractsystem.jl b/src/systems/abstractsystem.jl index c808aa4057..dbee3825d5 100644 --- a/src/systems/abstractsystem.jl +++ b/src/systems/abstractsystem.jl @@ -2384,3 +2384,239 @@ function dump_unknowns(sys::AbstractSystem) meta end end + +### Functions for accessing algebraic/differential equations in systems ### + +""" + is_diff_equation(eq) + +Returns `true` if the input is a differential equation, i.e. is an equatation that contain some +form of differential. + +Example: +```julia +using ModelingToolkit +import ModelingToolkit: t as nounits_t, D as nounits_D +@parameters p d +@variables X(t) +eq1 = D(X) ~ p - d*X +eq2 = 0 ~ p - d*X + +is_diff_equation(eq1) # true +is_diff_equation(eq2) # false +``` +""" +function is_diff_equation(eq) + (eq isa Equation) || (return false) + isdefined(eq, :lhs) && occursin(is_derivative, wrap(eq.lhs)) && (return true) + isdefined(eq, :rhs) && occursin(is_derivative, wrap(eq.rhs)) && (return true) + return false +end + +""" + is_alg_equation(eq) + +Returns `true` if the input is an algebraic equation, i.e. is an equatation that does not contain +any differentials. + +Example: +```julia +using ModelingToolkit +import ModelingToolkit: t as nounits_t, D as nounits_D +@parameters p d +@variables X(t) +eq1 = D(X) ~ p - d*X +eq2 = 0 ~ p - d*X + +is_alg_equation(eq1) # false +is_alg_equation(eq2) # true +``` +""" +function is_alg_equation(eq) + return (eq isa Equation) && !is_diff_equation(eq) +end + +""" + alg_equations(sys::AbstractSystem) + +For a system, returns a vector of all its algebraic equations (i.e. that does not contain any +differentials). + +Example: +```julia +using ModelingToolkit +import ModelingToolkit: t as nounits_t, D as nounits_D +@parameters p d +@variables X(t) +eq1 = D(X) ~ p - d*X +eq2 = 0 ~ p - d*X +@named osys = ODESystem([eq1, eq2], t) + +alg_equations(osys) # returns `0 ~ p - d*X(t)`. +""" +alg_equations(sys::AbstractSystem) = filter(is_alg_equation, equations(sys)) + +""" + diff_equations(sys::AbstractSystem) + +For a system, returns a vector of all its differential equations (i.e. that does contain a differential). + +Example: +```julia +using ModelingToolkit +import ModelingToolkit: t as nounits_t, D as nounits_D +@parameters p d +@variables X(t) +eq1 = D(X) ~ p - d*X +eq2 = 0 ~ p - d*X +@named osys = ODESystem([eq1, eq2], t) + +diff_equations(osys) # returns `Differential(t)(X(t)) ~ p - d*X(t)`. +""" +diff_equations(sys::AbstractSystem) = filter(is_diff_equation, equations(sys)) + +""" + has_alg_equations(sys::AbstractSystem) + +For a system, returns true if it contain at least one algebraic equation (i.e. that does not contain any +differentials). + +Example: +```julia +using ModelingToolkit +import ModelingToolkit: t as nounits_t, D as nounits_D +@parameters p 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) + +has_alg_equations(osys1) # returns `false`. +has_alg_equations(osys2) # returns `true`. +``` +""" +has_alg_equations(sys::AbstractSystem) = any(is_alg_equation, equations(sys)) + +""" + has_diff_equations(sys::AbstractSystem) + +For a system, returns true if it contain at least one differential equation (i.e. that contain a differential). + +Example: +```julia +using ModelingToolkit +import ModelingToolkit: t as nounits_t, D as nounits_D +@parameters p 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) + +has_diff_equations(osys1) # returns `true`. +has_diff_equations(osys2) # returns `false`. +``` +""" +has_diff_equations(sys::AbstractSystem) = any(is_diff_equation, equations(sys)) + + +""" + get_alg_eqs(sys::AbstractSystem) + +For a system, returns a vector of all algebraic equations (i.e. that does not contain any +differentials) in its *top-level system*. + +Example: +```julia +using ModelingToolkit +import ModelingToolkit: t as nounits_t, D as nounits_D +@parameters p 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]) +osys21 = compose(osys2, [osys1]) + +get_alg_eqs(osys12) # returns `Equation[]`. +get_alg_eqs(osys21) # returns `[0 ~ p - d*X(t)]`. +``` +""" +get_alg_eqs(sys::AbstractSystem) = filter(is_alg_equation, get_eqs(sys)) + +""" + get_diff_eqs(sys::AbstractSystem) + +For a system, returns a vector of all differential equations (i.e. that does contain a differential) +in its *top-level system*. + +Example: +```julia +using ModelingToolkit +import ModelingToolkit: t as nounits_t, D as nounits_D +@parameters p 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]) +osys21 = compose(osys2, [osys1]) + +get_diff_eqs(osys12) # returns `[Differential(t)(X(t)) ~ p - d*X(t)]`. +get_diff_eqs(osys21) # returns `Equation[]``. +``` +""" +get_diff_eqs(sys::AbstractSystem) = filter(is_diff_equation, get_eqs(sys)) + +""" + has_alg_eqs(sys::AbstractSystem) + +For a system, returns true if it contain at least one algebraic equation (i.e. that does not contain any +differentials) in its *top-level system*. + +Example: +```julia +using ModelingToolkit +import ModelingToolkit: t as nounits_t, D as nounits_D +@parameters p 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]) +osys21 = compose(osys2, [osys1]) + +has_alg_eqs(osys12) # returns `false`. +has_alg_eqs(osys21) # returns `true`. +``` +""" +has_alg_eqs(sys::AbstractSystem) = any(is_alg_equation, get_eqs(sys)) + +""" + has_diff_eqs(sys::AbstractSystem) + +For a system, returns true if it contain at least one differential equation (i.e. that contain a +differential) in its *top-level system*. + +Example: +```julia +using ModelingToolkit +import ModelingToolkit: t as nounits_t, D as nounits_D +@parameters p 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]) +osys21 = compose(osys2, [osys1]) + +has_diff_eqs(osys12) # returns `true`. +has_diff_eqs(osys21) # returns `false`. +``` +""" +has_diff_eqs(sys::AbstractSystem) = any(is_diff_equation, get_eqs(sys)) \ No newline at end of file diff --git a/test/equation_type_accessors.jl b/test/equation_type_accessors.jl new file mode 100644 index 0000000000..21f2eb7b9a --- /dev/null +++ b/test/equation_type_accessors.jl @@ -0,0 +1,189 @@ +# Fetch packages. +using ModelingToolkit +import ModelingToolkit: get_systems, namespace_equations +import ModelingToolkit: t_nounits as t, D_nounits as D, wrap, get_eqs + +# Creates equations. +@variables X(t) Y(t) Z(t) +@parameters a b c d +eq1 = X^Z - Z^(X+1) ~ log(X - a + b) * Y +eq2 = X ~ Y^(X + 1) +eq3 = a + b + c + d ~ X*(Y + d*(Y + Z)) +eq4 = X ~ sqrt(a + Z) + t +eq5 = D(D(X)) ~ a^(2Y) + 3Z*t - 6 +eq6 = X *(Z - Z*(b+X)) ~ c^(X+D(Y)) +eq7 = sqrt(X + c) ~ 2*(Y + log(a + D(Z))) +eq8 = -0.1 ~ D(Z) + X + +@test is_alg_equation(eq1) +@test is_alg_equation(eq2) +@test is_alg_equation(eq3) +@test is_alg_equation(eq4) +@test !is_alg_equation(eq5) +@test !is_alg_equation(eq6) +@test !is_alg_equation(eq7) +@test !is_alg_equation(eq8) + +@test !is_diff_equation(eq1) +@test !is_diff_equation(eq2) +@test !is_diff_equation(eq3) +@test !is_diff_equation(eq4) +@test is_diff_equation(eq5) +@test is_diff_equation(eq6) +@test is_diff_equation(eq7) +@test is_diff_equation(eq8) + +# Creates systems. +eqs1 = [ + X*Y + a ~ Z^3 - X*log(b + Y) + X ~ Z*Y*X + a + b + c*sin(X) + sin(Y) ~ d*(a + X*(b + Y* (c + Z))) +] +eqs2 = [ + X + Y + c ~ b*X^(X + Z + a) + D(X) ~ a*Y + b*X + c*Z + D(Z) + Z*Y ~ X - log(Z) +] +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) + +# Test `has...` for non-composed systems. +@test has_alg_equations(osys1) +@test has_alg_equations(osys2) +@test !has_alg_equations(osys3) +@test has_alg_eqs(osys1) +@test has_alg_eqs(osys2) +@test !has_alg_eqs(osys3) +@test !has_diff_equations(osys1) +@test has_diff_equations(osys2) +@test has_diff_equations(osys3) +@test !has_diff_eqs(osys1) +@test has_diff_eqs(osys2) +@test has_diff_eqs(osys3) + +# Test getters for non-composed systems. +isequal(alg_equations(osys1), eqs1) +isequal(alg_equations(osys2), eqs2[1:1]) +isequal(alg_equations(osys3), []) +isequal(get_alg_eqs(osys1), eqs1) +isequal(get_alg_eqs(osys2), eqs2[1:1]) +isequal(get_alg_eqs(osys3), []) +isequal(diff_equations(osys1), []) +isequal(diff_equations(osys2), eqs2[2:3]) +isequal(diff_equations(osys3), eqs3) +isequal(get_diff_eqs(osys1), []) +isequal(get_diff_eqs(osys2), eqs2[2:3]) +isequal(get_diff_eqs(osys3), eqs3) + +# Creates composed systems. +osys1_1 = compose(osys1, [osys1]) +osys1_12 = compose(osys1, [osys1, osys2]) +osys1_12_1 = compose(osys1, [osys1, compose(osys2, [osys1])]) +osys3_2 = compose(osys3, [osys2]) +osys3_33 = compose(osys3, [osys3, osys3]) + +# Test `has...` for composed systems. +@test has_alg_equations(osys1_1) +@test !has_diff_equations(osys1_1) +@test has_alg_eqs(osys1_1) +@test !has_diff_eqs(osys1_1) +@test has_alg_equations(get_systems(osys1_1)[1]) +@test !has_diff_equations(get_systems(osys1_1)[1]) +@test has_alg_eqs(get_systems(osys1_1)[1]) +@test !has_diff_eqs(get_systems(osys1_1)[1]) + +@test has_alg_equations(osys1_12) +@test has_diff_equations(osys1_12) +@test has_alg_eqs(osys1_12) +@test !has_diff_eqs(osys1_12) +@test has_alg_equations(get_systems(osys1_12)[1]) +@test !has_diff_equations(get_systems(osys1_12)[1]) +@test has_alg_eqs(get_systems(osys1_12)[1]) +@test !has_diff_eqs(get_systems(osys1_12)[1]) +@test has_alg_equations(get_systems(osys1_12)[2]) +@test has_diff_equations(get_systems(osys1_12)[2]) +@test has_alg_eqs(get_systems(osys1_12)[2]) +@test has_diff_eqs(get_systems(osys1_12)[2]) + +@test has_alg_equations(osys1_12_1) +@test has_diff_equations(osys1_12_1) +@test has_alg_eqs(osys1_12_1) +@test !has_diff_eqs(osys1_12_1) +@test has_alg_equations(get_systems(osys1_12_1)[1]) +@test !has_diff_equations(get_systems(osys1_12_1)[1]) +@test has_alg_eqs(get_systems(osys1_12_1)[1]) +@test !has_diff_eqs(get_systems(osys1_12_1)[1]) +@test has_alg_equations(get_systems(osys1_12_1)[2]) +@test has_diff_equations(get_systems(osys1_12_1)[2]) +@test has_alg_eqs(get_systems(osys1_12_1)[2]) +@test has_diff_eqs(get_systems(osys1_12_1)[2]) +@test has_alg_equations(get_systems(get_systems(osys1_12_1)[2])[1]) +@test !has_diff_equations(get_systems(get_systems(osys1_12_1)[2])[1]) +@test has_alg_eqs(get_systems(get_systems(osys1_12_1)[2])[1]) +@test !has_diff_eqs(get_systems(get_systems(osys1_12_1)[2])[1]) + +@test has_alg_equations(osys3_2) +@test has_diff_equations(osys3_2) +@test !has_alg_eqs(osys3_2) +@test has_diff_eqs(osys3_2) +@test has_alg_equations(get_systems(osys3_2)[1]) +@test has_diff_equations(get_systems(osys3_2)[1]) +@test has_alg_eqs(get_systems(osys3_2)[1]) +@test has_diff_eqs(get_systems(osys3_2)[1]) + +@test !has_alg_equations(osys3_33) +@test has_diff_equations(osys3_33) +@test !has_alg_eqs(osys3_33) +@test has_diff_eqs(osys3_33) +@test !has_alg_equations(get_systems(osys3_33)[1]) +@test has_diff_equations(get_systems(osys3_33)[1]) +@test !has_alg_eqs(get_systems(osys3_33)[1]) +@test has_diff_eqs(get_systems(osys3_33)[1]) +@test !has_alg_equations(get_systems(osys3_33)[2]) +@test has_diff_equations(get_systems(osys3_33)[2]) +@test !has_alg_eqs(get_systems(osys3_33)[2]) +@test has_diff_eqs(get_systems(osys3_33)[2]) + + +# Test getters for composed systems. +ns_eqs1 = namespace_equations(osys1) +ns_eqs2 = namespace_equations(osys2) +ns_eqs3 = namespace_equations(osys3) + +isequal(alg_equations(osys1_1), vcat(eqs1, ns_eqs1)) +isequal(diff_equations(osys1_1), []) +isequal(get_alg_eqs(osys1_1), eqs1) +isequal(get_diff_eqs(osys1_1), []) +isequal(alg_equations(get_systems(osys1_1)[1]), eqs1) +isequal(diff_equations(get_systems(osys1_1)[1]), []) +isequal(get_alg_eqs(get_systems(osys1_1)[1]), eqs1) +isequal(get_diff_eqs(get_systems(osys1_1)[1]), []) + +isequal(alg_equations(osys1_12), vcat(eqs1, ns_eqs1, filter(is_alg_equation, ns_eqs2))) +isequal(diff_equations(osys1_12), filter(is_diff_equation, ns_eqs2)) +isequal(get_alg_eqs(osys1_12), eqs1) +isequal(get_diff_eqs(osys1_12), []) +isequal(alg_equations(get_systems(osys1_12)[1]), eqs1) +isequal(diff_equations(get_systems(osys1_12)[1]), []) +isequal(get_alg_eqs(get_systems(osys1_12)[1]), eqs1) +isequal(get_diff_eqs(get_systems(osys1_12)[1]), []) +isequal(alg_equations(get_systems(osys1_12)[2]), eqs2[1:1]) +isequal(diff_equations(get_systems(osys1_12)[2]), eqs2[2:3]) +isequal(get_alg_eqs(get_systems(osys1_12)[2]), eqs2[1:1]) +isequal(get_diff_eqs(get_systems(osys1_12)[2]), eqs2[2:3]) + +isequal(alg_equations(osys3_2), vcat(filter(is_alg_equation, ns_eqs2))) +isequal(diff_equations(osys3_2), vcat(eqs3, filter(is_diff_equation, ns_eqs2))) +isequal(get_alg_eqs(osys3_2), []) +isequal(get_diff_eqs(osys3_2), eqs3) +isequal(alg_equations(get_systems(osys3_2)[1]), eqs2[1:1]) +isequal(diff_equations(get_systems(osys3_2)[1]), eqs2[2:3]) +isequal(get_alg_eqs(get_systems(osys3_2)[1]), eqs2[1:1]) +isequal(get_diff_eqs(get_systems(osys3_2)[1]), eqs2[2:3]) + diff --git a/test/runtests.jl b/test/runtests.jl index ae337ab822..64794581eb 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -17,58 +17,7 @@ end @time begin if GROUP == "All" || GROUP == "InterfaceI" @testset "InterfaceI" begin - @safetestset "Linear Algebra Test" include("linalg.jl") - @safetestset "AbstractSystem Test" include("abstractsystem.jl") - @safetestset "Variable Scope Tests" include("variable_scope.jl") - @safetestset "Symbolic Parameters Test" include("symbolic_parameters.jl") - @safetestset "Parsing Test" include("variable_parsing.jl") - @safetestset "Simplify Test" include("simplify.jl") - @safetestset "Direct Usage Test" include("direct.jl") - @safetestset "System Linearity Test" include("linearity.jl") - @safetestset "Input Output Test" include("input_output_handling.jl") - @safetestset "Clock Test" include("clock.jl") - @safetestset "ODESystem Test" include("odesystem.jl") - @safetestset "Dynamic Quantities Test" include("dq_units.jl") - @safetestset "Unitful Quantities Test" include("units.jl") - @safetestset "LabelledArrays Test" include("labelledarrays.jl") - @safetestset "Mass Matrix Test" include("mass_matrix.jl") - @safetestset "SteadyStateSystem Test" include("steadystatesystems.jl") - @safetestset "SDESystem Test" include("sdesystem.jl") - @safetestset "DDESystem Test" include("dde.jl") - @safetestset "NonlinearSystem Test" include("nonlinearsystem.jl") - @safetestset "InitializationSystem Test" include("initializationsystem.jl") - @safetestset "PDE Construction Test" include("pde.jl") - @safetestset "JumpSystem Test" include("jumpsystem.jl") - @safetestset "Constraints Test" include("constraints.jl") - @safetestset "Reduction Test" include("reduction.jl") - @safetestset "Split Parameters Test" include("split_parameters.jl") - @safetestset "StaticArrays Test" include("static_arrays.jl") - @safetestset "Components Test" include("components.jl") - @safetestset "Model Parsing Test" include("model_parsing.jl") - @safetestset "print_tree" include("print_tree.jl") - @safetestset "Error Handling" include("error_handling.jl") - @safetestset "StructuralTransformations" include("structural_transformation/runtests.jl") - @safetestset "State Selection Test" include("state_selection.jl") - @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 "Test Big System Usage" include("bigsystem.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") - @safetestset "Variable Utils Test" include("variable_utils.jl") - @safetestset "Variable Metadata Test" include("test_variable_metadata.jl") - @safetestset "DAE Jacobians Test" include("dae_jacobian.jl") - @safetestset "Jacobian Sparsity" include("jacobiansparsity.jl") - @safetestset "Modelingtoolkitize Test" include("modelingtoolkitize.jl") - @safetestset "OptimizationSystem Test" include("optimizationsystem.jl") - @safetestset "FuncAffect Test" include("funcaffect.jl") - @safetestset "Constants Test" include("constants.jl") - @safetestset "Parameter Dependency Test" include("parameter_dependencies.jl") - @safetestset "Generate Custom Function Test" include("generate_custom_function.jl") - @safetestset "Initial Values Test" include("initial_values.jl") - @safetestset "Discrete System" include("discrete_system.jl") + @safetestset "Equation Type Accessors Test" include("equation_type_accessors.jl") end end From a662da5f4b58c3fc0d2a93dbc5c108cb2e40ee4c Mon Sep 17 00:00:00 2001 From: Torkel Date: Tue, 2 Apr 2024 08:45:35 -0400 Subject: [PATCH 026/316] docstring fixes --- src/systems/abstractsystem.jl | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/systems/abstractsystem.jl b/src/systems/abstractsystem.jl index dbee3825d5..dbd0eda964 100644 --- a/src/systems/abstractsystem.jl +++ b/src/systems/abstractsystem.jl @@ -2396,7 +2396,7 @@ form of differential. Example: ```julia using ModelingToolkit -import ModelingToolkit: t as nounits_t, D as nounits_D +using ModelingToolkit: t_nounits as t, D_nounits as D @parameters p d @variables X(t) eq1 = D(X) ~ p - d*X @@ -2422,7 +2422,7 @@ any differentials. Example: ```julia using ModelingToolkit -import ModelingToolkit: t as nounits_t, D as nounits_D +using ModelingToolkit: t_nounits as t, D_nounits as D @parameters p d @variables X(t) eq1 = D(X) ~ p - d*X @@ -2445,14 +2445,14 @@ differentials). Example: ```julia using ModelingToolkit -import ModelingToolkit: t as nounits_t, D as nounits_D +using ModelingToolkit: t_nounits as t, D_nounits as D @parameters p d @variables X(t) eq1 = D(X) ~ p - d*X eq2 = 0 ~ p - d*X @named osys = ODESystem([eq1, eq2], t) -alg_equations(osys) # returns `0 ~ p - d*X(t)`. +alg_equations(osys) # returns `[0 ~ p - d*X(t)]`. """ alg_equations(sys::AbstractSystem) = filter(is_alg_equation, equations(sys)) @@ -2464,14 +2464,14 @@ For a system, returns a vector of all its differential equations (i.e. that does Example: ```julia using ModelingToolkit -import ModelingToolkit: t as nounits_t, D as nounits_D +using ModelingToolkit: t_nounits as t, D_nounits as D @parameters p d @variables X(t) eq1 = D(X) ~ p - d*X eq2 = 0 ~ p - d*X @named osys = ODESystem([eq1, eq2], t) -diff_equations(osys) # returns `Differential(t)(X(t)) ~ p - d*X(t)`. +diff_equations(osys) # returns `[Differential(t)(X(t)) ~ p - d*X(t)]`. """ diff_equations(sys::AbstractSystem) = filter(is_diff_equation, equations(sys)) @@ -2484,7 +2484,7 @@ differentials). Example: ```julia using ModelingToolkit -import ModelingToolkit: t as nounits_t, D as nounits_D +using ModelingToolkit: t_nounits as t, D_nounits as D @parameters p d @variables X(t) eq1 = D(X) ~ p - d*X @@ -2506,7 +2506,7 @@ For a system, returns true if it contain at least one differential equation (i.e Example: ```julia using ModelingToolkit -import ModelingToolkit: t as nounits_t, D as nounits_D +using ModelingToolkit: t_nounits as t, D_nounits as D @parameters p d @variables X(t) eq1 = D(X) ~ p - d*X @@ -2530,7 +2530,7 @@ differentials) in its *top-level system*. Example: ```julia using ModelingToolkit -import ModelingToolkit: t as nounits_t, D as nounits_D +using ModelingToolkit: t_nounits as t, D_nounits as D @parameters p d @variables X(t) eq1 = D(X) ~ p - d*X @@ -2555,7 +2555,7 @@ in its *top-level system*. Example: ```julia using ModelingToolkit -import ModelingToolkit: t as nounits_t, D as nounits_D +using ModelingToolkit: t_nounits as t, D_nounits as D @parameters p d @variables X(t) eq1 = D(X) ~ p - d*X @@ -2580,7 +2580,7 @@ differentials) in its *top-level system*. Example: ```julia using ModelingToolkit -import ModelingToolkit: t as nounits_t, D as nounits_D +using ModelingToolkit: t_nounits as t, D_nounits as D @parameters p d @variables X(t) eq1 = D(X) ~ p - d*X @@ -2605,7 +2605,7 @@ differential) in its *top-level system*. Example: ```julia using ModelingToolkit -import ModelingToolkit: t as nounits_t, D as nounits_D +using ModelingToolkit: t_nounits as t, D_nounits as D @parameters p d @variables X(t) eq1 = D(X) ~ p - d*X From f9abb18f5d01de07b8f5dd7bd7cf82d9f3053a4a Mon Sep 17 00:00:00 2001 From: Torkel Date: Tue, 2 Apr 2024 08:48:07 -0400 Subject: [PATCH 027/316] formatting --- src/ModelingToolkit.jl | 2 +- src/systems/abstractsystem.jl | 3 +- test/equation_type_accessors.jl | 36 +++++++++-------------- test/runtests.jl | 52 +++++++++++++++++++++++++++++++++ 4 files changed, 68 insertions(+), 25 deletions(-) diff --git a/src/ModelingToolkit.jl b/src/ModelingToolkit.jl index cde7630073..643ac5ae73 100644 --- a/src/ModelingToolkit.jl +++ b/src/ModelingToolkit.jl @@ -259,7 +259,7 @@ export modelingtoolkitize export generate_initializesystem export alg_equations, diff_equations, has_alg_equations, has_diff_equations -export get_alg_eqs, get_diff_eqs, has_alg_eqs,has_diff_eqs +export get_alg_eqs, get_diff_eqs, has_alg_eqs, has_diff_eqs export @variables, @parameters, @constants, @brownian export @named, @nonamespace, @namespace, extend, compose, complete diff --git a/src/systems/abstractsystem.jl b/src/systems/abstractsystem.jl index dbd0eda964..014cfd5b71 100644 --- a/src/systems/abstractsystem.jl +++ b/src/systems/abstractsystem.jl @@ -2520,7 +2520,6 @@ has_diff_equations(osys2) # returns `false`. """ has_diff_equations(sys::AbstractSystem) = any(is_diff_equation, equations(sys)) - """ get_alg_eqs(sys::AbstractSystem) @@ -2619,4 +2618,4 @@ has_diff_eqs(osys12) # returns `true`. has_diff_eqs(osys21) # returns `false`. ``` """ -has_diff_eqs(sys::AbstractSystem) = any(is_diff_equation, get_eqs(sys)) \ No newline at end of file +has_diff_eqs(sys::AbstractSystem) = any(is_diff_equation, get_eqs(sys)) diff --git a/test/equation_type_accessors.jl b/test/equation_type_accessors.jl index 21f2eb7b9a..cc67fd8281 100644 --- a/test/equation_type_accessors.jl +++ b/test/equation_type_accessors.jl @@ -6,13 +6,13 @@ import ModelingToolkit: t_nounits as t, D_nounits as D, wrap, get_eqs # Creates equations. @variables X(t) Y(t) Z(t) @parameters a b c d -eq1 = X^Z - Z^(X+1) ~ log(X - a + b) * Y +eq1 = X^Z - Z^(X + 1) ~ log(X - a + b) * Y eq2 = X ~ Y^(X + 1) -eq3 = a + b + c + d ~ X*(Y + d*(Y + Z)) +eq3 = a + b + c + d ~ X * (Y + d * (Y + Z)) eq4 = X ~ sqrt(a + Z) + t -eq5 = D(D(X)) ~ a^(2Y) + 3Z*t - 6 -eq6 = X *(Z - Z*(b+X)) ~ c^(X+D(Y)) -eq7 = sqrt(X + c) ~ 2*(Y + log(a + D(Z))) +eq5 = D(D(X)) ~ a^(2Y) + 3Z * t - 6 +eq6 = X * (Z - Z * (b + X)) ~ c^(X + D(Y)) +eq7 = sqrt(X + c) ~ 2 * (Y + log(a + D(Z))) eq8 = -0.1 ~ D(Z) + X @test is_alg_equation(eq1) @@ -34,21 +34,15 @@ eq8 = -0.1 ~ D(Z) + X @test is_diff_equation(eq8) # Creates systems. -eqs1 = [ - X*Y + a ~ Z^3 - X*log(b + Y) - X ~ Z*Y*X + a + b - c*sin(X) + sin(Y) ~ d*(a + X*(b + Y* (c + Z))) -] -eqs2 = [ - X + Y + c ~ b*X^(X + Z + a) - D(X) ~ a*Y + b*X + c*Z - D(Z) + Z*Y ~ X - log(Z) -] -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) -] +eqs1 = [X * Y + a ~ Z^3 - X * log(b + Y) + X ~ Z * Y * X + a + b + c * sin(X) + sin(Y) ~ d * (a + X * (b + Y * (c + Z)))] +eqs2 = [X + Y + c ~ b * X^(X + Z + a) + D(X) ~ a * Y + b * X + c * Z + D(Z) + Z * Y ~ X - log(Z)] +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) @@ -150,7 +144,6 @@ osys3_33 = compose(osys3, [osys3, osys3]) @test !has_alg_eqs(get_systems(osys3_33)[2]) @test has_diff_eqs(get_systems(osys3_33)[2]) - # Test getters for composed systems. ns_eqs1 = namespace_equations(osys1) ns_eqs2 = namespace_equations(osys2) @@ -186,4 +179,3 @@ isequal(alg_equations(get_systems(osys3_2)[1]), eqs2[1:1]) isequal(diff_equations(get_systems(osys3_2)[1]), eqs2[2:3]) isequal(get_alg_eqs(get_systems(osys3_2)[1]), eqs2[1:1]) isequal(get_diff_eqs(get_systems(osys3_2)[1]), eqs2[2:3]) - diff --git a/test/runtests.jl b/test/runtests.jl index 64794581eb..4cabedd118 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -17,6 +17,58 @@ end @time begin if GROUP == "All" || GROUP == "InterfaceI" @testset "InterfaceI" begin + @safetestset "Linear Algebra Test" include("linalg.jl") + @safetestset "AbstractSystem Test" include("abstractsystem.jl") + @safetestset "Variable Scope Tests" include("variable_scope.jl") + @safetestset "Symbolic Parameters Test" include("symbolic_parameters.jl") + @safetestset "Parsing Test" include("variable_parsing.jl") + @safetestset "Simplify Test" include("simplify.jl") + @safetestset "Direct Usage Test" include("direct.jl") + @safetestset "System Linearity Test" include("linearity.jl") + @safetestset "Input Output Test" include("input_output_handling.jl") + @safetestset "Clock Test" include("clock.jl") + @safetestset "ODESystem Test" include("odesystem.jl") + @safetestset "Dynamic Quantities Test" include("dq_units.jl") + @safetestset "Unitful Quantities Test" include("units.jl") + @safetestset "LabelledArrays Test" include("labelledarrays.jl") + @safetestset "Mass Matrix Test" include("mass_matrix.jl") + @safetestset "SteadyStateSystem Test" include("steadystatesystems.jl") + @safetestset "SDESystem Test" include("sdesystem.jl") + @safetestset "DDESystem Test" include("dde.jl") + @safetestset "NonlinearSystem Test" include("nonlinearsystem.jl") + @safetestset "InitializationSystem Test" include("initializationsystem.jl") + @safetestset "PDE Construction Test" include("pde.jl") + @safetestset "JumpSystem Test" include("jumpsystem.jl") + @safetestset "Constraints Test" include("constraints.jl") + @safetestset "Reduction Test" include("reduction.jl") + @safetestset "Split Parameters Test" include("split_parameters.jl") + @safetestset "StaticArrays Test" include("static_arrays.jl") + @safetestset "Components Test" include("components.jl") + @safetestset "Model Parsing Test" include("model_parsing.jl") + @safetestset "print_tree" include("print_tree.jl") + @safetestset "Error Handling" include("error_handling.jl") + @safetestset "StructuralTransformations" include("structural_transformation/runtests.jl") + @safetestset "State Selection Test" include("state_selection.jl") + @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 "Test Big System Usage" include("bigsystem.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") + @safetestset "Variable Utils Test" include("variable_utils.jl") + @safetestset "Variable Metadata Test" include("test_variable_metadata.jl") + @safetestset "DAE Jacobians Test" include("dae_jacobian.jl") + @safetestset "Jacobian Sparsity" include("jacobiansparsity.jl") + @safetestset "Modelingtoolkitize Test" include("modelingtoolkitize.jl") + @safetestset "OptimizationSystem Test" include("optimizationsystem.jl") + @safetestset "FuncAffect Test" include("funcaffect.jl") + @safetestset "Constants Test" include("constants.jl") + @safetestset "Parameter Dependency Test" include("parameter_dependencies.jl") + @safetestset "Generate Custom Function Test" include("generate_custom_function.jl") + @safetestset "Initial Values Test" include("initial_values.jl") + @safetestset "Discrete System" include("discrete_system.jl") @safetestset "Equation Type Accessors Test" include("equation_type_accessors.jl") end end From 751c35c5307dea0fd6f499b8e045202775cab9e4 Mon Sep 17 00:00:00 2001 From: Torkel Date: Tue, 2 Apr 2024 09:37:35 -0400 Subject: [PATCH 028/316] add docs --- docs/src/systems/ODESystem.md | 12 ++++++++++-- docs/src/systems/SDESystem.md | 12 ++++++++++-- test/equation_type_accessors.jl | 1 + test/runtests.jl | 2 +- 4 files changed, 22 insertions(+), 5 deletions(-) diff --git a/docs/src/systems/ODESystem.md b/docs/src/systems/ODESystem.md index 81a8f6f07c..6cc34725c4 100644 --- a/docs/src/systems/ODESystem.md +++ b/docs/src/systems/ODESystem.md @@ -13,8 +13,16 @@ ODESystem - `get_ps(sys)` or `parameters(sys)`: The parameters of the ODE. - `get_iv(sys)`: The independent variable of the ODE. - `get_u0_p(sys, u0map, parammap)` Numeric arrays for the initial condition and parameters given `var => value` maps. - - `continuous_events(sys)`: The set of continuous events in the ODE - - `discrete_events(sys)`: The set of discrete events in the ODE + - `continuous_events(sys)`: The set of continuous events in the ODE. + - `discrete_events(sys)`: The set of discrete events in the ODE. + - `alg_equations(sys)`: The algebraic equations (i.e. that does not contain a differential) that defines the ODE. + - `get_alg_eqs(sys)`: The algebraic equations (i.e. that does not contain a differential) that defines the ODE. Only returns equations of the current-level system. + - `diff_equations(sys)`: The differential equations (i.e. that contain a differential) that defines the ODE. + - `get_diff_eqs(sys)`: The differential equations (i.e. that contain a differential) that defines the ODE. Only returns equations of the current-level system. + - `has_alg_equations(sys)`: Returns `true` if the ODE contains any algebraic equations (i.e. that does not contain a differential). + - `has_alg_eqs(sys)`: Returns `true` if the ODE contains any algebraic equations (i.e. that does not contain a differential). Only considers the current-level system. + - `has_diff_equations(sys)`: Returns `true` if the ODE contains any differential equations (i.e. that does contain a differential). + - `has_diff_eqs(sys)`: Returns `true` if the ODE contains any differential equations (i.e. that does contain a differential). Only considers the current-level system. ## Transformations diff --git a/docs/src/systems/SDESystem.md b/docs/src/systems/SDESystem.md index ad4a0fb59a..5789d2d9cb 100644 --- a/docs/src/systems/SDESystem.md +++ b/docs/src/systems/SDESystem.md @@ -19,8 +19,16 @@ sde = SDESystem(ode, noiseeqs) - `get_unknowns(sys)` or `unknowns(sys)`: The set of unknowns in the SDE. - `get_ps(sys)` or `parameters(sys)`: The parameters of the SDE. - `get_iv(sys)`: The independent variable of the SDE. - - `continuous_events(sys)`: The set of continuous events in the SDE - - `discrete_events(sys)`: The set of discrete events in the SDE + - `continuous_events(sys)`: The set of continuous events in the SDE. + - `discrete_events(sys)`: The set of discrete events in the SDE. + - `alg_equations(sys)`: The algebraic equations (i.e. that does not contain a differential) that defines the ODE. + - `get_alg_eqs(sys)`: The algebraic equations (i.e. that does not contain a differential) that defines the ODE. Only returns equations of the current-level system. + - `diff_equations(sys)`: The differential equations (i.e. that contain a differential) that defines the ODE. + - `get_diff_eqs(sys)`: The differential equations (i.e. that contain a differential) that defines the ODE. Only returns equations of the current-level system. + - `has_alg_equations(sys)`: Returns `true` if the ODE contains any algebraic equations (i.e. that does not contain a differential). + - `has_alg_eqs(sys)`: Returns `true` if the ODE contains any algebraic equations (i.e. that does not contain a differential). Only considers the current-level system. + - `has_diff_equations(sys)`: Returns `true` if the ODE contains any differential equations (i.e. that does contain a differential). + - `has_diff_eqs(sys)`: Returns `true` if the ODE contains any differential equations (i.e. that does contain a differential). Only considers the current-level system. ## Transformations diff --git a/test/equation_type_accessors.jl b/test/equation_type_accessors.jl index cc67fd8281..f118784f44 100644 --- a/test/equation_type_accessors.jl +++ b/test/equation_type_accessors.jl @@ -1,6 +1,7 @@ # Fetch packages. using ModelingToolkit import ModelingToolkit: get_systems, namespace_equations +import ModelingToolkit: is_alg_equation, is_diff_equation import ModelingToolkit: t_nounits as t, D_nounits as D, wrap, get_eqs # Creates equations. diff --git a/test/runtests.jl b/test/runtests.jl index 4cabedd118..701c7933f7 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -69,7 +69,7 @@ end @safetestset "Generate Custom Function Test" include("generate_custom_function.jl") @safetestset "Initial Values Test" include("initial_values.jl") @safetestset "Discrete System" include("discrete_system.jl") - @safetestset "Equation Type Accessors Test" include("equation_type_accessors.jl") + @safetestset "Equation Type Accessors Test" include("equation_type_accessors.jl") end end From 2a0938c69d88cd2bd2d9714cec7313725c3c99f5 Mon Sep 17 00:00:00 2001 From: David Widmann Date: Tue, 2 Apr 2024 16:27:22 +0200 Subject: [PATCH 029/316] Support docstrings for `@connector`s and `@component`s (#2602) * Support docstrings for `@connector`s and `@component`s * Fix format --- src/systems/abstractsystem.jl | 2 +- test/components.jl | 22 ++++++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/src/systems/abstractsystem.jl b/src/systems/abstractsystem.jl index 014cfd5b71..906240522d 100644 --- a/src/systems/abstractsystem.jl +++ b/src/systems/abstractsystem.jl @@ -1555,7 +1555,7 @@ function component_post_processing(expr, isconnector) args = sig.args[2:end] quote - function $fname($(args...)) + $Base.@__doc__ function $fname($(args...)) # we need to create a closure to escape explicit return in `body`. res = (() -> $body)() if $isdefined(res, :gui_metadata) && $getfield(res, :gui_metadata) === nothing diff --git a/test/components.jl b/test/components.jl index d4bedb8eb7..d9233558c3 100644 --- a/test/components.jl +++ b/test/components.jl @@ -298,3 +298,25 @@ rc_eqs = [connect(capacitor.n, resistor.p) sys = structural_simplify(rc_model) prob = ODEProblem(sys, u0, (0, 10.0)) sol = solve(prob, Tsit5()) + +@testset "docstrings (#1155)" begin + """ + Hey there, Pin1! + """ + @connector function Pin1(; name) + @variables t + sts = @variables v(t)=1.0 i(t)=1.0 + ODESystem(Equation[], t, sts, []; name = name) + end + @test string(Base.doc(Pin1)) == "Hey there, Pin1!\n" + + """ + Hey there, Pin2! + """ + @component function Pin2(; name) + @variables t + sts = @variables v(t)=1.0 i(t)=1.0 + ODESystem(Equation[], t, sts, []; name = name) + end + @test string(Base.doc(Pin2)) == "Hey there, Pin2!\n" +end From 0a290b73385f255b7eaaa129d20c33efb3470808 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Thu, 4 Apr 2024 17:23:04 +0530 Subject: [PATCH 030/316] refactor: improve replace, remake_buffer --- src/systems/parameter_buffer.jl | 57 ++++++++++++--------------------- 1 file changed, 21 insertions(+), 36 deletions(-) diff --git a/src/systems/parameter_buffer.jl b/src/systems/parameter_buffer.jl index b8fa6a3b12..7ce13dda9b 100644 --- a/src/systems/parameter_buffer.jl +++ b/src/systems/parameter_buffer.jl @@ -192,10 +192,7 @@ for (Portion, field) in [(SciMLStructures.Tunable, :tunable) end @eval function SciMLStructures.replace!(::$Portion, p::MTKParameters, newvals) - src = split_into_buffers(newvals, p.$field) - for i in 1:length(p.$field) - (p.$field)[i] .= src[i] - end + update_tuple_of_buffers(newvals, p.$field) if p.dependent_update_iip !== nothing p.dependent_update_iip(ArrayPartition(p.dependent), p...) end @@ -318,44 +315,32 @@ function _set_parameter_unchecked!( p.dependent_update_iip(ArrayPartition(p.dependent), p...) end -function SymbolicIndexingInterface.remake_buffer(sys, oldbuf::MTKParameters, vals::Dict) - buftypes = Dict{Tuple{Any, Int}, Any}() - for (p, val) in vals - (idx = parameter_index(sys, p)) isa ParameterIndex || continue - k = (idx.portion, idx.idx[1]) - buftypes[k] = Union{get(buftypes, k, Union{}), typeof(val)} +function narrow_buffer_type(buffer::Vector) + type = Union{} + for x in buffer + type = Union{type, typeof(x)} end + return convert(Vector{type}, buffer) +end + +function SymbolicIndexingInterface.remake_buffer(sys, oldbuf::MTKParameters, vals::Dict) + newbuf = @set oldbuf.tunable = similar.(oldbuf.tunable, Any) + @set! newbuf.discrete = similar.(newbuf.discrete, Any) + @set! newbuf.constant = similar.(newbuf.constant, Any) + @set! newbuf.nonnumeric = similar.(newbuf.nonnumeric, Any) - newbufs = [] - for (portion, old) in [(SciMLStructures.Tunable(), oldbuf.tunable) - (SciMLStructures.Discrete(), oldbuf.discrete) - (SciMLStructures.Constants(), oldbuf.constant) - (NONNUMERIC_PORTION, oldbuf.nonnumeric)] - if isempty(old) - push!(newbufs, old) - continue - end - new = Any[copy(i) for i in old] - for i in eachindex(new) - buftype = get(buftypes, (portion, i), eltype(new[i])) - new[i] = similar(new[i], buftype) - end - push!(newbufs, Tuple(new)) - end - tmpbuf = MTKParameters( - newbufs[1], newbufs[2], newbufs[3], oldbuf.dependent, newbufs[4], nothing, nothing) for (p, val) in vals _set_parameter_unchecked!( - tmpbuf, val, parameter_index(sys, p); update_dependent = false) + newbuf, val, parameter_index(sys, p); update_dependent = false) end - if oldbuf.dependent_update_oop !== nothing - dependent = oldbuf.dependent_update_oop(tmpbuf...) - else - dependent = () + + @set! newbuf.tunable = narrow_buffer_type.(newbuf.tunable) + @set! newbuf.discrete = narrow_buffer_type.(newbuf.discrete) + @set! newbuf.constant = narrow_buffer_type.(newbuf.constant) + @set! newbuf.nonnumeric = narrow_buffer_type.(newbuf.nonnumeric) + if newbuf.dependent_update_oop !== nothing + @set! newbuf.dependent = newbuf.dependent_update_oop(newbuf...) end - newbuf = MTKParameters(newbufs[1], newbufs[2], newbufs[3], dependent, newbufs[4], - oldbuf.dependent_update_iip, oldbuf.dependent_update_oop) - return newbuf end _subarrays(v::AbstractVector) = isempty(v) ? () : (v,) From 7b0fb995a320acfcd4deb6d8854a7f3706a441fb Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Wed, 3 Apr 2024 16:47:27 +0530 Subject: [PATCH 031/316] fix: fix callback codegen, observed eqs with non-scalarized symbolic arrays --- .../symbolics_tearing.jl | 45 +++++++++++++++---- src/systems/abstractsystem.jl | 27 ++++++----- src/systems/callbacks.jl | 17 +++---- .../optimization/constraints_system.jl | 5 ++- src/utils.jl | 9 ++-- test/odesystem.jl | 27 +++++++++++ 6 files changed, 96 insertions(+), 34 deletions(-) diff --git a/src/structural_transformation/symbolics_tearing.jl b/src/structural_transformation/symbolics_tearing.jl index 4908140fb9..f2f27e8606 100644 --- a/src/structural_transformation/symbolics_tearing.jl +++ b/src/structural_transformation/symbolics_tearing.jl @@ -551,7 +551,43 @@ function tearing_reassemble(state::TearingState, var_eq_matching, end sys = state.sys + + obs_sub = dummy_sub + for eq in neweqs + isdiffeq(eq) || continue + obs_sub[eq.lhs] = eq.rhs + end + # TODO: compute the dependency correctly so that we don't have to do this + obs = [fast_substitute(observed(sys), obs_sub); subeqs] + + # HACK: Substitute non-scalarized symbolic arrays of observed variables + # E.g. if `p[1] ~ (...)` and `p[2] ~ (...)` then substitute `p => [p[1], p[2]]` in all equations + # ideally, we want to support equations such as `p ~ [p[1], p[2]]` which will then be handled + # by the topological sorting and dependency identification pieces + obs_arr_subs = Dict() + + for eq in obs + lhs = eq.lhs + istree(lhs) || continue + operation(lhs) === getindex || continue + Symbolics.shape(lhs) !== Symbolics.Unknown() || continue + arg1 = arguments(lhs)[1] + haskey(obs_arr_subs, arg1) && continue + obs_arr_subs[arg1] = [arg1[i] for i in eachindex(arg1)] + end + for i in eachindex(neweqs) + neweqs[i] = fast_substitute(neweqs[i], obs_arr_subs; operator = Symbolics.Operator) + end + for i in eachindex(obs) + obs[i] = fast_substitute(obs[i], obs_arr_subs; operator = Symbolics.Operator) + end + for i in eachindex(subeqs) + subeqs[i] = fast_substitute(subeqs[i], obs_arr_subs; operator = Symbolics.Operator) + end + @set! sys.eqs = neweqs + @set! sys.observed = obs + unknowns = Any[v for (i, v) in enumerate(fullvars) if diff_to_var[i] === nothing && ispresent(i)] @@ -563,15 +599,6 @@ function tearing_reassemble(state::TearingState, var_eq_matching, @set! sys.unknowns = unknowns @set! sys.substitutions = Substitutions(subeqs, deps) - obs_sub = dummy_sub - for eq in equations(sys) - isdiffeq(eq) || continue - obs_sub[eq.lhs] = eq.rhs - end - # TODO: compute the dependency correctly so that we don't have to do this - obs = [fast_substitute(observed(sys), obs_sub); subeqs] - @set! sys.observed = obs - # Only makes sense for time-dependent # TODO: generalize to SDE if sys isa ODESystem diff --git a/src/systems/abstractsystem.jl b/src/systems/abstractsystem.jl index 906240522d..6d7d517968 100644 --- a/src/systems/abstractsystem.jl +++ b/src/systems/abstractsystem.jl @@ -153,15 +153,15 @@ generate_custom_function(sys::AbstractSystem, exprs, dvs = unknowns(sys), Generate a function to evaluate `exprs`. `exprs` is a symbolic expression or array of symbolic expression involving symbolic variables in `sys`. The symbolic variables -may be subsetted using `dvs` and `ps`. All `kwargs` except `postprocess_fbody` and `states` -are passed to the internal [`build_function`](@ref) call. The returned function can be called -as `f(u, p, t)` or `f(du, u, p, t)` for time-dependent systems and `f(u, p)` or `f(du, u, p)` -for time-independent systems. If `split=true` (the default) was passed to [`complete`](@ref), +may be subsetted using `dvs` and `ps`. All `kwargs` are passed to the internal +[`build_function`](@ref) call. The returned function can be called as `f(u, p, t)` or +`f(du, u, p, t)` for time-dependent systems and `f(u, p)` or `f(du, u, p)` for +time-independent systems. If `split=true` (the default) was passed to [`complete`](@ref), [`structural_simplify`](@ref) or [`@mtkbuild`](@ref), `p` is expected to be an `MTKParameters` object. """ function generate_custom_function(sys::AbstractSystem, exprs, dvs = unknowns(sys), - ps = parameters(sys); wrap_code = nothing, kwargs...) + ps = parameters(sys); wrap_code = nothing, postprocess_fbody = nothing, states = nothing, kwargs...) if !iscomplete(sys) error("A completed system is required. Call `complete` or `structural_simplify` on the system.") end @@ -170,16 +170,21 @@ function generate_custom_function(sys::AbstractSystem, exprs, dvs = unknowns(sys if wrap_code === nothing wrap_code = isscalar ? identity : (identity, identity) end - pre, sol_states = get_substitutions_and_solved_unknowns(sys) - + pre, sol_states = get_substitutions_and_solved_unknowns(sys, isscalar ? [exprs] : exprs) + if postprocess_fbody === nothing + postprocess_fbody = pre + end + if states === nothing + states = sol_states + end if is_time_dependent(sys) return build_function(exprs, dvs, p..., get_iv(sys); kwargs..., - postprocess_fbody = pre, - states = sol_states, + postprocess_fbody, + states, wrap_code = wrap_code .∘ wrap_mtkparameters(sys, isscalar) .∘ wrap_array_vars(sys, exprs; dvs) ) @@ -188,8 +193,8 @@ function generate_custom_function(sys::AbstractSystem, exprs, dvs = unknowns(sys dvs, p...; kwargs..., - postprocess_fbody = pre, - states = sol_states, + postprocess_fbody, + states, wrap_code = wrap_code .∘ wrap_mtkparameters(sys, isscalar) .∘ wrap_array_vars(sys, exprs; dvs) ) diff --git a/src/systems/callbacks.jl b/src/systems/callbacks.jl index a031840f83..634cf6a01b 100644 --- a/src/systems/callbacks.jl +++ b/src/systems/callbacks.jl @@ -388,6 +388,7 @@ function compile_affect(eqs::Vector{Equation}, sys, dvs, ps; outputidxs = nothin return (args...) -> () # We don't do anything in the callback, we're just after the event end else + eqs = flatten_equations(eqs) rhss = map(x -> x.rhs, eqs) outvar = :u if outputidxs === nothing @@ -457,7 +458,7 @@ end function generate_rootfinding_callback(cbs, sys::AbstractODESystem, dvs = unknowns(sys), ps = full_parameters(sys); kwargs...) - eqs = map(cb -> cb.eqs, cbs) + eqs = map(cb -> flatten_equations(cb.eqs), cbs) num_eqs = length.(eqs) (isempty(eqs) || sum(num_eqs) == 0) && return nothing # fuse equations to create VectorContinuousCallback @@ -471,12 +472,8 @@ function generate_rootfinding_callback(cbs, sys::AbstractODESystem, dvs = unknow rhss = map(x -> x.rhs, eqs) root_eq_vars = unique(collect(Iterators.flatten(map(ModelingToolkit.vars, rhss)))) - u = map(x -> time_varying_as_func(value(x), sys), dvs) - p = map.(x -> time_varying_as_func(value(x), sys), reorder_parameters(sys, ps)) - t = get_iv(sys) - pre = get_preprocess_constants(rhss) - rf_oop, rf_ip = build_function(rhss, u, p..., t; expression = Val{false}, - postprocess_fbody = pre, kwargs...) + rf_oop, rf_ip = generate_custom_function(sys, rhss, dvs, ps; expression = Val{false}, + kwargs...) affect_functions = map(cbs) do cb # Keep affect function separate eq_aff = affects(cb) @@ -487,16 +484,16 @@ function generate_rootfinding_callback(cbs, sys::AbstractODESystem, dvs = unknow 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) + rf_ip(tmp, u, parameter_values(integ), t) tmp[1] else - rf_oop(u, parameter_values(integ)..., t) + rf_oop(u, parameter_values(integ), t) end end ContinuousCallback(cond, affect_functions[]) else cond = function (out, u, t, integ) - rf_ip(out, u, parameter_values(integ)..., t) + rf_ip(out, u, parameter_values(integ), t) end # since there may be different number of conditions and affects, diff --git a/src/systems/optimization/constraints_system.jl b/src/systems/optimization/constraints_system.jl index 32055e7e7c..77d8dc6406 100644 --- a/src/systems/optimization/constraints_system.jl +++ b/src/systems/optimization/constraints_system.jl @@ -226,12 +226,15 @@ 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) +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_contants(exprs)] + end # Swap constants for their values cmap = map(x -> x ~ getdefault(x), cs) return cmap, cs diff --git a/src/utils.jl b/src/utils.jl index 97dea54d89..363c43378e 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -564,19 +564,22 @@ function empty_substitutions(sys) isnothing(subs) || isempty(subs.deps) end -function get_cmap(sys) +function get_cmap(sys, exprs = nothing) #Inject substitutions for constants => values cs = collect_constants([get_eqs(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 -function get_substitutions_and_solved_unknowns(sys; no_postprocess = false) - cmap, cs = get_cmap(sys) +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) diff --git a/test/odesystem.jl b/test/odesystem.jl index f45987cdc5..118056218b 100644 --- a/test/odesystem.jl +++ b/test/odesystem.jl @@ -995,3 +995,30 @@ let # Issue https://github.com/SciML/ModelingToolkit.jl/issues/2322 sol = solve(prob, Rodas4()) @test sol(1)[]≈0.6065307685451087 rtol=1e-4 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]]) +prob = ODEProblem(sys, [x => 1.0], (0.0, 10.0)) +@test_nowarn solve(prob, Tsit5()) + +# Issue#2383 +@variables x(t)[1:3] +@parameters p[1:3, 1:3] +eqs = [ + D(x) ~ p * x +] +@mtkbuild sys = ODESystem(eqs, t; continuous_events = [[norm(x) ~ 3.0] => [x ~ ones(3)]]) +# array affect equations used to not work +prob1 = @test_nowarn ODEProblem(sys, [x => ones(3)], (0.0, 10.0), [p => ones(3, 3)]) +sol1 = @test_nowarn solve(prob1, Tsit5()) + +# array condition equations also used to not work +@mtkbuild sys = ODESystem( + eqs, t; continuous_events = [[x ~ sqrt(3) * ones(3)] => [x ~ ones(3)]]) +# array affect equations used to not work +prob2 = @test_nowarn ODEProblem(sys, [x => ones(3)], (0.0, 10.0), [p => ones(3, 3)]) +sol2 = @test_nowarn solve(prob2, Tsit5()) + +@test sol1 ≈ sol2 From ca2654ec0130372f32326af202a92caa95741f28 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Thu, 4 Apr 2024 17:49:21 +0530 Subject: [PATCH 032/316] fix: fix initialization with dummy derivatives of multidimensional arrays --- src/systems/diffeqs/abstractodesystem.jl | 1 + src/variables.jl | 1 + test/initial_values.jl | 7 +++++++ 3 files changed, 9 insertions(+) diff --git a/src/systems/diffeqs/abstractodesystem.jl b/src/systems/diffeqs/abstractodesystem.jl index 1feffb42be..e40407b002 100644 --- a/src/systems/diffeqs/abstractodesystem.jl +++ b/src/systems/diffeqs/abstractodesystem.jl @@ -857,6 +857,7 @@ function process_DEProblem(constructor, sys::AbstractODESystem, u0map, parammap; varmap = u0map === nothing || isempty(u0map) || eltype(u0map) <: Number ? defaults(sys) : merge(defaults(sys), todict(u0map)) + varmap = canonicalize_varmap(varmap) varlist = collect(map(unwrap, dvs)) missingvars = setdiff(varlist, collect(keys(varmap))) diff --git a/src/variables.jl b/src/variables.jl index 7a32cfc9b5..ed9edcffb4 100644 --- a/src/variables.jl +++ b/src/variables.jl @@ -216,6 +216,7 @@ function canonicalize_varmap(varmap; toterm = Symbolics.diff2term) if Symbolics.isarraysymbolic(k) && Symbolics.shape(k) !== Symbolics.Unknown() for i in eachindex(k) new_varmap[k[i]] = v[i] + new_varmap[toterm(k[i])] = v[i] end end end diff --git a/test/initial_values.jl b/test/initial_values.jl index 4a72bc72ef..8f9607089b 100644 --- a/test/initial_values.jl +++ b/test/initial_values.jl @@ -67,3 +67,10 @@ varmap = Dict(p => ones(3), q => 2ones(3)) cvarmap = ModelingToolkit.canonicalize_varmap(varmap) target_varmap = Dict(p => ones(3), q => 2ones(3), q[1] => 2.0, q[2] => 2.0, q[3] => 2.0) @test cvarmap == target_varmap + +# Initialization of ODEProblem with dummy derivatives of multidimensional arrays +# Issue#1283 +@variables z(t)[1:2, 1:2] +eqs = [D(D(z)) ~ ones(2, 2)] +@mtkbuild sys = ODESystem(eqs, t) +@test_nowarn ODEProblem(sys, [z => zeros(2, 2), D(z) => ones(2, 2)], (0.0, 10.0)) From 4047e4db34581ca6083cda55fe46096d2e99232d Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Thu, 4 Apr 2024 18:40:59 +0530 Subject: [PATCH 033/316] test: add broken test involving scalar-valued arrayops This needs to be fixed by Symbolics --- test/odesystem.jl | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/test/odesystem.jl b/test/odesystem.jl index 118056218b..542c3a555b 100644 --- a/test/odesystem.jl +++ b/test/odesystem.jl @@ -1022,3 +1022,11 @@ prob2 = @test_nowarn ODEProblem(sys, [x => ones(3)], (0.0, 10.0), [p => ones(3, sol2 = @test_nowarn solve(prob2, Tsit5()) @test sol1 ≈ sol2 + +# Requires fix in symbolics for `linear_expansion(p * x, D(y))` +@test_broken 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 ODEProblem(sys, [x => ones(3), y => 2], (0.0, 10.0), [p => ones(3, 3)]) +end From d927e04adb1e16baf5bd3cc2bb45a7bee52d034b Mon Sep 17 00:00:00 2001 From: Christopher Rackauckas Date: Fri, 5 Apr 2024 09:40:39 -0400 Subject: [PATCH 034/316] Update src/systems/optimization/constraints_system.jl --- src/systems/optimization/constraints_system.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/systems/optimization/constraints_system.jl b/src/systems/optimization/constraints_system.jl index 77d8dc6406..7fde00e2f4 100644 --- a/src/systems/optimization/constraints_system.jl +++ b/src/systems/optimization/constraints_system.jl @@ -233,7 +233,7 @@ function get_cmap(sys::ConstraintsSystem, exprs = nothing) cs = [cs; collect_constants(get_substitutions(sys).subs)] end if exprs !== nothing - cs = [cs; collect_contants(exprs)] + cs = [cs; collect_constants(exprs)] end # Swap constants for their values cmap = map(x -> x ~ getdefault(x), cs) From a28c12b2180da6c6e0a7270f89e10922fcba046c Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Fri, 5 Apr 2024 19:53:38 +0530 Subject: [PATCH 035/316] feat: improve error messages for missing variables and subsystems --- src/systems/diffeqs/odesystem.jl | 1 + src/systems/diffeqs/sdesystem.jl | 2 + .../discrete_system/discrete_system.jl | 1 + src/systems/nonlinear/nonlinearsystem.jl | 1 + .../optimization/constraints_system.jl | 1 + .../optimization/optimizationsystem.jl | 1 + src/utils.jl | 33 +++++- test/variable_scope.jl | 104 +++++++++++++++++- 8 files changed, 139 insertions(+), 5 deletions(-) diff --git a/src/systems/diffeqs/odesystem.jl b/src/systems/diffeqs/odesystem.jl index 34773524c9..89c2ab9479 100644 --- a/src/systems/diffeqs/odesystem.jl +++ b/src/systems/diffeqs/odesystem.jl @@ -188,6 +188,7 @@ struct ODESystem <: AbstractODESystem check_parameters(ps, iv) check_equations(deqs, iv) check_equations(equations(cevents), iv) + check_namespacing([deqs; equations(cevents)], dvs, ps, iv; systems) end if checks == true || (checks & CheckUnits) > 0 u = __get_unit_type(dvs, ps, iv) diff --git a/src/systems/diffeqs/sdesystem.jl b/src/systems/diffeqs/sdesystem.jl index b021a201fe..321330373e 100644 --- a/src/systems/diffeqs/sdesystem.jl +++ b/src/systems/diffeqs/sdesystem.jl @@ -141,6 +141,8 @@ struct SDESystem <: AbstractODESystem check_parameters(ps, iv) check_equations(deqs, iv) check_equations(equations(cevents), iv) + check_namespacing( + [deqs; equations(cevents); vec(unwrap.(neqs))], dvs, ps, iv; systems) end if checks == true || (checks & CheckUnits) > 0 u = __get_unit_type(dvs, ps, iv) diff --git a/src/systems/discrete_system/discrete_system.jl b/src/systems/discrete_system/discrete_system.jl index bd72c533d0..0d32f2a496 100644 --- a/src/systems/discrete_system/discrete_system.jl +++ b/src/systems/discrete_system/discrete_system.jl @@ -103,6 +103,7 @@ struct DiscreteSystem <: AbstractTimeDependentSystem if checks == true || (checks & CheckComponents) > 0 check_variables(dvs, iv) check_parameters(ps, iv) + check_namespacing(discreteEqs, dvs, ps, iv; systems) end if checks == true || (checks & CheckUnits) > 0 u = __get_unit_type(dvs, ps, iv) diff --git a/src/systems/nonlinear/nonlinearsystem.jl b/src/systems/nonlinear/nonlinearsystem.jl index cf9e88c686..b5d7f435f0 100644 --- a/src/systems/nonlinear/nonlinearsystem.jl +++ b/src/systems/nonlinear/nonlinearsystem.jl @@ -95,6 +95,7 @@ struct NonlinearSystem <: AbstractTimeIndependentSystem if checks == true || (checks & CheckUnits) > 0 u = __get_unit_type(unknowns, ps) check_units(u, eqs) + check_namespacing(eqs, unknowns, ps, nothing; systems) end new(tag, eqs, unknowns, ps, var_to_name, observed, jac, name, systems, defaults, connector_type, metadata, gui_metadata, tearing_state, substitutions, complete, diff --git a/src/systems/optimization/constraints_system.jl b/src/systems/optimization/constraints_system.jl index 7fde00e2f4..7df6179b1e 100644 --- a/src/systems/optimization/constraints_system.jl +++ b/src/systems/optimization/constraints_system.jl @@ -88,6 +88,7 @@ struct ConstraintsSystem <: AbstractTimeIndependentSystem if checks == true || (checks & CheckUnits) > 0 u = __get_unit_type(unknowns, ps) check_units(u, constraints) + check_namespacing(constraints, unknowns, ps, nothing; systems) end new(tag, constraints, unknowns, ps, var_to_name, observed, jac, name, systems, defaults, diff --git a/src/systems/optimization/optimizationsystem.jl b/src/systems/optimization/optimizationsystem.jl index faa324f4fd..c8cedc72c6 100644 --- a/src/systems/optimization/optimizationsystem.jl +++ b/src/systems/optimization/optimizationsystem.jl @@ -74,6 +74,7 @@ struct OptimizationSystem <: AbstractOptimizationSystem unwrap(op) isa Symbolic && check_units(u, op) check_units(u, observed) check_units(u, constraints) + check_namespacing([op; constraints], unknowns, ps, nothing; systems) end new(tag, op, unknowns, ps, var_to_name, observed, constraints, name, systems, defaults, metadata, gui_metadata, complete, diff --git a/src/utils.jl b/src/utils.jl index 363c43378e..e33f51ef63 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -182,6 +182,35 @@ function check_equations(eqs, iv) throw(ArgumentError("Differential w.r.t. variable ($single_iv) other than the independent variable ($iv) are not allowed.")) end end + +function check_namespacing(eqs, dvs, ps, iv; systems = []) + eqsyms = vars(eqs; op = Nothing) + syssyms = Set{Symbol}() + foldl(Iterators.flatten((dvs, ps)); init = syssyms) do prev, sym + sym = unwrap(sym) + while istree(sym) && operation(sym) isa Operator + sym = only(arguments(sym)) + end + push!(prev, getname(sym)) + prev + end + subsysnames = get_name.(systems) + if iv !== nothing + push!(syssyms, getname(iv)) + end + for sym in eqsyms + symname = getname(sym) + strname = String(symname) + if occursin('₊', strname) + subsysname = Symbol(first(split(strname, '₊'))) + subsysname in subsysnames && continue + error("Unexpected variable $sym. Expected system to have subsystem with name $subsysname.") + end + symname in syssyms && continue + error("Symbol $sym does not occur in the system.") + end +end + """ Get all the independent variables with respect to which differentials are taken. """ @@ -348,8 +377,8 @@ end vars(exprs::Num; op = Differential) = vars(unwrap(exprs); op) vars(exprs::Symbolics.Arr; op = Differential) = vars(unwrap(exprs); op) vars(exprs; op = Differential) = foldl((x, y) -> vars!(x, y; op = op), exprs; init = Set()) -vars(eq::Equation; op = Differential) = vars!(Set(), eq; op = op) -function vars!(vars, eq::Equation; op = Differential) +vars(eq::Union{Equation, Inequality}; op = Differential) = vars!(Set(), eq; op = op) +function vars!(vars, eq::Union{Equation, Inequality}; op = Differential) (vars!(vars, eq.lhs; op = op); vars!(vars, eq.rhs; op = op); vars) end function vars!(vars, O; op = Differential) diff --git a/test/variable_scope.jl b/test/variable_scope.jl index b80c004a2b..045f06f443 100644 --- a/test/variable_scope.jl +++ b/test/variable_scope.jl @@ -1,9 +1,8 @@ using ModelingToolkit -using ModelingToolkit: SymScope +using ModelingToolkit: SymScope, t_nounits as t, D_nounits as D using Symbolics: arguments, value using Test -@parameters t @variables a b(t) c d e(t) b = ParentScope(b) @@ -52,7 +51,7 @@ end @test renamed([:foo :bar :baz], c) == Symbol("foo₊c") @test renamed([:foo :bar :baz], d) == :d -@parameters t a b c d e f +@parameters a b c d e f p = [a ParentScope(b) ParentScope(ParentScope(c)) @@ -73,3 +72,102 @@ ps = ModelingToolkit.getname.(parameters(level3)) @test isequal(ps[4], :level2₊level0₊d) @test isequal(ps[5], :level1₊level0₊e) @test isequal(ps[6], :f) + +@variables x(t) y(t)[1:2] +@parameters p q[1:2] + +@test_throws ["Symbol", "x(t)", "does not occur"] ODESystem( + [D(x) ~ p], t, [], [p]; name = :foo) +@test_nowarn ODESystem([D(x) ~ p], t, [x], [p]; name = :foo) +@test_throws ["Symbol", "y(t)", "does not occur"] ODESystem( + D(y) ~ q, t, [], [q]; name = :foo) +@test_nowarn ODESystem(D(y) ~ q, t, [y], [q]; name = :foo) +@test_throws ["Symbol", "y(t)", "[1]", "does not occur"] ODESystem( + D(y[1]) ~ x, t, [x], []; name = :foo) +@test_nowarn ODESystem(D(y[1]) ~ x, t, [x, y], []; name = :foo) +@test_throws ["Symbol", "p", "does not occur"] ODESystem(D(x) ~ p, t, [x], []; name = :foo) +@test_nowarn ODESystem(D(x) ~ p, t, [x], [p]; name = :foo) +@test_throws ["Symbol", "q", "does not occur"] ODESystem(D(y) ~ q, t, [y], []; name = :foo) +@test_nowarn ODESystem(D(y) ~ q, t, [y], [q]; name = :foo) +@test_throws ["Symbol", "q", "[1]", "does not occur"] ODESystem( + D(y[1]) ~ q[1], t, [y], []; name = :foo) +@test_nowarn ODESystem(D(y[1]) ~ q[1], t, [y], [q]; name = :foo) +@test_throws ["Symbol", "x(t)", "does not occur"] ODESystem( + Equation[], t, [], [p]; name = :foo, continuous_events = [[x ~ 0.0] => [p ~ 1.0]]) +@test_nowarn ODESystem( + Equation[], t, [x], [p]; name = :foo, continuous_events = [[x ~ 0.0] => [p ~ 1.0]]) + +@named sys1 = ODESystem(Equation[], t, [x, y], [p, q]) +@test_throws ["Unexpected", "sys1₊x(t)", "subsystem with name sys1"] ODESystem( + [D(x) ~ sys1.x], t; name = :sys2) +@test_nowarn ODESystem([D(x) ~ sys1.x], t; name = :sys2, systems = [sys1]) +@test_throws ["Unexpected", "sys1₊y(t)", "subsystem with name sys1"] ODESystem( + [D(x) ~ sum(sys1.y)], t; name = :sys2) +@test_nowarn ODESystem([D(x) ~ sum(sys1.y)], t; name = :sys2, systems = [sys1]) +@test_throws ["Unexpected", "sys1₊y(t)", "[1]", "subsystem with name sys1"] ODESystem( + D(x) ~ sys1.y[1], t; name = :sys2) +@test_nowarn ODESystem(D(x) ~ sys1.y[1], t; name = :sys2, systems = [sys1]) +@test_throws ["Unexpected", "sys1₊p", "subsystem with name sys1"] ODESystem( + D(x) ~ sys1.p, t; name = :sys2) +@test_nowarn ODESystem(D(x) ~ sys1.p, t; name = :sys2, systems = [sys1]) +@test_throws ["Unexpected", "sys1₊q", "subsystem with name sys1"] ODESystem( + D(y) ~ sys1.q, t; name = :sys2) +@test_nowarn ODESystem(D(y) ~ sys1.q, t; name = :sys2, systems = [sys1]) +@test_throws ["Unexpected", "sys1₊q", "[1]", "subsystem with name sys1"] ODESystem( + D(x) ~ sys1.q[1], t; name = :sys2) +@test_nowarn ODESystem(D(x) ~ sys1.q[1], t; name = :sys2, systems = [sys1]) +@test_throws ["Unexpected", "sys1₊x(t)", "subsystem with name sys1"] ODESystem( + Equation[], t, [], [p]; name = :sys2, continuous_events = [[sys1.x ~ 0] => [p ~ 1.0]]) +@test_nowarn ODESystem(Equation[], t, [], [p]; name = :sys2, + continuous_events = [[sys1.x ~ 0] => [p ~ 1.0]], systems = [sys1]) + +# Ensure SDESystem checks noise eqs as well +@test_throws ["Symbol", "x(t)", "does not occur"] SDESystem( + Equation[], [0.1x], t, [], []; name = :foo) +@test_nowarn SDESystem(Equation[], [0.1x], t, [x], []; name = :foo) +@named sys1 = SDESystem(Equation[], [], t, [x], []) +@test_throws ["Unexpected", "sys1₊x(t)", "subsystem with name sys1"] SDESystem( + Equation[], [0.1sys1.x], t, [], []; name = :foo) +@test_nowarn SDESystem(Equation[], [0.1sys1.x], t, [], []; name = :foo, systems = [sys1]) + +# Ensure DiscreteSystem checks work +k = ShiftIndex(t) +@test_throws ["Symbol", "x(t)", "does not occur"] DiscreteSystem( + [x ~ x(k - 1) + x(k - 2)], t, [], []; name = :foo) +@test_nowarn DiscreteSystem([x ~ x(k - 1) + x(k - 2)], t; name = :foo) +@named sys1 = DiscreteSystem(Equation[], t, [x], []) +@test_throws ["Unexpected", "sys1₊x(t)", "subsystem with name sys1"] DiscreteSystem( + [x ~ x(k - 1) + sys1.x(k - 2)], t, [x], []; name = :sys2) +@test_nowarn DiscreteSystem( + [x ~ x(k - 1) + sys1.x(k - 2)], t, [x], []; name = :sys2, systems = [sys1]) + +# Ensure NonlinearSystem checks work +@variables x +@test_throws ["Symbol", "x", "does not occur"] NonlinearSystem( + [0 ~ 2x + 3], [], []; name = :foo) +@test_nowarn NonlinearSystem([0 ~ 2x + 3], [x], []; name = :foo) +@named sys1 = NonlinearSystem(Equation[], [x], []) +@test_throws ["Unexpected", "sys1₊x", "subsystem with name sys1"] NonlinearSystem( + [0 ~ sys1.x + 3], [], []; name = :foo) +@test_nowarn NonlinearSystem([0 ~ sys1.x + 3], [], []; name = :foo, systems = [sys1]) + +# Ensure ConstraintsSystem checks work +@test_throws ["Symbol", "x", "does not occur"] ConstraintsSystem( + [0 ~ x^2 - 3], [], []; name = :foo) +@test_nowarn ConstraintsSystem([0 ~ x^2 - 3], [x], []; name = :foo) +@test_throws ["Symbol", "x", "does not occur"] ConstraintsSystem( + [Inequality(x^2, 3, <)], [], []; name = :foo) +@test_nowarn ConstraintsSystem([Inequality(x^2, 3, <)], [x], []; name = :foo) +@named sys1 = ConstraintsSystem(Equation[], [x], []) +@test_throws ["Unexpected", "sys1₊x", "subsystem with name sys1"] ConstraintsSystem( + [0 ~ sys1.x^2 - 2], [], []; name = :sys2) +@test_nowarn ConstraintsSystem([0 ~ sys1.x^2 - 2], [], []; name = :sys2, systems = [sys1]) + +# Ensure OptimizationSystem checks work +@test_throws ["Symbol", "x", "does not occur"] OptimizationSystem( + y[1], [y[1]], []; constraints = [x ~ 3], name = :foo) +@test_nowarn OptimizationSystem(y[1], [y[1], x], []; constraints = [x ~ 3], name = :foo) +@named sys1 = OptimizationSystem(x, [x], []) +@test_throws ["Unexpected", "sys1₊x", "subsystem with name sys1"] OptimizationSystem( + sys1.x^2 - 2, [], []; name = :sys2) +@test_nowarn OptimizationSystem(sys1.x^2 - 2, [], []; name = :sys2, systems = [sys1]) From 9495e935d9728c306efbc2877d6863a8e3b1b876 Mon Sep 17 00:00:00 2001 From: Yingbo Ma Date: Fri, 5 Apr 2024 15:53:55 -0400 Subject: [PATCH 036/316] Revert "feat: improve error messages for missing variables and subsystems" --- src/systems/diffeqs/odesystem.jl | 1 - src/systems/diffeqs/sdesystem.jl | 2 - .../discrete_system/discrete_system.jl | 1 - src/systems/nonlinear/nonlinearsystem.jl | 1 - .../optimization/constraints_system.jl | 1 - .../optimization/optimizationsystem.jl | 1 - src/utils.jl | 33 +----- test/variable_scope.jl | 104 +----------------- 8 files changed, 5 insertions(+), 139 deletions(-) diff --git a/src/systems/diffeqs/odesystem.jl b/src/systems/diffeqs/odesystem.jl index 89c2ab9479..34773524c9 100644 --- a/src/systems/diffeqs/odesystem.jl +++ b/src/systems/diffeqs/odesystem.jl @@ -188,7 +188,6 @@ struct ODESystem <: AbstractODESystem check_parameters(ps, iv) check_equations(deqs, iv) check_equations(equations(cevents), iv) - check_namespacing([deqs; equations(cevents)], dvs, ps, iv; systems) end if checks == true || (checks & CheckUnits) > 0 u = __get_unit_type(dvs, ps, iv) diff --git a/src/systems/diffeqs/sdesystem.jl b/src/systems/diffeqs/sdesystem.jl index 321330373e..b021a201fe 100644 --- a/src/systems/diffeqs/sdesystem.jl +++ b/src/systems/diffeqs/sdesystem.jl @@ -141,8 +141,6 @@ struct SDESystem <: AbstractODESystem check_parameters(ps, iv) check_equations(deqs, iv) check_equations(equations(cevents), iv) - check_namespacing( - [deqs; equations(cevents); vec(unwrap.(neqs))], dvs, ps, iv; systems) end if checks == true || (checks & CheckUnits) > 0 u = __get_unit_type(dvs, ps, iv) diff --git a/src/systems/discrete_system/discrete_system.jl b/src/systems/discrete_system/discrete_system.jl index 0d32f2a496..bd72c533d0 100644 --- a/src/systems/discrete_system/discrete_system.jl +++ b/src/systems/discrete_system/discrete_system.jl @@ -103,7 +103,6 @@ struct DiscreteSystem <: AbstractTimeDependentSystem if checks == true || (checks & CheckComponents) > 0 check_variables(dvs, iv) check_parameters(ps, iv) - check_namespacing(discreteEqs, dvs, ps, iv; systems) end if checks == true || (checks & CheckUnits) > 0 u = __get_unit_type(dvs, ps, iv) diff --git a/src/systems/nonlinear/nonlinearsystem.jl b/src/systems/nonlinear/nonlinearsystem.jl index b5d7f435f0..cf9e88c686 100644 --- a/src/systems/nonlinear/nonlinearsystem.jl +++ b/src/systems/nonlinear/nonlinearsystem.jl @@ -95,7 +95,6 @@ struct NonlinearSystem <: AbstractTimeIndependentSystem if checks == true || (checks & CheckUnits) > 0 u = __get_unit_type(unknowns, ps) check_units(u, eqs) - check_namespacing(eqs, unknowns, ps, nothing; systems) end new(tag, eqs, unknowns, ps, var_to_name, observed, jac, name, systems, defaults, connector_type, metadata, gui_metadata, tearing_state, substitutions, complete, diff --git a/src/systems/optimization/constraints_system.jl b/src/systems/optimization/constraints_system.jl index 7df6179b1e..7fde00e2f4 100644 --- a/src/systems/optimization/constraints_system.jl +++ b/src/systems/optimization/constraints_system.jl @@ -88,7 +88,6 @@ struct ConstraintsSystem <: AbstractTimeIndependentSystem if checks == true || (checks & CheckUnits) > 0 u = __get_unit_type(unknowns, ps) check_units(u, constraints) - check_namespacing(constraints, unknowns, ps, nothing; systems) end new(tag, constraints, unknowns, ps, var_to_name, observed, jac, name, systems, defaults, diff --git a/src/systems/optimization/optimizationsystem.jl b/src/systems/optimization/optimizationsystem.jl index c8cedc72c6..faa324f4fd 100644 --- a/src/systems/optimization/optimizationsystem.jl +++ b/src/systems/optimization/optimizationsystem.jl @@ -74,7 +74,6 @@ struct OptimizationSystem <: AbstractOptimizationSystem unwrap(op) isa Symbolic && check_units(u, op) check_units(u, observed) check_units(u, constraints) - check_namespacing([op; constraints], unknowns, ps, nothing; systems) end new(tag, op, unknowns, ps, var_to_name, observed, constraints, name, systems, defaults, metadata, gui_metadata, complete, diff --git a/src/utils.jl b/src/utils.jl index e33f51ef63..363c43378e 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -182,35 +182,6 @@ function check_equations(eqs, iv) throw(ArgumentError("Differential w.r.t. variable ($single_iv) other than the independent variable ($iv) are not allowed.")) end end - -function check_namespacing(eqs, dvs, ps, iv; systems = []) - eqsyms = vars(eqs; op = Nothing) - syssyms = Set{Symbol}() - foldl(Iterators.flatten((dvs, ps)); init = syssyms) do prev, sym - sym = unwrap(sym) - while istree(sym) && operation(sym) isa Operator - sym = only(arguments(sym)) - end - push!(prev, getname(sym)) - prev - end - subsysnames = get_name.(systems) - if iv !== nothing - push!(syssyms, getname(iv)) - end - for sym in eqsyms - symname = getname(sym) - strname = String(symname) - if occursin('₊', strname) - subsysname = Symbol(first(split(strname, '₊'))) - subsysname in subsysnames && continue - error("Unexpected variable $sym. Expected system to have subsystem with name $subsysname.") - end - symname in syssyms && continue - error("Symbol $sym does not occur in the system.") - end -end - """ Get all the independent variables with respect to which differentials are taken. """ @@ -377,8 +348,8 @@ end vars(exprs::Num; op = Differential) = vars(unwrap(exprs); op) vars(exprs::Symbolics.Arr; op = Differential) = vars(unwrap(exprs); op) vars(exprs; op = Differential) = foldl((x, y) -> vars!(x, y; op = op), exprs; init = Set()) -vars(eq::Union{Equation, Inequality}; op = Differential) = vars!(Set(), eq; op = op) -function vars!(vars, eq::Union{Equation, Inequality}; op = Differential) +vars(eq::Equation; op = Differential) = vars!(Set(), eq; op = op) +function vars!(vars, eq::Equation; op = Differential) (vars!(vars, eq.lhs; op = op); vars!(vars, eq.rhs; op = op); vars) end function vars!(vars, O; op = Differential) diff --git a/test/variable_scope.jl b/test/variable_scope.jl index 045f06f443..b80c004a2b 100644 --- a/test/variable_scope.jl +++ b/test/variable_scope.jl @@ -1,8 +1,9 @@ using ModelingToolkit -using ModelingToolkit: SymScope, t_nounits as t, D_nounits as D +using ModelingToolkit: SymScope using Symbolics: arguments, value using Test +@parameters t @variables a b(t) c d e(t) b = ParentScope(b) @@ -51,7 +52,7 @@ 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 t a b c d e f p = [a ParentScope(b) ParentScope(ParentScope(c)) @@ -72,102 +73,3 @@ ps = ModelingToolkit.getname.(parameters(level3)) @test isequal(ps[4], :level2₊level0₊d) @test isequal(ps[5], :level1₊level0₊e) @test isequal(ps[6], :f) - -@variables x(t) y(t)[1:2] -@parameters p q[1:2] - -@test_throws ["Symbol", "x(t)", "does not occur"] ODESystem( - [D(x) ~ p], t, [], [p]; name = :foo) -@test_nowarn ODESystem([D(x) ~ p], t, [x], [p]; name = :foo) -@test_throws ["Symbol", "y(t)", "does not occur"] ODESystem( - D(y) ~ q, t, [], [q]; name = :foo) -@test_nowarn ODESystem(D(y) ~ q, t, [y], [q]; name = :foo) -@test_throws ["Symbol", "y(t)", "[1]", "does not occur"] ODESystem( - D(y[1]) ~ x, t, [x], []; name = :foo) -@test_nowarn ODESystem(D(y[1]) ~ x, t, [x, y], []; name = :foo) -@test_throws ["Symbol", "p", "does not occur"] ODESystem(D(x) ~ p, t, [x], []; name = :foo) -@test_nowarn ODESystem(D(x) ~ p, t, [x], [p]; name = :foo) -@test_throws ["Symbol", "q", "does not occur"] ODESystem(D(y) ~ q, t, [y], []; name = :foo) -@test_nowarn ODESystem(D(y) ~ q, t, [y], [q]; name = :foo) -@test_throws ["Symbol", "q", "[1]", "does not occur"] ODESystem( - D(y[1]) ~ q[1], t, [y], []; name = :foo) -@test_nowarn ODESystem(D(y[1]) ~ q[1], t, [y], [q]; name = :foo) -@test_throws ["Symbol", "x(t)", "does not occur"] ODESystem( - Equation[], t, [], [p]; name = :foo, continuous_events = [[x ~ 0.0] => [p ~ 1.0]]) -@test_nowarn ODESystem( - Equation[], t, [x], [p]; name = :foo, continuous_events = [[x ~ 0.0] => [p ~ 1.0]]) - -@named sys1 = ODESystem(Equation[], t, [x, y], [p, q]) -@test_throws ["Unexpected", "sys1₊x(t)", "subsystem with name sys1"] ODESystem( - [D(x) ~ sys1.x], t; name = :sys2) -@test_nowarn ODESystem([D(x) ~ sys1.x], t; name = :sys2, systems = [sys1]) -@test_throws ["Unexpected", "sys1₊y(t)", "subsystem with name sys1"] ODESystem( - [D(x) ~ sum(sys1.y)], t; name = :sys2) -@test_nowarn ODESystem([D(x) ~ sum(sys1.y)], t; name = :sys2, systems = [sys1]) -@test_throws ["Unexpected", "sys1₊y(t)", "[1]", "subsystem with name sys1"] ODESystem( - D(x) ~ sys1.y[1], t; name = :sys2) -@test_nowarn ODESystem(D(x) ~ sys1.y[1], t; name = :sys2, systems = [sys1]) -@test_throws ["Unexpected", "sys1₊p", "subsystem with name sys1"] ODESystem( - D(x) ~ sys1.p, t; name = :sys2) -@test_nowarn ODESystem(D(x) ~ sys1.p, t; name = :sys2, systems = [sys1]) -@test_throws ["Unexpected", "sys1₊q", "subsystem with name sys1"] ODESystem( - D(y) ~ sys1.q, t; name = :sys2) -@test_nowarn ODESystem(D(y) ~ sys1.q, t; name = :sys2, systems = [sys1]) -@test_throws ["Unexpected", "sys1₊q", "[1]", "subsystem with name sys1"] ODESystem( - D(x) ~ sys1.q[1], t; name = :sys2) -@test_nowarn ODESystem(D(x) ~ sys1.q[1], t; name = :sys2, systems = [sys1]) -@test_throws ["Unexpected", "sys1₊x(t)", "subsystem with name sys1"] ODESystem( - Equation[], t, [], [p]; name = :sys2, continuous_events = [[sys1.x ~ 0] => [p ~ 1.0]]) -@test_nowarn ODESystem(Equation[], t, [], [p]; name = :sys2, - continuous_events = [[sys1.x ~ 0] => [p ~ 1.0]], systems = [sys1]) - -# Ensure SDESystem checks noise eqs as well -@test_throws ["Symbol", "x(t)", "does not occur"] SDESystem( - Equation[], [0.1x], t, [], []; name = :foo) -@test_nowarn SDESystem(Equation[], [0.1x], t, [x], []; name = :foo) -@named sys1 = SDESystem(Equation[], [], t, [x], []) -@test_throws ["Unexpected", "sys1₊x(t)", "subsystem with name sys1"] SDESystem( - Equation[], [0.1sys1.x], t, [], []; name = :foo) -@test_nowarn SDESystem(Equation[], [0.1sys1.x], t, [], []; name = :foo, systems = [sys1]) - -# Ensure DiscreteSystem checks work -k = ShiftIndex(t) -@test_throws ["Symbol", "x(t)", "does not occur"] DiscreteSystem( - [x ~ x(k - 1) + x(k - 2)], t, [], []; name = :foo) -@test_nowarn DiscreteSystem([x ~ x(k - 1) + x(k - 2)], t; name = :foo) -@named sys1 = DiscreteSystem(Equation[], t, [x], []) -@test_throws ["Unexpected", "sys1₊x(t)", "subsystem with name sys1"] DiscreteSystem( - [x ~ x(k - 1) + sys1.x(k - 2)], t, [x], []; name = :sys2) -@test_nowarn DiscreteSystem( - [x ~ x(k - 1) + sys1.x(k - 2)], t, [x], []; name = :sys2, systems = [sys1]) - -# Ensure NonlinearSystem checks work -@variables x -@test_throws ["Symbol", "x", "does not occur"] NonlinearSystem( - [0 ~ 2x + 3], [], []; name = :foo) -@test_nowarn NonlinearSystem([0 ~ 2x + 3], [x], []; name = :foo) -@named sys1 = NonlinearSystem(Equation[], [x], []) -@test_throws ["Unexpected", "sys1₊x", "subsystem with name sys1"] NonlinearSystem( - [0 ~ sys1.x + 3], [], []; name = :foo) -@test_nowarn NonlinearSystem([0 ~ sys1.x + 3], [], []; name = :foo, systems = [sys1]) - -# Ensure ConstraintsSystem checks work -@test_throws ["Symbol", "x", "does not occur"] ConstraintsSystem( - [0 ~ x^2 - 3], [], []; name = :foo) -@test_nowarn ConstraintsSystem([0 ~ x^2 - 3], [x], []; name = :foo) -@test_throws ["Symbol", "x", "does not occur"] ConstraintsSystem( - [Inequality(x^2, 3, <)], [], []; name = :foo) -@test_nowarn ConstraintsSystem([Inequality(x^2, 3, <)], [x], []; name = :foo) -@named sys1 = ConstraintsSystem(Equation[], [x], []) -@test_throws ["Unexpected", "sys1₊x", "subsystem with name sys1"] ConstraintsSystem( - [0 ~ sys1.x^2 - 2], [], []; name = :sys2) -@test_nowarn ConstraintsSystem([0 ~ sys1.x^2 - 2], [], []; name = :sys2, systems = [sys1]) - -# Ensure OptimizationSystem checks work -@test_throws ["Symbol", "x", "does not occur"] OptimizationSystem( - y[1], [y[1]], []; constraints = [x ~ 3], name = :foo) -@test_nowarn OptimizationSystem(y[1], [y[1], x], []; constraints = [x ~ 3], name = :foo) -@named sys1 = OptimizationSystem(x, [x], []) -@test_throws ["Unexpected", "sys1₊x", "subsystem with name sys1"] OptimizationSystem( - sys1.x^2 - 2, [], []; name = :sys2) -@test_nowarn OptimizationSystem(sys1.x^2 - 2, [], []; name = :sys2, systems = [sys1]) From eb291d76d7ca616772894643f5fdc47de47e6c44 Mon Sep 17 00:00:00 2001 From: Christopher Rackauckas Date: Fri, 5 Apr 2024 18:17:33 -0400 Subject: [PATCH 037/316] Revert "feat: improve error messages for missing variables and subsystems" From 507350b1913722d4e988dbdea8e9253dfdbfcf0b Mon Sep 17 00:00:00 2001 From: Yingbo Ma Date: Tue, 9 Apr 2024 20:49:03 -0400 Subject: [PATCH 038/316] Fix a minor issue in generate_initializesystem --- src/systems/nonlinear/initializesystem.jl | 2 +- test/odesystem.jl | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/systems/nonlinear/initializesystem.jl b/src/systems/nonlinear/initializesystem.jl index dadc66e262..86921a1af0 100644 --- a/src/systems/nonlinear/initializesystem.jl +++ b/src/systems/nonlinear/initializesystem.jl @@ -62,7 +62,7 @@ function generate_initializesystem(sys::ODESystem; end else dd_guess = Dict() - filtered_u0 = u0map + filtered_u0 = todict(u0map) end defs = merge(defaults(sys), filtered_u0) diff --git a/test/odesystem.jl b/test/odesystem.jl index 542c3a555b..0f12035c22 100644 --- a/test/odesystem.jl +++ b/test/odesystem.jl @@ -1030,3 +1030,15 @@ sol2 = @test_nowarn solve(prob2, Tsit5()) @test_nowarn @mtkbuild sys = ODESystem([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 + +@parameters g L +@variables q₁(t) q₂(t) λ(t) θ(t) + +eqs = [D(D(q₁)) ~ -λ * q₁, + D(D(q₂)) ~ -λ * q₂ - g, + q₁ ~ L * sin(θ), + q₂ ~ L * cos(θ)] + +@named pend = ODESystem(eqs, t) +@test_nowarn generate_initializesystem( + pend, u0map = [q₁ => 1.0, q₂ => 0.0], guesses = [λ => 1]) From 971359a26d9a23394adce622ef58a4823e95cdbf Mon Sep 17 00:00:00 2001 From: Christopher Rackauckas Date: Wed, 10 Apr 2024 12:22:25 -0400 Subject: [PATCH 039/316] Update Project.toml --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index b2992259e8..ea2ce017a7 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.9.0" +version = "9.10.0" [deps] AbstractTrees = "1520ce14-60c1-5f80-bbc7-55ef81b5835c" From 326b2ca737b796ee6e456bdeb01f85863c0fbdc2 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Thu, 11 Apr 2024 12:43:10 +0530 Subject: [PATCH 040/316] feat: allow creating NonlinearSystem without specifying unknowns/parameters --- src/systems/nonlinear/nonlinearsystem.jl | 26 ++++++++++++++++++++++++ test/nonlinearsystem.jl | 9 ++++++++ 2 files changed, 35 insertions(+) diff --git a/src/systems/nonlinear/nonlinearsystem.jl b/src/systems/nonlinear/nonlinearsystem.jl index cf9e88c686..b548521f45 100644 --- a/src/systems/nonlinear/nonlinearsystem.jl +++ b/src/systems/nonlinear/nonlinearsystem.jl @@ -153,6 +153,32 @@ function NonlinearSystem(eqs, unknowns, ps; connector_type, metadata, gui_metadata, checks = checks) end +function NonlinearSystem(eqs; kwargs...) + eqs = collect(eqs) + allunknowns = OrderedSet() + ps = OrderedSet() + for eq in eqs + collect_vars!(allunknowns, ps, eq.lhs, nothing) + collect_vars!(allunknowns, ps, eq.rhs, nothing) + end + new_ps = OrderedSet() + for p in ps + if istree(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 NonlinearSystem(eqs, collect(allunknowns), collect(new_ps); kwargs...) +end + function calculate_jacobian(sys::NonlinearSystem; sparse = false, simplify = false) cache = get_jac(sys)[] if cache isa Tuple && cache[2] == (sparse, simplify) diff --git a/test/nonlinearsystem.jl b/test/nonlinearsystem.jl index 6fdd099c78..ea31cff644 100644 --- a/test/nonlinearsystem.jl +++ b/test/nonlinearsystem.jl @@ -257,3 +257,12 @@ end sol = solve(prob) @test_nowarn sol[unknowns(ns)] end + +# Issue#2625 +@parameters p d +@variables X(t) +alg_eqs = [0 ~ p - d * X] + +sys = @test_nowarn NonlinearSystem(alg_eqs; name = :name) +@test isequal(only(unknowns(sys)), X) +@test all(isequal.(parameters(sys), [p, d])) From f640c1bd298247c97003ecf76fababbdccef0d4e Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Thu, 11 Apr 2024 12:20:09 +0530 Subject: [PATCH 041/316] fix: fix intialization of array parameters with unknown size --- src/systems/parameter_buffer.jl | 23 ++++++++++++++++------- test/mtkparameters.jl | 11 +++++++++++ 2 files changed, 27 insertions(+), 7 deletions(-) diff --git a/src/systems/parameter_buffer.jl b/src/systems/parameter_buffer.jl index 7ce13dda9b..6d47817a9b 100644 --- a/src/systems/parameter_buffer.jl +++ b/src/systems/parameter_buffer.jl @@ -43,8 +43,7 @@ function MTKParameters( p = merge(defs, p) p = merge(Dict(unwrap(k) => v for (k, v) in p), Dict(default_toterm(unwrap(k)) => v for (k, v) in p)) - p = Dict(k => fixpoint_sub(v, p) - for (k, v) in p if k in all_ps || default_toterm(k) in all_ps) + p = Dict(k => fixpoint_sub(v, p) for (k, v) in p) for (sym, _) in p if istree(sym) && operation(sym) === getindex && first(arguments(sym)) in all_ps @@ -89,14 +88,24 @@ function MTKParameters( for (sym, val) in p sym = unwrap(sym) + val = unwrap(val) ctype = concrete_symtype(sym) - val = symconvert(ctype, unwrap(fixpoint_sub(val, p))) + if symbolic_type(val) !== NotSymbolic() + continue + end + val = symconvert(ctype, val) done = set_value(sym, val) if !done && Symbolics.isarraysymbolic(sym) - done = all(set_value.(collect(sym), val)) - end - if !done - error("Symbol $sym does not have an index") + if Symbolics.shape(sym) === Symbolics.Unknown() + for i in eachindex(val) + set_value(sym[i], val[i]) + end + else + if size(sym) != size(val) + error("Got value of size $(size(val)) for parameter $sym of size $(size(sym))") + end + set_value.(collect(sym), val) + end end end diff --git a/test/mtkparameters.jl b/test/mtkparameters.jl index 688ccba84a..33d03e62c6 100644 --- a/test/mtkparameters.jl +++ b/test/mtkparameters.jl @@ -101,3 +101,14 @@ function loss(value, sys, ps) end @test ForwardDiff.derivative(x -> loss(x, sys, ps), 1.5) == 3.0 + +# Issue#2615 +@parameters p::Vector{Float64} +@variables X(t) +eq = D(X) ~ p[1] - p[2] * X +@mtkbuild osys = ODESystem([eq], t) + +u0 = [X => 1.0] +ps = [p => [2.0, 0.1]] +p = MTKParameters(osys, ps, u0) +@test p.tunable[1] == [2.0, 0.1] From ec0795ef1c52f92666911840c9c0826d26c634ce Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Thu, 11 Apr 2024 15:45:04 +0530 Subject: [PATCH 042/316] fix: fix remake_buffer for MTKParameters --- src/systems/parameter_buffer.jl | 1 + 1 file changed, 1 insertion(+) diff --git a/src/systems/parameter_buffer.jl b/src/systems/parameter_buffer.jl index 6d47817a9b..04ea92d7fe 100644 --- a/src/systems/parameter_buffer.jl +++ b/src/systems/parameter_buffer.jl @@ -350,6 +350,7 @@ function SymbolicIndexingInterface.remake_buffer(sys, oldbuf::MTKParameters, val if newbuf.dependent_update_oop !== nothing @set! newbuf.dependent = newbuf.dependent_update_oop(newbuf...) end + return newbuf end _subarrays(v::AbstractVector) = isempty(v) ? () : (v,) From f15fa79e58d3a33c9e3d810bfe26134a17e8f102 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Thu, 11 Apr 2024 15:45:47 +0530 Subject: [PATCH 043/316] test: fix odesystem tests --- test/odesystem.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/odesystem.jl b/test/odesystem.jl index 0f12035c22..cead546d43 100644 --- a/test/odesystem.jl +++ b/test/odesystem.jl @@ -1024,7 +1024,7 @@ sol2 = @test_nowarn solve(prob2, Tsit5()) @test sol1 ≈ sol2 # Requires fix in symbolics for `linear_expansion(p * x, D(y))` -@test_broken begin +@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) From 2a4fbd2d4899f3fb461962e44cec618a9eb6a7cb Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Thu, 11 Apr 2024 17:59:49 +0530 Subject: [PATCH 044/316] feat: support parameter dependencies for NonlinearSystem --- src/systems/nonlinear/nonlinearsystem.jl | 19 ++++++++++++++----- test/parameter_dependencies.jl | 17 +++++++++++++++++ 2 files changed, 31 insertions(+), 5 deletions(-) diff --git a/src/systems/nonlinear/nonlinearsystem.jl b/src/systems/nonlinear/nonlinearsystem.jl index b548521f45..9d50e11549 100644 --- a/src/systems/nonlinear/nonlinearsystem.jl +++ b/src/systems/nonlinear/nonlinearsystem.jl @@ -57,6 +57,11 @@ struct NonlinearSystem <: AbstractTimeIndependentSystem """ connector_type::Any """ + A mapping from dependent parameters to expressions describing how they are calculated from + other parameters. + """ + parameter_dependencies::Union{Nothing, Dict} + """ Metadata for the system, to be used by downstream packages. """ metadata::Any @@ -87,7 +92,7 @@ struct NonlinearSystem <: AbstractTimeIndependentSystem function NonlinearSystem(tag, eqs, unknowns, ps, var_to_name, observed, jac, name, systems, - defaults, connector_type, metadata = nothing, + defaults, connector_type, parameter_dependencies = nothing, metadata = nothing, gui_metadata = nothing, tearing_state = nothing, substitutions = nothing, complete = false, index_cache = nothing, parent = nothing; checks::Union{ @@ -97,8 +102,8 @@ struct NonlinearSystem <: AbstractTimeIndependentSystem check_units(u, eqs) end new(tag, eqs, unknowns, ps, var_to_name, observed, jac, name, systems, defaults, - connector_type, metadata, gui_metadata, tearing_state, substitutions, complete, - index_cache, parent) + connector_type, parameter_dependencies, metadata, gui_metadata, tearing_state, + substitutions, complete, index_cache, parent) end end @@ -113,6 +118,7 @@ function NonlinearSystem(eqs, unknowns, ps; 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 = nothing, metadata = nothing, gui_metadata = nothing) continuous_events === nothing || isempty(continuous_events) || @@ -148,9 +154,11 @@ function NonlinearSystem(eqs, unknowns, ps; process_variables!(var_to_name, defaults, ps) isempty(observed) || collect_var_to_name!(var_to_name, (eq.lhs for eq in observed)) + parameter_dependencies, ps = process_parameter_dependencies( + parameter_dependencies, ps) NonlinearSystem(Threads.atomic_add!(SYSTEM_COUNT, UInt(1)), eqs, unknowns, ps, var_to_name, observed, jac, name, systems, defaults, - connector_type, metadata, gui_metadata, checks = checks) + connector_type, parameter_dependencies, metadata, gui_metadata, checks = checks) end function NonlinearSystem(eqs; kwargs...) @@ -233,6 +241,7 @@ function generate_function( pre, sol_states = get_substitutions_and_solved_unknowns(sys) p = reorder_parameters(sys, value.(ps)) + @show p ps return build_function(rhss, value.(dvs), p...; postprocess_fbody = pre, states = sol_states, kwargs...) end @@ -385,7 +394,7 @@ function process_NonlinearProblem(constructor, sys::NonlinearSystem, u0map, para kwargs...) eqs = equations(sys) dvs = unknowns(sys) - ps = parameters(sys) + ps = full_parameters(sys) if has_index_cache(sys) && get_index_cache(sys) !== nothing u0, defs = get_u0(sys, u0map, parammap) diff --git a/test/parameter_dependencies.jl b/test/parameter_dependencies.jl index d55043383a..ffb5053c14 100644 --- a/test/parameter_dependencies.jl +++ b/test/parameter_dependencies.jl @@ -7,6 +7,7 @@ using JumpProcesses using StableRNGs using SciMLStructures: canonicalize, Tunable, replace, replace! using SymbolicIndexingInterface +using NonlinearSolve @testset "ODESystem with callbacks" begin @parameters p1=1.0 p2=1.0 @@ -162,6 +163,22 @@ end @test integ.ps[β] == 0.0002 end +@testset "NonlinearSystem" begin + @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]) + @test isequal(only(parameters(sys)), p1) + @test Set(full_parameters(sys)) == Set([p1, p2]) + prob = NonlinearProblem(sys, [x => 1.0]) + @test prob.ps[p1] == 1.0 + @test prob.ps[p2] == 2.0 + @test_nowarn solve(prob, NewtonRaphson()) + prob = NonlinearProblem(sys, [x => 1.0], [p1 => 2.0]) + @test prob.ps[p1] == 2.0 + @test prob.ps[p2] == 4.0 +end + @testset "SciMLStructures interface" begin @parameters p1=1.0 p2=1.0 @variables x(t) From c97d559396dbb6097623c22c00b78815763e1386 Mon Sep 17 00:00:00 2001 From: Christopher Rackauckas Date: Fri, 12 Apr 2024 11:24:40 -0400 Subject: [PATCH 045/316] Update Project.toml --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index ea2ce017a7..99c2ceb73b 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.10.0" +version = "9.11.0" [deps] AbstractTrees = "1520ce14-60c1-5f80-bbc7-55ef81b5835c" From 0c53f0561c963721c81bd9b82f34480df1e84799 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Miclu=C8=9Ba-C=C3=A2mpeanu?= Date: Wed, 10 Apr 2024 23:09:24 +0300 Subject: [PATCH 046/316] fix: propagate parameter dependencies in `extend` --- src/systems/abstractsystem.jl | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/systems/abstractsystem.jl b/src/systems/abstractsystem.jl index 6d7d517968..9069304063 100644 --- a/src/systems/abstractsystem.jl +++ b/src/systems/abstractsystem.jl @@ -2225,6 +2225,10 @@ function extend(sys::AbstractSystem, basesys::AbstractSystem; name::Symbol = nam eqs = union(get_eqs(basesys), get_eqs(sys)) sts = union(get_unknowns(basesys), get_unknowns(sys)) ps = union(get_ps(basesys), get_ps(sys)) + base_deps = get_parameter_dependencies(basesys) + deps = get_parameter_dependencies(sys) + dep_ps = isnothing(base_deps) ? deps : + isnothing(deps) ? base_deps : union(base_deps, deps) obs = union(get_observed(basesys), get_observed(sys)) cevs = union(get_continuous_events(basesys), get_continuous_events(sys)) devs = union(get_discrete_events(basesys), get_discrete_events(sys)) @@ -2233,11 +2237,12 @@ function extend(sys::AbstractSystem, basesys::AbstractSystem; name::Symbol = nam if length(ivs) == 0 T(eqs, sts, ps, observed = obs, defaults = defs, name = name, systems = syss, - continuous_events = cevs, discrete_events = devs, gui_metadata = gui_metadata) + continuous_events = cevs, discrete_events = devs, gui_metadata = gui_metadata, + parameter_dependencies = dep_ps) elseif length(ivs) == 1 T(eqs, ivs[1], sts, ps, observed = obs, defaults = defs, name = name, systems = syss, continuous_events = cevs, discrete_events = devs, - gui_metadata = gui_metadata) + gui_metadata = gui_metadata, parameter_dependencies = dep_ps) end end @@ -2395,7 +2400,7 @@ end """ is_diff_equation(eq) -Returns `true` if the input is a differential equation, i.e. is an equatation that contain some +Returns `true` if the input is a differential equation, i.e. is an equatation that contain some form of differential. Example: @@ -2421,7 +2426,7 @@ end """ is_alg_equation(eq) -Returns `true` if the input is an algebraic equation, i.e. is an equatation that does not contain +Returns `true` if the input is an algebraic equation, i.e. is an equatation that does not contain any differentials. Example: @@ -2603,7 +2608,7 @@ has_alg_eqs(sys::AbstractSystem) = any(is_alg_equation, get_eqs(sys)) """ has_diff_eqs(sys::AbstractSystem) -For a system, returns true if it contain at least one differential equation (i.e. that contain a +For a system, returns true if it contain at least one differential equation (i.e. that contain a differential) in its *top-level system*. Example: From c7e1c03a5c8c468b3951fc85d0924971e2e28208 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Miclu=C8=9Ba-C=C3=A2mpeanu?= <31181429+SebastianM-C@users.noreply.github.com> Date: Thu, 11 Apr 2024 11:38:40 +0300 Subject: [PATCH 047/316] docs: fix docstrings Co-authored-by: Aayush Sabharwal --- src/systems/abstractsystem.jl | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/systems/abstractsystem.jl b/src/systems/abstractsystem.jl index 9069304063..394e4aab7b 100644 --- a/src/systems/abstractsystem.jl +++ b/src/systems/abstractsystem.jl @@ -2400,8 +2400,8 @@ end """ is_diff_equation(eq) -Returns `true` if the input is a differential equation, i.e. is an equatation that contain some -form of differential. +Return `true` if the input is a differential equation, i.e. an equation that contains a +differential term. Example: ```julia @@ -2426,7 +2426,7 @@ end """ is_alg_equation(eq) -Returns `true` if the input is an algebraic equation, i.e. is an equatation that does not contain +Return `true` if the input is an algebraic equation, i.e. an equation that does not contain any differentials. Example: @@ -2608,8 +2608,9 @@ has_alg_eqs(sys::AbstractSystem) = any(is_alg_equation, get_eqs(sys)) """ has_diff_eqs(sys::AbstractSystem) -For a system, returns true if it contain at least one differential equation (i.e. that contain a -differential) in its *top-level system*. +Return `true` if a system contains at least one differential equation (i.e. an equation with a +differential term). Note that this does not consider subsystems, and only takes into account +equations in the top-level system. Example: ```julia From 9c9ecd955dd65779f60bc91aba36f7aca870fbe2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Miclu=C8=9Ba-C=C3=A2mpeanu?= Date: Thu, 11 Apr 2024 11:52:06 +0300 Subject: [PATCH 048/316] test: add test for extend with parameter dependencies --- test/parameter_dependencies.jl | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/test/parameter_dependencies.jl b/test/parameter_dependencies.jl index ffb5053c14..caaae3544f 100644 --- a/test/parameter_dependencies.jl +++ b/test/parameter_dependencies.jl @@ -49,6 +49,24 @@ using NonlinearSolve @test integ.ps[p2] == 10.0 end +@testset "extend" begin + @parameters p1=1.0 p2=1.0 + @variables x(t) + + @mtkbuild sys1 = ODESystem( + [D(x) ~ p1 * t + p2], + t + ) + @named sys2 = ODESystem( + [], + t; + parameter_dependencies = [p2 => 2p1] + ) + sys = extend(sys2, sys1) + @test isequal(only(parameters(sys)), p1) + @test Set(full_parameters(sys)) == Set([p1, p2]) +end + @testset "Clock system" begin dt = 0.1 @variables x(t) y(t) u(t) yd(t) ud(t) r(t) z(t) From 3ff4c268881d64531462ce21fd899165e29be7da Mon Sep 17 00:00:00 2001 From: Christopher Rackauckas Date: Sat, 13 Apr 2024 06:52:37 -0400 Subject: [PATCH 049/316] Fix steady state problem construction --- src/systems/diffeqs/abstractodesystem.jl | 3 ++- test/odesystem.jl | 24 ++++++++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/src/systems/diffeqs/abstractodesystem.jl b/src/systems/diffeqs/abstractodesystem.jl index e40407b002..69a4cf6310 100644 --- a/src/systems/diffeqs/abstractodesystem.jl +++ b/src/systems/diffeqs/abstractodesystem.jl @@ -906,7 +906,8 @@ function process_DEProblem(constructor, sys::AbstractODESystem, u0map, parammap; if sys isa ODESystem && build_initializeprob && (implicit_dae || !isempty(missingvars)) && all(isequal(Continuous()), ci.var_domain) && - ModelingToolkit.get_tearing_state(sys) !== nothing + ModelingToolkit.get_tearing_state(sys) !== nothing && + t !== nothing if eltype(u0map) <: Number u0map = unknowns(sys) .=> u0map end diff --git a/test/odesystem.jl b/test/odesystem.jl index cead546d43..3985eb72f3 100644 --- a/test/odesystem.jl +++ b/test/odesystem.jl @@ -1042,3 +1042,27 @@ eqs = [D(D(q₁)) ~ -λ * q₁, @named pend = ODESystem(eqs, t) @test_nowarn generate_initializesystem( pend, u0map = [q₁ => 1.0, q₂ => 0.0], guesses = [λ => 1]) + +# https://github.com/SciML/ModelingToolkit.jl/issues/2618 +@parameters σ ρ β +@variables x(t) y(t) z(t) + +eqs = [D(D(x)) ~ σ * (y - x), + D(y) ~ x * (ρ - z) - y, + D(z) ~ x * y - β * z] + +@mtkbuild sys = ODESystem(eqs, t) + +u0 = [D(x) => 2.0, + x => 1.0, + y => 0.0, + z => 0.0] + +p = [σ => 28.0, + ρ => 10.0, + β => 8 / 3] + +prob = SteadyStateProblem(sys, u0, p) +@test prob isa SteadyStateProblem +prob = SteadyStateProblem(ODEProblem(sys, u0, (0.0, 10.0), p)) +@test prob isa SteadyStateProblem \ No newline at end of file From f59211c42536eef303dbd69338e39928034f9c8c Mon Sep 17 00:00:00 2001 From: Christopher Rackauckas Date: Sat, 13 Apr 2024 07:16:47 -0400 Subject: [PATCH 050/316] format --- test/odesystem.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/odesystem.jl b/test/odesystem.jl index 3985eb72f3..b1a650d94a 100644 --- a/test/odesystem.jl +++ b/test/odesystem.jl @@ -1065,4 +1065,4 @@ p = [σ => 28.0, prob = SteadyStateProblem(sys, u0, p) @test prob isa SteadyStateProblem prob = SteadyStateProblem(ODEProblem(sys, u0, (0.0, 10.0), p)) -@test prob isa SteadyStateProblem \ No newline at end of file +@test prob isa SteadyStateProblem From df0ce794a2c2695a10aa31ec9ed7efb7b4f72adb Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Thu, 11 Apr 2024 19:05:25 +0530 Subject: [PATCH 051/316] feat: support partial updates in `remake_buffer` --- src/systems/parameter_buffer.jl | 37 ++++++++++++++++++++++----------- test/mtkparameters.jl | 9 ++++++++ 2 files changed, 34 insertions(+), 12 deletions(-) diff --git a/src/systems/parameter_buffer.jl b/src/systems/parameter_buffer.jl index 04ea92d7fe..0ef21618ca 100644 --- a/src/systems/parameter_buffer.jl +++ b/src/systems/parameter_buffer.jl @@ -324,29 +324,42 @@ function _set_parameter_unchecked!( p.dependent_update_iip(ArrayPartition(p.dependent), p...) end -function narrow_buffer_type(buffer::Vector) +function narrow_buffer_type_and_fallback_undefs(oldbuf::Vector, newbuf::Vector) type = Union{} - for x in buffer - type = Union{type, typeof(x)} + for i in eachindex(newbuf) + isassigned(newbuf, i) || continue + type = promote_type(type, typeof(newbuf[i])) end - return convert(Vector{type}, buffer) + for i in eachindex(newbuf) + isassigned(newbuf, i) && continue + newbuf[i] = convert(type, oldbuf[i]) + end + return convert(Vector{type}, newbuf) end function SymbolicIndexingInterface.remake_buffer(sys, oldbuf::MTKParameters, vals::Dict) - newbuf = @set oldbuf.tunable = similar.(oldbuf.tunable, Any) - @set! newbuf.discrete = similar.(newbuf.discrete, Any) - @set! newbuf.constant = similar.(newbuf.constant, Any) - @set! newbuf.nonnumeric = similar.(newbuf.nonnumeric, Any) + newbuf = @set oldbuf.tunable = Tuple(Vector{Any}(undef, length(buf)) + for buf in oldbuf.tunable) + @set! newbuf.discrete = Tuple(Vector{Any}(undef, length(buf)) + for buf in newbuf.discrete) + @set! newbuf.constant = Tuple(Vector{Any}(undef, length(buf)) + for buf in newbuf.constant) + @set! newbuf.nonnumeric = Tuple(Vector{Any}(undef, length(buf)) + for buf in newbuf.nonnumeric) for (p, val) in vals _set_parameter_unchecked!( newbuf, val, parameter_index(sys, p); update_dependent = false) end - @set! newbuf.tunable = narrow_buffer_type.(newbuf.tunable) - @set! newbuf.discrete = narrow_buffer_type.(newbuf.discrete) - @set! newbuf.constant = narrow_buffer_type.(newbuf.constant) - @set! newbuf.nonnumeric = narrow_buffer_type.(newbuf.nonnumeric) + @set! newbuf.tunable = narrow_buffer_type_and_fallback_undefs.( + oldbuf.tunable, newbuf.tunable) + @set! newbuf.discrete = narrow_buffer_type_and_fallback_undefs.( + oldbuf.discrete, newbuf.discrete) + @set! newbuf.constant = narrow_buffer_type_and_fallback_undefs.( + oldbuf.constant, newbuf.constant) + @set! newbuf.nonnumeric = narrow_buffer_type_and_fallback_undefs.( + oldbuf.nonnumeric, newbuf.nonnumeric) if newbuf.dependent_update_oop !== nothing @set! newbuf.dependent = newbuf.dependent_update_oop(newbuf...) end diff --git a/test/mtkparameters.jl b/test/mtkparameters.jl index 33d03e62c6..d6274ef506 100644 --- a/test/mtkparameters.jl +++ b/test/mtkparameters.jl @@ -112,3 +112,12 @@ u0 = [X => 1.0] ps = [p => [2.0, 0.1]] p = MTKParameters(osys, ps, u0) @test p.tunable[1] == [2.0, 0.1] + +# Ensure partial update promotes the buffer +@parameters p q r +@named sys = ODESystem(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, Dict(p => 1.0f0)) +@test newps.tunable[1] isa Vector{Float32} +@test newps.tunable[1] == [1.0f0, 2.0f0, 3.0f0] From 2b16047c271b3c0b9350d9c608d08e754c9f23a3 Mon Sep 17 00:00:00 2001 From: Frames White Date: Mon, 15 Apr 2024 16:25:18 +0800 Subject: [PATCH 052/316] collapse unneed branch --- src/systems/systems.jl | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/systems/systems.jl b/src/systems/systems.jl index 7cda96aac2..96e2043625 100644 --- a/src/systems/systems.jl +++ b/src/systems/systems.jl @@ -26,14 +26,12 @@ function structural_simplify( else newsys = newsys′ end - if newsys isa ODESystem - @set! newsys.parent = complete(sys; split) - elseif has_parent(newsys) + if newsys isa ODESystem || has_parent(newsys) @set! newsys.parent = complete(sys; split) end newsys = complete(newsys; split) if has_defaults(newsys) && (defs = get_defaults(newsys)) !== nothing - ks = collect(keys(defs)) + ks = collect(keys(defs)) # take copy to avoid mutating defs while iterating. for k in ks if Symbolics.isarraysymbolic(k) && Symbolics.shape(k) !== Symbolics.Unknown() for i in eachindex(k) From 3d52b313431aa6eb1b3497b853733b34c28d6409 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Miclu=C8=9Ba-C=C3=A2mpeanu?= Date: Wed, 27 Mar 2024 01:24:01 +0200 Subject: [PATCH 053/316] perf: speed up `split_into_buffers` By avoiding boxing in this function, SciMLStructures.replace becomes inferable --- src/systems/parameter_buffer.jl | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/src/systems/parameter_buffer.jl b/src/systems/parameter_buffer.jl index 0ef21618ca..34859b9b56 100644 --- a/src/systems/parameter_buffer.jl +++ b/src/systems/parameter_buffer.jl @@ -141,18 +141,20 @@ function buffer_to_arraypartition(buf) v for v in buf)) end +_split_helper(buf_v::T, recurse, raw, idx) where {T} = _split_helper(eltype(T), buf_v, recurse, raw, idx) + +function _split_helper(::Type{<:AbstractArray}, buf_v, recurse, raw, idx) + recurse ? map(b -> _split_helper(b, recurse, raw, idx), buf_v) : _split_helper(Any, b, recurse, raw, idx) +end + +function _split_helper(::Any, buf_v, recurse, raw, idx) + res = reshape(raw[idx[]:(idx[] + length(buf_v) - 1)], size(buf_v)) + idx[] += length(buf_v) + return res +end + function split_into_buffers(raw::AbstractArray, buf; recurse = true) - idx = 1 - function _helper(buf_v; recurse = true) - if eltype(buf_v) <: AbstractArray && recurse - return _helper.(buf_v; recurse = false) - else - res = reshape(raw[idx:(idx + length(buf_v) - 1)], size(buf_v)) - idx += length(buf_v) - return res - end - end - return Tuple(_helper(buf_v; recurse) for buf_v in buf) + ntuple(i->_split_helper(buf[i], recurse, raw, Ref(1)), Val(length(buf))) end function update_tuple_of_buffers(raw::AbstractArray, buf) From baa928c10d425dfa43dbca474843118bc96a1cbc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Miclu=C8=9Ba-C=C3=A2mpeanu?= Date: Fri, 5 Apr 2024 02:27:25 +0300 Subject: [PATCH 054/316] refactor: remove all branching via value types --- src/systems/parameter_buffer.jl | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/systems/parameter_buffer.jl b/src/systems/parameter_buffer.jl index 34859b9b56..da0a3f9f5f 100644 --- a/src/systems/parameter_buffer.jl +++ b/src/systems/parameter_buffer.jl @@ -143,18 +143,24 @@ end _split_helper(buf_v::T, recurse, raw, idx) where {T} = _split_helper(eltype(T), buf_v, recurse, raw, idx) -function _split_helper(::Type{<:AbstractArray}, buf_v, recurse, raw, idx) - recurse ? map(b -> _split_helper(b, recurse, raw, idx), buf_v) : _split_helper(Any, b, recurse, raw, idx) +function _split_helper(::Type{<:AbstractArray}, buf_v, ::Val{true}, raw, idx) + map(b -> _split_helper(eltype(b), b, Val(false), raw, idx), buf_v) end -function _split_helper(::Any, buf_v, recurse, raw, idx) +function _split_helper(::Type{<:AbstractArray}, buf_v, ::Val{false}, raw, idx) + _split_helper((), buf_v, (), raw, idx) +end + +function _split_helper(_, buf_v, _, raw, idx) res = reshape(raw[idx[]:(idx[] + length(buf_v) - 1)], size(buf_v)) idx[] += length(buf_v) return res end -function split_into_buffers(raw::AbstractArray, buf; recurse = true) - ntuple(i->_split_helper(buf[i], recurse, raw, Ref(1)), Val(length(buf))) +function split_into_buffers(raw::AbstractArray, buf, recurse = Val(true)) + idx = Ref(1) + ntuple(i->_split_helper(buf[i], recurse, raw, idx), Val(length(buf))) +end end function update_tuple_of_buffers(raw::AbstractArray, buf) @@ -197,7 +203,7 @@ for (Portion, field) in [(SciMLStructures.Tunable, :tunable) @set! p.$field = split_into_buffers(newvals, p.$field) if p.dependent_update_oop !== nothing raw = p.dependent_update_oop(p...) - @set! p.dependent = split_into_buffers(raw, p.dependent; recurse = false) + @set! p.dependent = split_into_buffers(raw, p.dependent, Val(false)) end p end From 848bf4173034d80b88e1f773ff441615b012b1b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Miclu=C8=9Ba-C=C3=A2mpeanu?= Date: Fri, 5 Apr 2024 02:28:19 +0300 Subject: [PATCH 055/316] perf: avoid boxing in `update_tuple_of_buffers` --- src/systems/parameter_buffer.jl | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/src/systems/parameter_buffer.jl b/src/systems/parameter_buffer.jl index da0a3f9f5f..4428e8bbb0 100644 --- a/src/systems/parameter_buffer.jl +++ b/src/systems/parameter_buffer.jl @@ -141,7 +141,9 @@ function buffer_to_arraypartition(buf) v for v in buf)) end -_split_helper(buf_v::T, recurse, raw, idx) where {T} = _split_helper(eltype(T), buf_v, recurse, raw, idx) +function _split_helper(buf_v::T, recurse, raw, idx) where {T} + _split_helper(eltype(T), buf_v, recurse, raw, idx) +end function _split_helper(::Type{<:AbstractArray}, buf_v, ::Val{true}, raw, idx) map(b -> _split_helper(eltype(b), b, Val(false), raw, idx), buf_v) @@ -159,21 +161,25 @@ end function split_into_buffers(raw::AbstractArray, buf, recurse = Val(true)) idx = Ref(1) - ntuple(i->_split_helper(buf[i], recurse, raw, idx), Val(length(buf))) + ntuple(i -> _split_helper(buf[i], recurse, raw, idx), Val(length(buf))) +end + +function _update_tuple_helper(buf_v::T, raw, idx) where {T} + _update_tuple_helper(eltype(T), buf_v, raw, idx) end + +function _update_tuple_helper(::Type{<:AbstractArray}, buf_v, raw, idx) + map(b -> _update_tuple_helper(b, raw, idx), buf_v) +end + +function _update_tuple_helper(::Any, buf_v, raw, idx) + copyto!(buf_v, view(raw, idx[]:(idx[] + length(buf_v) - 1))) + idx[] += length(buf_v) end function update_tuple_of_buffers(raw::AbstractArray, buf) - idx = 1 - function _helper(buf_v) - if eltype(buf_v) <: AbstractArray - _helper.(buf_v) - else - copyto!(buf_v, view(raw, idx:(idx + length(buf_v) - 1))) - idx += length(buf_v) - end - end - _helper.(buf) + idx = Ref(1) + map(b -> _update_tuple_helper(b, raw, idx), buf) end SciMLStructures.isscimlstructure(::MTKParameters) = true From d1140480359b5864ee786ab9448fdc72f8d62415 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Mon, 15 Apr 2024 13:33:11 +0530 Subject: [PATCH 056/316] fix: error when all parameters are not initialized --- src/systems/parameter_buffer.jl | 20 +++++++++++++++++++- test/mtkparameters.jl | 13 +++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/src/systems/parameter_buffer.jl b/src/systems/parameter_buffer.jl index 0ef21618ca..47e74e0fbb 100644 --- a/src/systems/parameter_buffer.jl +++ b/src/systems/parameter_buffer.jl @@ -43,7 +43,7 @@ function MTKParameters( p = merge(defs, p) p = merge(Dict(unwrap(k) => v for (k, v) in p), Dict(default_toterm(unwrap(k)) => v for (k, v) in p)) - p = Dict(k => fixpoint_sub(v, p) for (k, v) in p) + p = Dict(unwrap(k) => fixpoint_sub(v, p) for (k, v) in p) for (sym, _) in p if istree(sym) && operation(sym) === getindex && first(arguments(sym)) in all_ps @@ -51,6 +51,24 @@ function MTKParameters( end end + missing_params = Set() + for idxmap in (ic.tunable_idx, ic.discrete_idx, ic.constant_idx, ic.nonnumeric_idx) + for sym in keys(idxmap) + sym isa Symbol && continue + haskey(p, sym) && continue + hasname(sym) && haskey(p, getname(sym)) && continue + ttsym = default_toterm(sym) + haskey(p, ttsym) && continue + hasname(ttsym) && haskey(p, getname(ttsym)) && continue + + istree(sym) && operation(sym) === getindex && haskey(p, arguments(sym)[1]) && + continue + push!(missing_params, sym) + end + end + + isempty(missing_params) || throw(MissingVariablesError(collect(missing_params))) + tunable_buffer = Tuple(Vector{temp.type}(undef, temp.length) for temp in ic.tunable_buffer_sizes) disc_buffer = Tuple(Vector{temp.type}(undef, temp.length) diff --git a/test/mtkparameters.jl b/test/mtkparameters.jl index d6274ef506..10b437f3de 100644 --- a/test/mtkparameters.jl +++ b/test/mtkparameters.jl @@ -121,3 +121,16 @@ ps = MTKParameters(sys, [p => 1.0, q => 2.0, r => 3.0]) newps = remake_buffer(sys, ps, Dict(p => 1.0f0)) @test newps.tunable[1] isa Vector{Float32} @test newps.tunable[1] == [1.0f0, 2.0f0, 3.0f0] + +# Issue#2624 +@parameters p d +@variables X(t) +eqs = [D(X) ~ p - d * X] +@mtkbuild sys = ODESystem(eqs, t) + +u0 = [X => 1.0] +tspan = (0.0, 100.0) +ps = [p => 1.0] # Value for `d` is missing + +@test_throws ModelingToolkit.MissingVariablesError ODEProblem(sys, u0, tspan, ps) +@test_nowarn ODEProblem(sys, u0, tspan, [ps..., d => 1.0]) From c75ca6d19740cdc921188274fef93018d863b461 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Mon, 15 Apr 2024 15:17:07 +0530 Subject: [PATCH 057/316] fix: bug fixes in initialization of `MTKParameters` --- src/systems/diffeqs/abstractodesystem.jl | 61 +++++++++++++----------- src/systems/nonlinear/nonlinearsystem.jl | 2 - 2 files changed, 34 insertions(+), 29 deletions(-) diff --git a/src/systems/diffeqs/abstractodesystem.jl b/src/systems/diffeqs/abstractodesystem.jl index 69a4cf6310..6feff228f2 100644 --- a/src/systems/diffeqs/abstractodesystem.jl +++ b/src/systems/diffeqs/abstractodesystem.jl @@ -876,31 +876,34 @@ function process_DEProblem(constructor, sys::AbstractODESystem, u0map, parammap; parammap = Dict(unwrap.(parameters(sys)) .=> parammap) end end - clockedparammap = Dict() - defs = ModelingToolkit.get_defaults(sys) - for v in ps - v = unwrap(v) - is_discrete_domain(v) || continue - op = operation(v) - if !isa(op, Symbolics.Operator) && parammap != SciMLBase.NullParameters() && - haskey(parammap, v) - error("Initial conditions for discrete variables must be for the past state of the unknown. Instead of providing the condition for $v, provide the condition for $(Shift(iv, -1)(v)).") + + if has_discrete_subsystems(sys) && get_discrete_subsystems(sys) !== nothing + clockedparammap = Dict() + defs = ModelingToolkit.get_defaults(sys) + for v in ps + v = unwrap(v) + is_discrete_domain(v) || continue + op = operation(v) + if !isa(op, Symbolics.Operator) && parammap != SciMLBase.NullParameters() && + haskey(parammap, v) + error("Initial conditions for discrete variables must be for the past state of the unknown. Instead of providing the condition for $v, provide the condition for $(Shift(iv, -1)(v)).") + end + shiftedv = StructuralTransformations.simplify_shifts(Shift(iv, -1)(v)) + if parammap != SciMLBase.NullParameters() && + (val = get(parammap, shiftedv, nothing)) !== nothing + clockedparammap[v] = val + elseif op isa Shift + root = arguments(v)[1] + haskey(defs, root) || error("Initial condition for $v not provided.") + clockedparammap[v] = defs[root] + end end - shiftedv = StructuralTransformations.simplify_shifts(Shift(iv, -1)(v)) - if parammap != SciMLBase.NullParameters() && - (val = get(parammap, shiftedv, nothing)) !== nothing - clockedparammap[v] = val - elseif op isa Shift - root = arguments(v)[1] - haskey(defs, root) || error("Initial condition for $v not provided.") - clockedparammap[v] = defs[root] + parammap = if parammap == SciMLBase.NullParameters() + clockedparammap + else + merge(parammap, clockedparammap) end end - parammap = if parammap == SciMLBase.NullParameters() - clockedparammap - else - merge(parammap, clockedparammap) - end # TODO: make it work with clocks # ModelingToolkit.get_tearing_state(sys) !== nothing => Requires structural_simplify first if sys isa ODESystem && build_initializeprob && @@ -931,7 +934,12 @@ function process_DEProblem(constructor, sys::AbstractODESystem, u0map, parammap; if has_index_cache(sys) && get_index_cache(sys) !== nothing u0, defs = get_u0(sys, trueinit, parammap; symbolic_u0) check_eqs_u0(eqs, dvs, u0; kwargs...) - p = MTKParameters(sys, parammap, trueinit) + p = if parammap === nothing || + parammap == SciMLBase.NullParameters() && isempty(defs) + nothing + else + MTKParameters(sys, parammap, trueinit) + end else u0, p, defs = get_u0_p(sys, trueinit, @@ -1592,7 +1600,6 @@ function InitializationProblem{iip, specialize}(sys::AbstractODESystem, 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) elseif isempty(u0map) && get_initializesystem(sys) === nothing @@ -1620,9 +1627,9 @@ function InitializationProblem{iip, specialize}(sys::AbstractODESystem, @warn "Initialization system is underdetermined. $neqs equations for $nunknown unknowns. Initialization will default to using least squares. To suppress this warning pass warn_initialize_determined = false." end - parammap isa DiffEqBase.NullParameters || isempty(parammap) ? - [get_iv(sys) => t] : - merge(todict(parammap), Dict(get_iv(sys) => t)) + parammap = parammap isa DiffEqBase.NullParameters || isempty(parammap) ? + [get_iv(sys) => t] : + merge(todict(parammap), Dict(get_iv(sys) => t)) if neqs == nunknown NonlinearProblem(isys, guesses, parammap) diff --git a/src/systems/nonlinear/nonlinearsystem.jl b/src/systems/nonlinear/nonlinearsystem.jl index 9d50e11549..8035add4b7 100644 --- a/src/systems/nonlinear/nonlinearsystem.jl +++ b/src/systems/nonlinear/nonlinearsystem.jl @@ -241,7 +241,6 @@ function generate_function( pre, sol_states = get_substitutions_and_solved_unknowns(sys) p = reorder_parameters(sys, value.(ps)) - @show p ps return build_function(rhss, value.(dvs), p...; postprocess_fbody = pre, states = sol_states, kwargs...) end @@ -395,7 +394,6 @@ function process_NonlinearProblem(constructor, sys::NonlinearSystem, u0map, para eqs = equations(sys) dvs = unknowns(sys) ps = full_parameters(sys) - if has_index_cache(sys) && get_index_cache(sys) !== nothing u0, defs = get_u0(sys, u0map, parammap) check_eqs_u0(eqs, dvs, u0; kwargs...) From 802ac1d3e82b566247110f907368af3da8e084f7 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Tue, 2 Apr 2024 19:01:39 +0530 Subject: [PATCH 058/316] fix: fix SymScope metadata for array variables Co-authored-by: contradict --- src/systems/abstractsystem.jl | 51 ++++++++++++++++++++++++++--------- test/variable_scope.jl | 10 +++++++ 2 files changed, 48 insertions(+), 13 deletions(-) diff --git a/src/systems/abstractsystem.jl b/src/systems/abstractsystem.jl index 394e4aab7b..460239c63d 100644 --- a/src/systems/abstractsystem.jl +++ b/src/systems/abstractsystem.jl @@ -746,19 +746,32 @@ end abstract type SymScope end struct LocalScope <: SymScope end -function LocalScope(sym::Union{Num, Symbolic}) +function LocalScope(sym::Union{Num, Symbolic, Symbolics.Arr{Num}}) apply_to_variables(sym) do sym - setmetadata(sym, SymScope, LocalScope()) + if istree(sym) && operation(sym) === getindex + args = arguments(sym) + a1 = setmetadata(args[1], SymScope, LocalScope()) + similarterm(sym, operation(sym), [a1, args[2:end]...]) + else + setmetadata(sym, SymScope, LocalScope()) + end end end struct ParentScope <: SymScope parent::SymScope end -function ParentScope(sym::Union{Num, Symbolic}) +function ParentScope(sym::Union{Num, Symbolic, Symbolics.Arr{Num}}) apply_to_variables(sym) do sym - setmetadata(sym, SymScope, - ParentScope(getmetadata(value(sym), SymScope, LocalScope()))) + if istree(sym) && operation(sym) == getindex + args = arguments(sym) + a1 = setmetadata(args[1], SymScope, + ParentScope(getmetadata(value(args[1]), SymScope, LocalScope()))) + similarterm(sym, operation(sym), [a1, args[2:end]...]) + else + setmetadata(sym, SymScope, + ParentScope(getmetadata(value(sym), SymScope, LocalScope()))) + end end end @@ -766,18 +779,31 @@ struct DelayParentScope <: SymScope parent::SymScope N::Int end -function DelayParentScope(sym::Union{Num, Symbolic}, N) +function DelayParentScope(sym::Union{Num, Symbolic, Symbolics.Arr{Num}}, N) apply_to_variables(sym) do sym - setmetadata(sym, SymScope, - DelayParentScope(getmetadata(value(sym), SymScope, LocalScope()), N)) + if istree(sym) && operation(sym) == getindex + args = arguments(sym) + a1 = setmetadata(args[1], SymScope, + DelayParentScope(getmetadata(value(args[1]), SymScope, LocalScope()), N)) + similarterm(sym, operation(sym), [a1, args[2:end]...]) + else + setmetadata(sym, SymScope, + DelayParentScope(getmetadata(value(sym), SymScope, LocalScope()), N)) + end end end -DelayParentScope(sym::Union{Num, Symbolic}) = DelayParentScope(sym, 1) +DelayParentScope(sym::Union{Num, Symbolic, Symbolics.Arr{Num}}) = DelayParentScope(sym, 1) struct GlobalScope <: SymScope end -function GlobalScope(sym::Union{Num, Symbolic}) +function GlobalScope(sym::Union{Num, Symbolic, Symbolics.Arr{Num}}) apply_to_variables(sym) do sym - setmetadata(sym, SymScope, GlobalScope()) + if istree(sym) && operation(sym) == getindex + args = arguments(sym) + a1 = setmetadata(args[1], SymScope, GlobalScope()) + similarterm(sym, operation(sym), [a1, args[2:end]...]) + else + setmetadata(sym, SymScope, GlobalScope()) + end end end @@ -1500,8 +1526,7 @@ function default_to_parentscope(v) uv isa Symbolic || return v apply_to_variables(v) do sym if !hasmetadata(uv, SymScope) - setmetadata(sym, SymScope, - ParentScope(getmetadata(value(sym), SymScope, LocalScope()))) + ParentScope(sym) else sym end diff --git a/test/variable_scope.jl b/test/variable_scope.jl index b80c004a2b..81a248f08f 100644 --- a/test/variable_scope.jl +++ b/test/variable_scope.jl @@ -73,3 +73,13 @@ ps = ModelingToolkit.getname.(parameters(level3)) @test isequal(ps[4], :level2₊level0₊d) @test isequal(ps[5], :level1₊level0₊e) @test isequal(ps[6], :f) + +# Issue@2252 +# 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 +arr_ps = ModelingToolkit.getname.(parameters(arr1)) +@test isequal(arr_ps[1], Symbol("xx")) +@test isequal(arr_ps[2], Symbol("arr0₊xx")) From e24b1882b35a8e9e916b0736462ae8af7896acd7 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Mon, 1 Apr 2024 16:50:52 +0530 Subject: [PATCH 059/316] test: add tests for array variable namespacing from solved issues --- src/systems/abstractsystem.jl | 3 +- test/odesystem.jl | 54 +++++++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+), 1 deletion(-) diff --git a/src/systems/abstractsystem.jl b/src/systems/abstractsystem.jl index 460239c63d..e4d5018b9e 100644 --- a/src/systems/abstractsystem.jl +++ b/src/systems/abstractsystem.jl @@ -875,7 +875,8 @@ function namespace_assignment(eq::Assignment, sys) Assignment(_lhs, _rhs) end -function namespace_expr(O, sys, n = nameof(sys); ivs = independent_variables(sys)) +function namespace_expr( + O, sys, n = nameof(sys); ivs = independent_variables(sys)) O = unwrap(O) if any(isequal(O), ivs) return O diff --git a/test/odesystem.jl b/test/odesystem.jl index b1a650d94a..18d320701f 100644 --- a/test/odesystem.jl +++ b/test/odesystem.jl @@ -1066,3 +1066,57 @@ prob = SteadyStateProblem(sys, u0, p) @test prob isa SteadyStateProblem prob = SteadyStateProblem(ODEProblem(sys, u0, (0.0, 10.0), p)) @test prob isa SteadyStateProblem + +# Issue#2344 +using ModelingToolkitStandardLibrary.Blocks + +function FML2(; name) + @parameters begin + k2[1:1] = [1.0] + end + systems = @named begin + constant = Constant(k = k2[1]) + end + @variables begin + x(t) = 0 + end + eqs = [ + D(x) ~ constant.output.u + k2[1] + ] + ODESystem(eqs, t; systems, name) +end + +@mtkbuild model = FML2() + +@test isequal(ModelingToolkit.defaults(model)[model.constant.k], model.k2[1]) +@test_nowarn ODEProblem(model, [], (0.0, 10.0)) + +# Issue#2477 +function RealExpression(; name, y) + vars = @variables begin + u(t) + end + eqns = [ + u ~ y + ] + sys = ODESystem(eqns, t, vars, []; name) +end + +function RealExpressionSystem(; name) + vars = @variables begin + x(t) + z(t)[1:1] + end # doing a collect on z doesn't work either. + @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) +end + +@named sys = RealExpressionSystem() +sys = complete(sys) +@test Set(equations(sys)) == Set([sys.e1.u ~ sys.x, sys.e2.u ~ sys.z[1]]) +tearing_state = TearingState(expand_connections(sys)) +ts_vars = tearing_state.fullvars +orig_vars = unknowns(sys) +@test isempty(setdiff(ts_vars, orig_vars)) From 677925bec5a52a8fe68beb4f1f41b2bc5dc2af14 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Wed, 3 Apr 2024 11:12:26 +0530 Subject: [PATCH 060/316] fix: fix renamespace for array variables --- src/systems/abstractsystem.jl | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/systems/abstractsystem.jl b/src/systems/abstractsystem.jl index e4d5018b9e..afe3b1419e 100644 --- a/src/systems/abstractsystem.jl +++ b/src/systems/abstractsystem.jl @@ -763,7 +763,7 @@ struct ParentScope <: SymScope end function ParentScope(sym::Union{Num, Symbolic, Symbolics.Arr{Num}}) apply_to_variables(sym) do sym - if istree(sym) && operation(sym) == getindex + if istree(sym) && operation(sym) === getindex args = arguments(sym) a1 = setmetadata(args[1], SymScope, ParentScope(getmetadata(value(args[1]), SymScope, LocalScope()))) @@ -819,6 +819,11 @@ function renamespace(sys, x) return similarterm(x, operation(x), Any[renamespace(sys, only(arguments(x)))])::T end + if istree(x) && operation(x) === getindex + args = arguments(x) + return similarterm( + x, operation(x), vcat(renamespace(sys, args[1]), args[2:end]))::T + end let scope = getmetadata(x, SymScope, LocalScope()) if scope isa LocalScope rename(x, renamespace(getname(sys), getname(x)))::T From 8ef7908907a39e8e8659856f42df94bd2694870e Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Thu, 11 Apr 2024 15:45:47 +0530 Subject: [PATCH 061/316] test: fix input_output_handling and odesystem tests --- test/input_output_handling.jl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/input_output_handling.jl b/test/input_output_handling.jl index 2aff6d44c0..41b27aad4a 100644 --- a/test/input_output_handling.jl +++ b/test/input_output_handling.jl @@ -20,9 +20,9 @@ end @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) ~ -sys.x], t, systems = [sys1]) # 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) ~ -sys.x + sys1.v[1]], t, systems = [sys1]) # This binds both sys.x and sys1.v[1] +@named sys31 = ODESystem([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 @@ -43,7 +43,7 @@ end @test is_bound(sys2, sys.x) @test !is_bound(sys2, sys.u) @test !is_bound(sys2, sys2.sys.u) -@test is_bound(sys21, sys.x) +@test is_bound(sys21, sys1.x) @test !is_bound(sys21, sys1.v[1]) @test !is_bound(sys21, sys1.v[2]) @test is_bound(sys31, sys1.v[1]) From 745e88956fe70771e27ef7843e4e1ece64b18445 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Tue, 16 Apr 2024 13:28:18 +0530 Subject: [PATCH 062/316] fix: fix incorrect indexes of array symbolics --- src/systems/index_cache.jl | 20 ++++++++++++++++++-- test/odesystem.jl | 13 +++++++++++++ 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/src/systems/index_cache.jl b/src/systems/index_cache.jl index 5b0e87edff..fd23f97181 100644 --- a/src/systems/index_cache.jl +++ b/src/systems/index_cache.jl @@ -17,7 +17,8 @@ struct ParameterIndex{P, I} end const ParamIndexMap = Dict{Union{Symbol, BasicSymbolic}, Tuple{Int, Int}} -const UnknownIndexMap = Dict{Union{Symbol, BasicSymbolic}, Union{Int, UnitRange{Int}}} +const UnknownIndexMap = Dict{ + Union{Symbol, BasicSymbolic}, Union{Int, UnitRange{Int}, Array{Int}}} struct IndexCache unknown_idx::UnknownIndexMap @@ -46,11 +47,26 @@ function IndexCache(sys::AbstractSystem) end unk_idxs[usym] = sym_idx - if hasname(sym) + if hasname(sym) && (!istree(sym) || operation(sym) !== getindex) unk_idxs[getname(usym)] = sym_idx end idx += length(sym) end + for sym in unks + usym = unwrap(sym) + istree(sym) && operation(sym) === getindex || continue + arrsym = arguments(sym)[1] + all(haskey(unk_idxs, arrsym[i]) for i in eachindex(arrsym)) || continue + + idxs = [unk_idxs[arrsym[i]] for i in eachindex(arrsym)] + if idxs == idxs[begin]:idxs[end] + idxs = idxs[begin]:idxs[end] + end + unk_idxs[arrsym] = idxs + if hasname(arrsym) + unk_idxs[getname(arrsym)] = idxs + end + end end disc_buffers = Dict{Any, Set{BasicSymbolic}}() diff --git a/test/odesystem.jl b/test/odesystem.jl index 18d320701f..b98602c256 100644 --- a/test/odesystem.jl +++ b/test/odesystem.jl @@ -1120,3 +1120,16 @@ tearing_state = TearingState(expand_connections(sys)) ts_vars = tearing_state.fullvars orig_vars = unknowns(sys) @test isempty(setdiff(ts_vars, orig_vars)) + +# Ensure indexes of array symbolics are cached appropriately +@variables x(t)[1:2] +@named sys = ODESystem(Equation[], t, [x], []) +sys1 = complete(sys) +@named sys = ODESystem(Equation[], t, [x...], []) +sys2 = complete(sys) +for sys in [sys1, sys2] + for (sym, idx) in [(x, 1:2), (x[1], 1), (x[2], 2)] + @test is_variable(sys, sym) + @test variable_index(sys, sym) == idx + end +end From 41018eabb25726210fd0cddcce0ce0d5c0843b82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Miclu=C8=9Ba-C=C3=A2mpeanu?= Date: Tue, 16 Apr 2024 17:06:14 +0300 Subject: [PATCH 063/316] perf: make `buffer_to_arraypartition` type inferable --- src/systems/parameter_buffer.jl | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/systems/parameter_buffer.jl b/src/systems/parameter_buffer.jl index 4428e8bbb0..3f1d828f11 100644 --- a/src/systems/parameter_buffer.jl +++ b/src/systems/parameter_buffer.jl @@ -137,10 +137,13 @@ function MTKParameters( end function buffer_to_arraypartition(buf) - return ArrayPartition(Tuple(eltype(v) <: AbstractArray ? buffer_to_arraypartition(v) : - v for v in buf)) + return ArrayPartition(ntuple(i -> _buffer_to_arrp_helper(buf[i]), Val(length(buf)))) end +_buffer_to_arrp_helper(v::T) where {T} = _buffer_to_arrp_helper(eltype(T), v) +_buffer_to_arrp_helper(::Type{<:AbstractArray}, v) = buffer_to_arraypartition(v) +_buffer_to_arrp_helper(::Any, v) = v + function _split_helper(buf_v::T, recurse, raw, idx) where {T} _split_helper(eltype(T), buf_v, recurse, raw, idx) end From 2da4f8a5667621801cea60b42c5a2f0a1d801a88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Miclu=C8=9Ba-C=C3=A2mpeanu?= Date: Tue, 16 Apr 2024 17:07:03 +0300 Subject: [PATCH 064/316] perf: make update_tuple_of_buffers type stable --- src/systems/parameter_buffer.jl | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/systems/parameter_buffer.jl b/src/systems/parameter_buffer.jl index 3f1d828f11..9195a6db1f 100644 --- a/src/systems/parameter_buffer.jl +++ b/src/systems/parameter_buffer.jl @@ -172,17 +172,18 @@ function _update_tuple_helper(buf_v::T, raw, idx) where {T} end function _update_tuple_helper(::Type{<:AbstractArray}, buf_v, raw, idx) - map(b -> _update_tuple_helper(b, raw, idx), buf_v) + ntuple(i -> _update_tuple_helper(buf_v[i], raw, idx), Val(length(buf_v))) end function _update_tuple_helper(::Any, buf_v, raw, idx) copyto!(buf_v, view(raw, idx[]:(idx[] + length(buf_v) - 1))) idx[] += length(buf_v) + return nothing end function update_tuple_of_buffers(raw::AbstractArray, buf) idx = Ref(1) - map(b -> _update_tuple_helper(b, raw, idx), buf) + ntuple(i -> _update_tuple_helper(buf[i], raw, idx), Val(length(buf))) end SciMLStructures.isscimlstructure(::MTKParameters) = true From 43460945dbd7d4010c56e1e9c0cdd1779b100eb9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Miclu=C8=9Ba-C=C3=A2mpeanu?= Date: Tue, 16 Apr 2024 20:50:41 +0300 Subject: [PATCH 065/316] test: add type inference tests --- Project.toml | 3 +- test/mtkparameters.jl | 72 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 74 insertions(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 99c2ceb73b..e1fae04f76 100644 --- a/Project.toml +++ b/Project.toml @@ -119,6 +119,7 @@ DelayDiffEq = "bcd4f6db-9728-5f36-b5f7-82caef46ccdb" ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210" Ipopt = "b6b21f68-93f8-5de0-b562-5493be1d77c9" Ipopt_jll = "9cc047cb-c261-5740-88fc-0cf96f7bdcc7" +JET = "c3a54625-cd67-489e-a8e7-0a5a0ff4e31b" ModelingToolkitStandardLibrary = "16a59e39-deab-5bd0-87e4-056b12336739" NonlinearSolve = "8913a72c-1f9b-4ce2-8d82-65094dcecaec" Optimization = "7f7a1694-90dd-40f0-9382-eb1efda571ba" @@ -137,4 +138,4 @@ Sundials = "c3572dad-4567-51f8-b174-8c6c989267f4" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" [targets] -test = ["AmplNLWriter", "BenchmarkTools", "ControlSystemsBase", "DelayDiffEq", "NonlinearSolve", "ForwardDiff", "Ipopt", "Ipopt_jll", "ModelingToolkitStandardLibrary", "Optimization", "OptimizationOptimJL", "OptimizationMOI", "Random", "ReferenceTests", "SafeTestsets", "StableRNGs", "Statistics", "SteadyStateDiffEq", "Test", "StochasticDiffEq", "Sundials", "StochasticDelayDiffEq", "Pkg"] +test = ["AmplNLWriter", "BenchmarkTools", "ControlSystemsBase", "DelayDiffEq", "NonlinearSolve", "ForwardDiff", "Ipopt", "Ipopt_jll", "ModelingToolkitStandardLibrary", "Optimization", "OptimizationOptimJL", "OptimizationMOI", "Random", "ReferenceTests", "SafeTestsets", "StableRNGs", "Statistics", "SteadyStateDiffEq", "Test", "StochasticDiffEq", "Sundials", "StochasticDelayDiffEq", "Pkg", "JET"] diff --git a/test/mtkparameters.jl b/test/mtkparameters.jl index d6274ef506..54b7403052 100644 --- a/test/mtkparameters.jl +++ b/test/mtkparameters.jl @@ -3,6 +3,7 @@ using ModelingToolkit: t_nounits as t, D_nounits as D, MTKParameters using SymbolicIndexingInterface using SciMLStructures: SciMLStructures, canonicalize, Tunable, Discrete, Constants using ForwardDiff +using JET @parameters a b c d::Integer e[1:3] f[1:3, 1:3]::Int g::Vector{AbstractFloat} h::String @named sys = ODESystem( @@ -121,3 +122,74 @@ ps = MTKParameters(sys, [p => 1.0, q => 2.0, r => 3.0]) newps = remake_buffer(sys, ps, Dict(p => 1.0f0)) @test newps.tunable[1] isa Vector{Float32} @test newps.tunable[1] == [1.0f0, 2.0f0, 3.0f0] + +# JET tests + +# scalar parameters only +function level1() + @parameters p1=0.5 [tunable = true] p2 = 1 [tunable=true] p3 = 3 [tunable = false] p4=3 [tunable = true] y0=1 + @variables x(t)=2 y(t)=y0 + D = Differential(t) + + eqs = [D(x) ~ p1 * x - p2 * x * y + D(y) ~ -p3 * y + p4 * x * y] + + sys = structural_simplify(complete(ODESystem( + eqs, t, tspan = (0, 3.0), name = :sys, parameter_dependencies = [y0 => 2p4]))) + prob = ODEProblem{true, SciMLBase.FullSpecialize}(sys) +end + +# scalar and vector parameters +function level2() + @parameters p1=0.5 [tunable = true] (p23[1:2]=[1, 3.0]) [tunable = true] p4=3 [tunable = false] y0=1 + @variables x(t)=2 y(t)=y0 + D = Differential(t) + + eqs = [D(x) ~ p1 * x - p23[1] * x * y + D(y) ~ -p23[2] * y + p4 * x * y] + + sys = structural_simplify(complete(ODESystem( + eqs, t, tspan = (0, 3.0), name = :sys, parameter_dependencies = [y0 => 2p4]))) + prob = ODEProblem{true, SciMLBase.FullSpecialize}(sys) +end + +# scalar and vector parameters with different scalar types +function level3() + @parameters p1=0.5 [tunable = true] (p23[1:2]=[1, 3.0]) [tunable = true] p4::Int=3 [tunable = true] y0::Int=1 + @variables x(t)=2 y(t)=y0 + D = Differential(t) + + eqs = [D(x) ~ p1 * x - p23[1] * x * y + D(y) ~ -p23[2] * y + p4 * x * y] + + sys = structural_simplify(complete(ODESystem( + eqs, t, tspan = (0, 3.0), name = :sys, parameter_dependencies = [y0 => 2p4]))) + prob = ODEProblem{true, SciMLBase.FullSpecialize}(sys) +end + +@testset "level$i" for (i, prob) in enumerate([level1(), level2(), level3()]) + ps = prob.p + @testset "Type stability of $portion" for portion in [Tunable(), Discrete(), Constants()] + @test_call canonicalize(portion, ps) + # @inferred canonicalize(portion, ps) + broken = + (i ∈ [2,3] && portion == Tunable()) + + # broken because the size of a vector of vectors can't be determined at compile time + @test_opt broken=broken target_modules = (ModelingToolkit,) canonicalize( + portion, ps) + + buffer, repack, alias = canonicalize(portion, ps) + + @test_call SciMLStructures.replace(portion, ps, ones(length(buffer))) + @inferred SciMLStructures.replace(portion, ps, ones(length(buffer))) + @test_opt target_modules=(ModelingToolkit,) SciMLStructures.replace( + portion, ps, ones(length(buffer))) + + @test_call target_modules = (ModelingToolkit,) SciMLStructures.replace!( + portion, ps, ones(length(buffer))) + @inferred SciMLStructures.replace!(portion, ps, ones(length(buffer))) + @test_opt target_modules=(ModelingToolkit,) SciMLStructures.replace!( + portion, ps, ones(length(buffer))) + end +end From f440f7668d4d769ed07945ce311ba168fe2eaea7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Miclu=C8=9Ba-C=C3=A2mpeanu?= Date: Tue, 16 Apr 2024 21:05:50 +0300 Subject: [PATCH 066/316] style: fix formatting --- test/mtkparameters.jl | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/test/mtkparameters.jl b/test/mtkparameters.jl index 2823b44f32..09ce04bf59 100644 --- a/test/mtkparameters.jl +++ b/test/mtkparameters.jl @@ -136,12 +136,11 @@ ps = [p => 1.0] # Value for `d` is missing @test_throws ModelingToolkit.MissingVariablesError ODEProblem(sys, u0, tspan, ps) @test_nowarn ODEProblem(sys, u0, tspan, [ps..., d => 1.0]) - # JET tests # scalar parameters only function level1() - @parameters p1=0.5 [tunable = true] p2 = 1 [tunable=true] p3 = 3 [tunable = false] p4=3 [tunable = true] y0=1 + @parameters p1=0.5 [tunable = true] p2=1 [tunable = true] p3=3 [tunable = false] p4=3 [tunable = true] y0=1 @variables x(t)=2 y(t)=y0 D = Differential(t) @@ -183,14 +182,14 @@ end @testset "level$i" for (i, prob) in enumerate([level1(), level2(), level3()]) ps = prob.p - @testset "Type stability of $portion" for portion in [Tunable(), Discrete(), Constants()] + @testset "Type stability of $portion" for portion in [ + Tunable(), Discrete(), Constants()] @test_call canonicalize(portion, ps) # @inferred canonicalize(portion, ps) - broken = - (i ∈ [2,3] && portion == Tunable()) + broken = (i ∈ [2, 3] && portion == Tunable()) # broken because the size of a vector of vectors can't be determined at compile time - @test_opt broken=broken target_modules = (ModelingToolkit,) canonicalize( + @test_opt broken=broken target_modules=(ModelingToolkit,) canonicalize( portion, ps) buffer, repack, alias = canonicalize(portion, ps) @@ -200,7 +199,7 @@ end @test_opt target_modules=(ModelingToolkit,) SciMLStructures.replace( portion, ps, ones(length(buffer))) - @test_call target_modules = (ModelingToolkit,) SciMLStructures.replace!( + @test_call target_modules=(ModelingToolkit,) SciMLStructures.replace!( portion, ps, ones(length(buffer))) @inferred SciMLStructures.replace!(portion, ps, ones(length(buffer))) @test_opt target_modules=(ModelingToolkit,) SciMLStructures.replace!( From c9498e25832b3cd53deaeaad1f15574a426189fe Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Wed, 17 Apr 2024 11:11:32 +0530 Subject: [PATCH 067/316] fix: fix IndexCache stored indices for array unknowns --- src/systems/index_cache.jl | 8 ++++---- test/odesystem.jl | 14 ++++++++++++++ 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/src/systems/index_cache.jl b/src/systems/index_cache.jl index fd23f97181..fb1b3f75c1 100644 --- a/src/systems/index_cache.jl +++ b/src/systems/index_cache.jl @@ -18,7 +18,7 @@ end const ParamIndexMap = Dict{Union{Symbol, BasicSymbolic}, Tuple{Int, Int}} const UnknownIndexMap = Dict{ - Union{Symbol, BasicSymbolic}, Union{Int, UnitRange{Int}, Array{Int}}} + Union{Symbol, BasicSymbolic}, Union{Int, UnitRange{Int}, AbstractArray{Int}}} struct IndexCache unknown_idx::UnknownIndexMap @@ -41,7 +41,7 @@ function IndexCache(sys::AbstractSystem) for sym in unks usym = unwrap(sym) sym_idx = if Symbolics.isarraysymbolic(sym) - idx:(idx + length(sym) - 1) + reshape(idx:(idx + length(sym) - 1), size(sym)) else idx end @@ -60,7 +60,7 @@ function IndexCache(sys::AbstractSystem) idxs = [unk_idxs[arrsym[i]] for i in eachindex(arrsym)] if idxs == idxs[begin]:idxs[end] - idxs = idxs[begin]:idxs[end] + idxs = reshape(idxs[begin]:idxs[end], size(idxs)) end unk_idxs[arrsym] = idxs if hasname(arrsym) @@ -140,7 +140,7 @@ function IndexCache(sys::AbstractSystem) for (j, p) in enumerate(buf) idxs[p] = (i, j) idxs[default_toterm(p)] = (i, j) - if hasname(p) + if hasname(p) && (!istree(p) || operation(p) !== getindex) idxs[getname(p)] = (i, j) idxs[getname(default_toterm(p))] = (i, j) end diff --git a/test/odesystem.jl b/test/odesystem.jl index b98602c256..6841d81be8 100644 --- a/test/odesystem.jl +++ b/test/odesystem.jl @@ -1133,3 +1133,17 @@ for sys in [sys1, sys2] @test variable_index(sys, sym) == idx end end + +@variables x(t)[1:2, 1:2] +@named sys = ODESystem(Equation[], t, [x], []) +sys1 = complete(sys) +@named sys = ODESystem(Equation[], t, [x...], []) +sys2 = complete(sys) +for sys in [sys1, sys2] + @test is_variable(sys, x) + @test variable_index(sys, x) == [1 3; 2 4] + for i in eachindex(x) + @test is_variable(sys, x[i]) + @test variable_index(sys, x[i]) == variable_index(sys, x)[i] + end +end From d2df4c3802f0eb5faec2b8789096340df3820d72 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Tue, 16 Apr 2024 15:50:51 +0530 Subject: [PATCH 068/316] fix: implement `DiffEqBase.anyeltypedual` for `MTKParameters` --- docs/src/examples/remake.md | 5 +---- src/systems/parameter_buffer.jl | 9 +++++++++ test/mtkparameters.jl | 22 ++++++++++++++++++++++ 3 files changed, 32 insertions(+), 4 deletions(-) diff --git a/docs/src/examples/remake.md b/docs/src/examples/remake.md index 8cd1f9b81c..de6aa77d74 100644 --- a/docs/src/examples/remake.md +++ b/docs/src/examples/remake.md @@ -51,11 +51,8 @@ function loss(x, p) odeprob = p[1] # ODEProblem stored as parameters to avoid using global variables ps = parameter_values(odeprob) # obtain the parameter object from the problem ps = replace(Tunable(), ps, x) # create a copy with the values passed to the loss function - T = eltype(x) - # we also have to convert the `u0` vector - u0 = T.(state_values(odeprob)) # remake the problem, passing in our new parameter object - newprob = remake(odeprob; u0 = u0, p = ps) + newprob = remake(odeprob; p = ps) timesteps = p[2] sol = solve(newprob, AutoTsit5(Rosenbrock23()); saveat = timesteps) truth = p[3] diff --git a/src/systems/parameter_buffer.jl b/src/systems/parameter_buffer.jl index 47e74e0fbb..bbcf074561 100644 --- a/src/systems/parameter_buffer.jl +++ b/src/systems/parameter_buffer.jl @@ -384,6 +384,15 @@ function SymbolicIndexingInterface.remake_buffer(sys, oldbuf::MTKParameters, val return newbuf end +function DiffEqBase.anyeltypedual( + p::MTKParameters, ::Type{Val{counter}} = Val{0}) where {counter} + DiffEqBase.anyeltypedual(p.tunable) +end +function DiffEqBase.anyeltypedual(p::Type{<:MTKParameters{T}}, + ::Type{Val{counter}} = Val{0}) where {counter} where {T} + DiffEqBase.__anyeltypedual(T) +end + _subarrays(v::AbstractVector) = isempty(v) ? () : (v,) _subarrays(v::ArrayPartition) = v.x _subarrays(v::Tuple) = v diff --git a/test/mtkparameters.jl b/test/mtkparameters.jl index 10b437f3de..f1568a662a 100644 --- a/test/mtkparameters.jl +++ b/test/mtkparameters.jl @@ -2,6 +2,7 @@ using ModelingToolkit using ModelingToolkit: t_nounits as t, D_nounits as D, MTKParameters using SymbolicIndexingInterface using SciMLStructures: SciMLStructures, canonicalize, Tunable, Discrete, Constants +using OrdinaryDiffEq using ForwardDiff @parameters a b c d::Integer e[1:3] f[1:3, 1:3]::Int g::Vector{AbstractFloat} h::String @@ -134,3 +135,24 @@ ps = [p => 1.0] # Value for `d` is missing @test_throws ModelingToolkit.MissingVariablesError ODEProblem(sys, u0, tspan, ps) @test_nowarn ODEProblem(sys, u0, tspan, [ps..., d => 1.0]) + +# Issue#2642 +@parameters α β γ δ +@variables x(t) y(t) +eqs = [D(x) ~ (α - β * y) * x + D(y) ~ (δ * x - γ) * y] +@mtkbuild odesys = ODESystem(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) +@test tunables isa AbstractVector{Float64} + +function loss(x) + ps = odeprob.p + newps = SciMLStructures.replace(Tunable(), ps, x) + newprob = remake(odeprob, p = newps) + sol = solve(newprob, Tsit5()) + return sum(sol) +end + +@test_nowarn ForwardDiff.gradient(loss, collect(tunables)) From a9f425148cef6abc520dbef5690edfcf9065e060 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Wed, 17 Apr 2024 12:49:45 +0530 Subject: [PATCH 069/316] fix: fix propagation of guesses in hierarchical systems --- src/systems/abstractsystem.jl | 13 ++++++++++++- src/systems/diffeqs/odesystem.jl | 1 + test/odesystem.jl | 12 ++++++++++++ 3 files changed, 25 insertions(+), 1 deletion(-) diff --git a/src/systems/abstractsystem.jl b/src/systems/abstractsystem.jl index afe3b1419e..5222861c02 100644 --- a/src/systems/abstractsystem.jl +++ b/src/systems/abstractsystem.jl @@ -859,6 +859,11 @@ function namespace_defaults(sys) for (k, v) in pairs(defs)) end +function namespace_guesses(sys) + guess = guesses(sys) + Dict(unknowns(sys, k) => namespace_expr(v, sys) for (k, v) in guess) +end + function namespace_equations(sys::AbstractSystem, ivs = independent_variables(sys)) eqs = equations(sys) isempty(eqs) && return Equation[] @@ -968,7 +973,13 @@ function full_parameters(sys::AbstractSystem) end function guesses(sys::AbstractSystem) - get_guesses(sys) + guess = get_guesses(sys) + systems = get_systems(sys) + isempty(systems) && return guess + for subsys in systems + guess = merge(guess, namespace_guesses(subsys)) + end + return guess end # required in `src/connectors.jl:437` diff --git a/src/systems/diffeqs/odesystem.jl b/src/systems/diffeqs/odesystem.jl index 34773524c9..d2a112e9a9 100644 --- a/src/systems/diffeqs/odesystem.jl +++ b/src/systems/diffeqs/odesystem.jl @@ -354,6 +354,7 @@ function flatten(sys::ODESystem, noeqs = false) get_iv(sys), unknowns(sys), parameters(sys), + guesses = guesses(sys), observed = observed(sys), continuous_events = continuous_events(sys), discrete_events = discrete_events(sys), diff --git a/test/odesystem.jl b/test/odesystem.jl index 18d320701f..9a1292d10b 100644 --- a/test/odesystem.jl +++ b/test/odesystem.jl @@ -1120,3 +1120,15 @@ tearing_state = TearingState(expand_connections(sys)) ts_vars = tearing_state.fullvars orig_vars = unknowns(sys) @test isempty(setdiff(ts_vars, orig_vars)) + +# Guesses in hierarchical systems +@variables x(t) y(t) +@named sys = ODESystem(Equation[], t, [x], []; guesses = [x => 1.0]) +@named outer = ODESystem( + [D(y) ~ sys.x + t, 0 ~ t + y - sys.x * y], t, [y], []; systems = [sys]) +@test ModelingToolkit.guesses(outer)[sys.x] == 1.0 +outer = structural_simplify(outer) +@test ModelingToolkit.get_guesses(outer)[sys.x] == 1.0 +prob = ODEProblem(outer, [outer.y => 2.0], (0.0, 10.0)) +int = init(prob, Rodas4()) +@test int[outer.sys.x] == 1.0 From ddb250cc0938c644d60283474fa0fef6a83a48a2 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Wed, 17 Apr 2024 13:01:24 +0530 Subject: [PATCH 070/316] fix: fix getguess for array variables --- src/variables.jl | 4 +--- test/test_variable_metadata.jl | 12 ++++++++++++ 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/variables.jl b/src/variables.jl index ed9edcffb4..7fce2f5ad4 100644 --- a/src/variables.jl +++ b/src/variables.jl @@ -456,7 +456,7 @@ end ## Guess ====================================================================== struct VariableGuess end Symbolics.option_to_metadata_type(::Val{:guess}) = VariableGuess -getguess(x::Num) = getguess(Symbolics.unwrap(x)) +getguess(x::Union{Num, Symbolics.Arr}) = getguess(Symbolics.unwrap(x)) """ getguess(x) @@ -469,8 +469,6 @@ Create variables with a guess like this ``` """ function getguess(x) - p = Symbolics.getparent(x, nothing) - p === nothing || (x = p) Symbolics.getmetadata(x, VariableGuess, nothing) end diff --git a/test/test_variable_metadata.jl b/test/test_variable_metadata.jl index cdaeae8b93..22b436301f 100644 --- a/test/test_variable_metadata.jl +++ b/test/test_variable_metadata.jl @@ -16,6 +16,18 @@ using ModelingToolkit @test hasguess(y) === true @test ModelingToolkit.dump_variable_metadata(y).guess == 0 +# Issue#2653 +@variables y[1:3] [guess = ones(3)] +@test getguess(y) == ones(3) +@test hasguess(y) === true +@test ModelingToolkit.dump_variable_metadata(y).guess == ones(3) + +for i in 1:3 + @test getguess(y[i]) == 1.0 + @test hasguess(y[i]) === true + @test ModelingToolkit.dump_variable_metadata(y[i]).guess == 1.0 +end + @variables y @test hasguess(y) === false @test !haskey(ModelingToolkit.dump_variable_metadata(y), :guess) From c6387146f426a1aa2e53c1e9d3f8aaf0d03fa774 Mon Sep 17 00:00:00 2001 From: Christopher Rackauckas Date: Wed, 17 Apr 2024 08:54:07 -0400 Subject: [PATCH 071/316] Update Project.toml --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 99c2ceb73b..03fb5f2419 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.11.0" +version = "9.12.0" [deps] AbstractTrees = "1520ce14-60c1-5f80-bbc7-55ef81b5835c" From d2684e66e0d15efd9965473fad4086e5a8d9b794 Mon Sep 17 00:00:00 2001 From: Vaibhav Dixit Date: Wed, 17 Apr 2024 16:59:20 -0400 Subject: [PATCH 072/316] Allow Array unknowns in OptimizationSystem --- src/systems/optimization/optimizationsystem.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/systems/optimization/optimizationsystem.jl b/src/systems/optimization/optimizationsystem.jl index faa324f4fd..4494e61074 100644 --- a/src/systems/optimization/optimizationsystem.jl +++ b/src/systems/optimization/optimizationsystem.jl @@ -26,7 +26,7 @@ struct OptimizationSystem <: AbstractOptimizationSystem """Objective function of the system.""" op::Any """Unknown variables.""" - unknowns::Vector + unknowns::Array """Parameters.""" ps::Vector """Array variables.""" From f5f8ffe4739fc9d4aece17865c17a78a630eca36 Mon Sep 17 00:00:00 2001 From: Yingbo Ma Date: Mon, 22 Apr 2024 14:13:11 -0400 Subject: [PATCH 073/316] Always attach metadata in `similarterm` --- src/systems/abstractsystem.jl | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/systems/abstractsystem.jl b/src/systems/abstractsystem.jl index 5222861c02..b4452bf11c 100644 --- a/src/systems/abstractsystem.jl +++ b/src/systems/abstractsystem.jl @@ -817,12 +817,14 @@ function renamespace(sys, x) T = typeof(x) if istree(x) && operation(x) isa Operator return similarterm(x, operation(x), - Any[renamespace(sys, only(arguments(x)))])::T + Any[renamespace(sys, only(arguments(x)))]; + metadata = metadata(x))::T end if istree(x) && operation(x) === getindex args = arguments(x) return similarterm( - x, operation(x), vcat(renamespace(sys, args[1]), args[2:end]))::T + x, operation(x), vcat(renamespace(sys, args[1]), args[2:end]); + metadata = metadata(x))::T end let scope = getmetadata(x, SymScope, LocalScope()) if scope isa LocalScope From 87344b4d80801884b748ffd35586961cf84828a5 Mon Sep 17 00:00:00 2001 From: Yingbo Ma Date: Mon, 22 Apr 2024 14:19:58 -0400 Subject: [PATCH 074/316] Add metadata to all similarterm with missing metadata --- src/systems/abstractsystem.jl | 12 ++++++++---- src/systems/diffeqs/abstractodesystem.jl | 3 ++- src/utils.jl | 6 ++++-- 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/src/systems/abstractsystem.jl b/src/systems/abstractsystem.jl index b4452bf11c..ee527dc820 100644 --- a/src/systems/abstractsystem.jl +++ b/src/systems/abstractsystem.jl @@ -751,7 +751,8 @@ function LocalScope(sym::Union{Num, Symbolic, Symbolics.Arr{Num}}) if istree(sym) && operation(sym) === getindex args = arguments(sym) a1 = setmetadata(args[1], SymScope, LocalScope()) - similarterm(sym, operation(sym), [a1, args[2:end]...]) + similarterm(sym, operation(sym), [a1, args[2:end]...]; + metadata = metadata(sym)) else setmetadata(sym, SymScope, LocalScope()) end @@ -767,7 +768,8 @@ function ParentScope(sym::Union{Num, Symbolic, Symbolics.Arr{Num}}) args = arguments(sym) a1 = setmetadata(args[1], SymScope, ParentScope(getmetadata(value(args[1]), SymScope, LocalScope()))) - similarterm(sym, operation(sym), [a1, args[2:end]...]) + similarterm(sym, operation(sym), [a1, args[2:end]...]; + metadata = metadata(sym)) else setmetadata(sym, SymScope, ParentScope(getmetadata(value(sym), SymScope, LocalScope()))) @@ -785,7 +787,8 @@ function DelayParentScope(sym::Union{Num, Symbolic, Symbolics.Arr{Num}}, N) args = arguments(sym) a1 = setmetadata(args[1], SymScope, DelayParentScope(getmetadata(value(args[1]), SymScope, LocalScope()), N)) - similarterm(sym, operation(sym), [a1, args[2:end]...]) + similarterm(sym, operation(sym), [a1, args[2:end]...]; + metadata = metadata(sym)) else setmetadata(sym, SymScope, DelayParentScope(getmetadata(value(sym), SymScope, LocalScope()), N)) @@ -800,7 +803,8 @@ function GlobalScope(sym::Union{Num, Symbolic, Symbolics.Arr{Num}}) if istree(sym) && operation(sym) == getindex args = arguments(sym) a1 = setmetadata(args[1], SymScope, GlobalScope()) - similarterm(sym, operation(sym), [a1, args[2:end]...]) + similarterm(sym, operation(sym), [a1, args[2:end]...]; + metadata = metadata(sym)) else setmetadata(sym, SymScope, GlobalScope()) end diff --git a/src/systems/diffeqs/abstractodesystem.jl b/src/systems/diffeqs/abstractodesystem.jl index 6feff228f2..59b2e8462d 100644 --- a/src/systems/diffeqs/abstractodesystem.jl +++ b/src/systems/diffeqs/abstractodesystem.jl @@ -232,7 +232,8 @@ function delay_to_function(expr, iv, sts, ps, h) elseif istree(expr) return similarterm(expr, operation(expr), - map(x -> delay_to_function(x, iv, sts, ps, h), arguments(expr))) + map(x -> delay_to_function(x, iv, sts, ps, h), arguments(expr)); + metadata = metadata(expr)) else return expr end diff --git a/src/utils.jl b/src/utils.jl index 363c43378e..547399c832 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -21,14 +21,16 @@ function detime_dvs(op) elseif issym(operation(op)) Sym{Real}(nameof(operation(op))) else - similarterm(op, operation(op), detime_dvs.(arguments(op))) + similarterm(op, operation(op), detime_dvs.(arguments(op)); + metadata = metadata(op)) end end function retime_dvs(op, dvs, iv) issym(op) && return Sym{FnType{Tuple{symtype(iv)}, Real}}(nameof(op))(iv) istree(op) ? - similarterm(op, operation(op), retime_dvs.(arguments(op), (dvs,), (iv,))) : + similarterm(op, operation(op), retime_dvs.(arguments(op), (dvs,), (iv,)); + metadata = metadata(op)) : op end From b4f14a4fae26a11436b36bc3a968b5cb6b5eb7c7 Mon Sep 17 00:00:00 2001 From: Yingbo Ma Date: Mon, 22 Apr 2024 20:00:07 -0400 Subject: [PATCH 075/316] Update Project.toml --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 86cc9b0d52..6a7a0bcecf 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.12.0" +version = "9.12.1" [deps] AbstractTrees = "1520ce14-60c1-5f80-bbc7-55ef81b5835c" From ae9415341bc9d73a4c4b3efa3ca503f5bf3f2549 Mon Sep 17 00:00:00 2001 From: Venkateshprasad <32921645+ven-k@users.noreply.github.com> Date: Mon, 22 Apr 2024 14:47:55 +0530 Subject: [PATCH 076/316] fix: improve handling of expression in metadata The variables in mtkmodel can have complicated expressions as `guess` and other metadata. Instead of evaluating them while defining the mtk-model, make those exprs part of the the definition. Along with this, the metadata (in structure dict) is kept in expr form. --- docs/src/basics/MTKModel_Connector.md | 5 +- src/systems/model_parsing.jl | 66 +++++++++++++++++++-------- test/model_parsing.jl | 6 +-- 3 files changed, 54 insertions(+), 23 deletions(-) diff --git a/docs/src/basics/MTKModel_Connector.md b/docs/src/basics/MTKModel_Connector.md index 5282b7db18..7b92e19ceb 100644 --- a/docs/src/basics/MTKModel_Connector.md +++ b/docs/src/basics/MTKModel_Connector.md @@ -408,12 +408,13 @@ The conditional parts are reflected in the `structure`. For `BranchOutsideTheBlo ```julia julia> BranchOutsideTheBlock.structure -Dict{Symbol, Any} with 6 entries: +Dict{Symbol, Any} with 7 entries: :components => Any[(:if, :flag, Vector{Union{Expr, Symbol}}[[:sys1, :C]], Any[])] :kwargs => Dict{Symbol, Dict}(:flag=>Dict{Symbol, Bool}(:value=>1)) :structural_parameters => Dict{Symbol, Dict}(:flag=>Dict{Symbol, Bool}(:value=>1)) :independent_variable => t - :parameters => Dict{Symbol, Dict{Symbol, Any}}(:a2 => Dict(:type => AbstractArray{Real}, :condition => (:if, :flag, Dict{Symbol, Any}(:kwargs => Dict{Any, Any}(:a1 => Dict{Symbol, Union{Nothing, DataType}}(:value => nothing, :type => Real)), :parameters => Any[Dict{Symbol, Dict{Symbol, Any}}(:a1 => Dict(:type => AbstractArray{Real}))]), Dict{Symbol, Any}(:variables => Any[Dict{Symbol, Dict{Symbol, Any}}()], :kwargs => Dict{Any, Any}(:a2 => Dict{Symbol, Union{Nothing, DataType}}(:value => nothing, :type => Real)), :parameters => Any[Dict{Symbol, Dict{Symbol, Any}}(:a2 => Dict(:type => AbstractArray{Real}))]))), :a1 => Dict(:type => AbstractArray{Real}, :condition => (:if, :flag, Dict{Symbol, Any}(:kwargs => Dict{Any, Any}(:a1 => Dict{Symbol, Union{Nothing, DataType}}(:value => nothing, :type => Real)), :parameters => Any[Dict{Symbol, Dict{Symbol, Any}}(:a1 => Dict(:type => AbstractArray{Real}))]), Dict{Symbol, Any}(:variables => Any[Dict{Symbol, Dict{Symbol, Any}}()], :kwargs => Dict{Any, Any}(:a2 => Dict{Symbol, Union{Nothing, DataType}}(:value => nothing, :type => Real)), :parameters => Any[Dict{Symbol, Dict{Symbol, Any}}(:a2 => Dict(:type => AbstractArray{Real}))])))) + :parameters => Dict{Symbol, Dict{Symbol, Any}}(:a2 => Dict(:type=>AbstractArray{Real}, :condition=>(:if, :flag, Dict{Symbol, Any}(:kwargs=>Dict{Any, Any}(:a1=>Dict{Symbol, Union{Nothing, DataType}}(:value=>nothing, :type=>Real)), :parameters=>Any[Dict{Symbol, Dict{Symbol, Any}}(:a1=>Dict(:type=>AbstractArray{Real}))]), Dict{Symbol, Any}(:variables=>Any[Dict{Symbol, Dict{Symbol, Any}}()], :kwargs=>Dict{Any, Any}(:a2=>Dict{Symbol, Union{Nothing, DataType}}(:value=>nothing, :type=>Real)), :parameters=>Any[Dict{Symbol, Dict{Symbol, Any}}(:a2=>Dict(:type=>AbstractArray{Real}))]))), :a1 => Dict(:type=>AbstractArray{Real}, :condition=>(:if, :flag, Dict{Symbol, Any}(:kwargs=>Dict{Any, Any}(:a1=>Dict{Symbol, Union{Nothing, DataType}}(:value=>nothing, :type=>Real)), :parameters=>Any[Dict{Symbol, Dict{Symbol, Any}}(:a1=>Dict(:type=>AbstractArray{Real}))]), Dict{Symbol, Any}(:variables=>Any[Dict{Symbol, Dict{Symbol, Any}}()], :kwargs=>Dict{Any, Any}(:a2=>Dict{Symbol, Union{Nothing, DataType}}(:value=>nothing, :type=>Real)), :parameters=>Any[Dict{Symbol, Dict{Symbol, Any}}(:a2=>Dict(:type=>AbstractArray{Real}))])))) + :defaults => Dict{Symbol, Any}(:a1=>10) :equations => Any[(:if, :flag, ["a1 ~ 0"], ["a2 ~ 0"])] ``` diff --git a/src/systems/model_parsing.jl b/src/systems/model_parsing.jl index ec8d5aa6f5..d6fac17616 100644 --- a/src/systems/model_parsing.jl +++ b/src/systems/model_parsing.jl @@ -187,7 +187,7 @@ function parse_variable_def!(dict, mod, arg, varclass, kwargs, where_types; var = generate_var!(dict, a, varclass; indices, type) update_kwargs_and_metadata!(dict, kwargs, a, def, indices, type, var, varclass, where_types) - (var, def) + return var, def, Dict() end Expr(:(::), a, type) => begin type = getfield(mod, type) @@ -202,12 +202,12 @@ function parse_variable_def!(dict, mod, arg, varclass, kwargs, where_types; var = generate_var!(dict, a, b, varclass, mod; indices, type) update_kwargs_and_metadata!(dict, kwargs, a, def, indices, type, var, varclass, where_types) - (var, def) + return var, def, Dict() end Expr(:(=), a, b) => begin Base.remove_linenums!(b) def, meta = parse_default(mod, b) - var, def = parse_variable_def!( + var, def, _ = parse_variable_def!( dict, mod, a, varclass, kwargs, where_types; def, type) if dict[varclass] isa Vector dict[varclass][1][getname(var)][:default] = def @@ -225,12 +225,13 @@ function parse_variable_def!(dict, mod, arg, varclass, kwargs, where_types; end end end - var = set_var_metadata(var, meta) + var, metadata_with_exprs = set_var_metadata(var, meta) + return var, def, metadata_with_exprs end - (var, def) + return var, def, Dict() end Expr(:tuple, a, b) => begin - var, def = parse_variable_def!( + var, def, _ = parse_variable_def!( dict, mod, a, varclass, kwargs, where_types; type) meta = parse_metadata(mod, b) if meta !== nothing @@ -244,9 +245,10 @@ function parse_variable_def!(dict, mod, arg, varclass, kwargs, where_types; end end end - var = set_var_metadata(var, meta) + var, metadata_with_exprs = set_var_metadata(var, meta) + return var, def, metadata_with_exprs end - (var, def) + return var, def, Dict() end Expr(:ref, a, b...) => begin indices = map(i -> UnitRange(i.args[2], i.args[end]), b) @@ -350,21 +352,33 @@ function parse_metadata(mod, a) end end +function _set_var_metadata!(metadata_with_exprs, a, m, v::Expr) + push!(metadata_with_exprs, m => v) + a +end +function _set_var_metadata!(metadata_with_exprs, a, m, v) + wrap(set_scalar_metadata(unwrap(a), m, v)) +end + function set_var_metadata(a, ms) + metadata_with_exprs = Dict{DataType, Expr}() for (m, v) in ms - a = wrap(set_scalar_metadata(unwrap(a), m, v)) + if m == VariableGuess && v isa Symbol + v = quote + $v + end + end + a = _set_var_metadata!(metadata_with_exprs, a, m, v) end - a + a, metadata_with_exprs end function get_var(mod::Module, b) if b isa Symbol - getproperty(mod, b) - elseif b isa Expr - Core.eval(mod, b) - else - b + isdefined(mod, b) && return getproperty(mod, b) + isdefined(@__MODULE__, b) && return getproperty(@__MODULE__, b) end + b end function parse_model!(exprs, comps, ext, eqs, icon, vs, ps, sps, c_evts, d_evts, @@ -595,10 +609,26 @@ function parse_variable_arg!(exprs, vs, dict, mod, arg, varclass, kwargs, where_ end function parse_variable_arg(dict, mod, arg, varclass, kwargs, where_types) - vv, def = parse_variable_def!(dict, mod, arg, varclass, kwargs, where_types) + vv, def, metadata_with_exprs = parse_variable_def!( + dict, mod, arg, varclass, kwargs, where_types) name = getname(vv) - return vv isa Num ? name : :($name...), - :($name = $name === nothing ? $setdefault($vv, $def) : $setdefault($vv, $name)) + + varexpr = quote + $name = if $name === nothing + $setdefault($vv, $def) + else + $setdefault($vv, $name) + end + end + + metadata_expr = Expr(:block) + for (k, v) in metadata_with_exprs + push!(metadata_expr.args, + :($name = $wrap($set_scalar_metadata($unwrap($name), $k, $v)))) + end + + push!(varexpr.args, metadata_expr) + return vv isa Num ? name : :($name...), varexpr end function handle_conditional_vars!( diff --git a/test/model_parsing.jl b/test/model_parsing.jl index 578eb60c74..50cbd64270 100644 --- a/test/model_parsing.jl +++ b/test/model_parsing.jl @@ -376,8 +376,8 @@ end @testset "Metadata in variables" begin metadata = Dict(:description => "Variable to test metadata in the Model.structure", - :input => true, :bounds => (-1, 1), :connection_type => :Flow, - :tunable => false, :disturbance => true, :dist => Normal(1, 1)) + :input => true, :bounds => :((-1, 1)), :connection_type => :Flow, + :tunable => false, :disturbance => true, :dist => :(Normal(1, 1))) @connector MockMeta begin m(t), @@ -473,7 +473,7 @@ using ModelingToolkit: getdefault, scalarize @named model_with_component_array = ModelWithComponentArray() - @test ModelWithComponentArray.structure[:parameters][:r][:unit] == u"Ω" + @test eval(ModelWithComponentArray.structure[:parameters][:r][:unit]) == eval(u"Ω") @test lastindex(parameters(model_with_component_array)) == 3 # Test the constant `k`. Manually k's value should be kept in sync here From 35013c846cedb6a0ab3fb214731484d69bb6b8f2 Mon Sep 17 00:00:00 2001 From: Venkateshprasad <32921645+ven-k@users.noreply.github.com> Date: Thu, 25 Apr 2024 16:57:38 +0530 Subject: [PATCH 077/316] test: various forms of guess as expression --- mo.diff | 582 ++++++++++++++++++++++++++++++++++++++++++ test/model_parsing.jl | 30 ++- 2 files changed, 609 insertions(+), 3 deletions(-) create mode 100644 mo.diff diff --git a/mo.diff b/mo.diff new file mode 100644 index 0000000000..abb2a3b037 --- /dev/null +++ b/mo.diff @@ -0,0 +1,582 @@ +27,28d26 +< Base.parentmodule(m::Model) = parentmodule(m.f) +< +40,46c38,40 +< dict = Dict{Symbol, Any}( +< :constants => Dict{Symbol, Dict}(), +< :defaults => Dict{Symbol, Any}(), +< :kwargs => Dict{Symbol, Dict}(), +< :structural_parameters => Dict{Symbol, Dict}() +< ) +< comps = Union{Symbol, Expr}[] +--- +> dict = Dict{Symbol, Any}() +> dict[:kwargs] = Dict{Symbol, Any}() +> comps = Symbol[] +51,52d44 +< c_evts = [] +< d_evts = [] +54d45 +< where_types = Expr[] +60d50 +< push!(exprs.args, :(defaults = Dict{Num, Union{Number, Symbol, Function}}())) +66c56 +< sps, c_evts, d_evts, dict, mod, arg, kwargs, where_types) +--- +> sps, dict, mod, arg, kwargs) +73,74c63 +< mod, ps, vs, where_types, +< parse_top_level_branch(condition, x.args)...) +--- +> mod, ps, vs, parse_top_level_branch(condition, x.args)...) +78,79c67 +< mod, ps, vs, where_types, +< parse_top_level_branch(condition, x.args, y)...) +--- +> mod, ps, vs, parse_top_level_branch(condition, x.args, y)...) +86,87c74 +< parse_variable_arg!( +< exprs.args, vs, dict, mod, arg, :variables, kwargs, where_types) +--- +> parse_variable_arg!(exprs.args, vs, dict, mod, arg, :variables, kwargs) +95c82 +< iv = dict[:independent_variable] = get_t(mod, :t) +--- +> iv = dict[:independent_variable] = variable(:t) +106,108d92 +< @inline pop_structure_dict!.( +< Ref(dict), [:constants, :defaults, :kwargs, :structural_parameters]) +< +110c94 +< name, systems, gui_metadata = $gui_metadata, defaults)) +--- +> name, systems, gui_metadata = $gui_metadata)) +121,139c105 +< !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 +< f_with_where = Expr(:where) +< push!(f_with_where.args, +< :($(Symbol(:__, name, :__))(; name, $(kwargs...))), where_types...) +< :($f_with_where = $exprs) +< end +< +--- +> f = :($(Symbol(:__, name, :__))(; name, $(kwargs...)) = $exprs) +143,169c109,110 +< pop_structure_dict!(dict, key) = length(dict[key]) == 0 && pop!(dict, key) +< +< function update_kwargs_and_metadata!(dict, kwargs, a, def, indices, type, var, +< varclass, where_types) +< if indices isa Nothing +< push!(kwargs, Expr(:kw, Expr(:(::), a, Union{Nothing, type}), nothing)) +< dict[:kwargs][getname(var)] = Dict(:value => def, :type => type) +< else +< vartype = gensym(:T) +< push!(kwargs, +< Expr(:kw, +< Expr(:(::), a, +< Expr(:curly, :Union, :Nothing, Expr(:curly, :AbstractArray, vartype))), +< nothing)) +< push!(where_types, :($vartype <: $type)) +< dict[:kwargs][getname(var)] = Dict(:value => def, :type => AbstractArray{type}) +< end +< if dict[varclass] isa Vector +< dict[varclass][1][getname(var)][:type] = AbstractArray{type} +< else +< dict[varclass][getname(var)][:type] = type +< end +< end +< +< function parse_variable_def!(dict, mod, arg, varclass, kwargs, where_types; +< def = nothing, indices::Union{Vector{UnitRange{Int}}, Nothing} = nothing, +< type::Type = Real) +--- +> function parse_variable_def!(dict, mod, arg, varclass, kwargs; +> def = nothing, indices::Union{Vector{UnitRange{Int}}, Nothing} = nothing) +182c123,125 +< (:dist, VariableDistribution)] +--- +> (:dist, VariableDistribution), +> (:binary, VariableBinary), +> (:integer, VariableInteger)] +187,199c130,133 +< var = generate_var!(dict, a, varclass; indices, type) +< update_kwargs_and_metadata!(dict, kwargs, a, def, indices, type, var, +< varclass, where_types) +< return var, def, Dict() +< end +< Expr(:(::), a, type) => begin +< type = getfield(mod, type) +< parse_variable_def!(dict, mod, a, varclass, kwargs, where_types; def, type) +< end +< Expr(:(::), Expr(:call, a, b), type) => begin +< type = getfield(mod, type) +< def = _type_check!(def, a, type, varclass) +< parse_variable_def!(dict, mod, a, varclass, kwargs, where_types; def, type) +--- +> push!(kwargs, Expr(:kw, a, nothing)) +> var = generate_var!(dict, a, varclass; indices) +> dict[:kwargs][getname(var)] = def +> (var, def, Dict()) +202,205c136,139 +< var = generate_var!(dict, a, b, varclass, mod; indices, type) +< update_kwargs_and_metadata!(dict, kwargs, a, def, indices, type, var, +< varclass, where_types) +< return var, def, Dict() +--- +> push!(kwargs, Expr(:kw, a, nothing)) +> var = generate_var!(dict, a, b, varclass; indices) +> dict[:kwargs][getname(var)] = def +> (var, def, Dict()) +211,217c145,146 +< var, def, _ = parse_variable_def!( +< dict, mod, a, varclass, kwargs, where_types; def, type) +< if dict[varclass] isa Vector +< dict[varclass][1][getname(var)][:default] = def +< else +< dict[varclass][getname(var)][:default] = def +< end +--- +> var, def, _ = parse_variable_def!(dict, mod, a, varclass, kwargs; def) +> dict[varclass][getname(var)][:default] = def +222d150 +< key == VariableConnectType && (mt = nameof(mt)) +236,237c164,165 +< var, def, _ = parse_variable_def!( +< dict, mod, a, varclass, kwargs, where_types; type) +--- +> @info 166 a b +> var, def, _ = parse_variable_def!(dict, mod, a, varclass, kwargs) +257,258c185,186 +< parse_variable_def!(dict, mod, a, varclass, kwargs, where_types; +< def, indices, type) +--- +> parse_variable_def!(dict, mod, a, varclass, kwargs; +> def, indices) +265,268c193,194 +< indices::Union{Vector{UnitRange{Int}}, Nothing} = nothing, +< type = Real) +< var = indices === nothing ? Symbolics.variable(a; T = type) : +< first(@variables $a[indices...]::type) +--- +> indices::Union{Vector{UnitRange{Int}}, Nothing} = nothing) +> var = indices === nothing ? Symbolics.variable(a) : first(@variables $a[indices...]) +276,277c202 +< indices::Union{Vector{UnitRange{Int}}, Nothing} = nothing, +< type = Real) +--- +> indices::Union{Vector{UnitRange{Int}}, Nothing} = nothing) +284c209 +< generate_var(a, varclass; indices, type) +--- +> generate_var(a, varclass; indices) +287,290c212,214 +< function generate_var!(dict, a, b, varclass, mod; +< indices::Union{Vector{UnitRange{Int}}, Nothing} = nothing, +< type = Real) +< iv = b == :t ? get_t(mod, b) : generate_var(b, :variables) +--- +> function generate_var!(dict, a, b, varclass; +> indices::Union{Vector{UnitRange{Int}}, Nothing} = nothing) +> iv = generate_var(b, :variables) +301c225 +< Symbolics.variable(a, T = SymbolicUtils.FnType{Tuple{Any}, type})(iv) +--- +> Symbolics.variable(a, T = SymbolicUtils.FnType{Tuple{Any}, Real})(iv) +304c228 +< first(@variables $a(iv)[indices...]::type) +--- +> first(@variables $a(iv)[indices...]) +312,325d235 +< # Use the `t` defined in the `mod`. When it is unavailable, generate a new `t` with a warning. +< function get_t(mod, t) +< try +< get_var(mod, t) +< catch e +< if e isa UndefVarError +< @warn("Could not find a predefined `t` in `$mod`; generating a new one within this model.\nConsider defining it or importing `t` (or `t_nounits`, `t_unitful` as `t`) from ModelingToolkit.") +< variable(:t) +< else +< throw(e) +< end +< end +< end +< +365a276 +> @info typeof(m) typeof(v) m v +380,381c291,292 +< function parse_model!(exprs, comps, ext, eqs, icon, vs, ps, sps, c_evts, d_evts, +< dict, mod, arg, kwargs, where_types) +--- +> function parse_model!(exprs, comps, ext, eqs, icon, vs, ps, sps, +> dict, mod, arg, kwargs) +389c300 +< parse_variables!(exprs, vs, dict, mod, body, :variables, kwargs, where_types) +--- +> parse_variables!(exprs, vs, dict, mod, body, :variables, kwargs) +391c302 +< parse_variables!(exprs, ps, dict, mod, body, :parameters, kwargs, where_types) +--- +> parse_variables!(exprs, ps, dict, mod, body, :parameters, kwargs) +396,401d306 +< elseif mname == Symbol("@constants") +< parse_constants!(exprs, dict, body, mod) +< elseif mname == Symbol("@continuous_events") +< parse_continuous_events!(c_evts, dict, body) +< elseif mname == Symbol("@discrete_events") +< parse_discrete_events!(d_evts, dict, body) +405,406d309 +< elseif mname == Symbol("@defaults") +< parse_system_defaults!(exprs, arg, dict) +412,476d314 +< 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) +< dict[:defaults][a] = readable_code(b) +< end +< +< function parse_system_defaults!(exprs, defaults_body, dict) +< for default_arg in defaults_body.args[end].args +< # for arg in default_arg.args +< MLStyle.@match default_arg begin +< # For cases like `p => 1` and `p => f()`. In both cases the definitions of +< # `a`, here `p` and when `b` is a function, here `f` are available while +< # defining the model +< Expr(:call, :(=>), a, b) => begin +< push!(exprs, :(defaults[$a] = $b)) +< push_additional_defaults!(dict, a, b) +< end +< _ => error("Invalid `defaults` entry $default_arg $(typeof(a)) $(typeof(b))") +< end +< end +< end +< +481,488d318 +< Expr(:(=), Expr(:(::), a, type), b) => begin +< type = getfield(mod, type) +< b = _type_check!(get_var(mod, b), a, type, :structural_parameters) +< push!(sps, a) +< push!(kwargs, Expr(:kw, Expr(:(::), a, type), b)) +< dict[:structural_parameters][a] = dict[:kwargs][a] = Dict( +< :value => b, :type => type) +< end +492c322 +< dict[:structural_parameters][a] = dict[:kwargs][a] = Dict(:value => b) +--- +> dict[:kwargs][a] = b +497c327 +< dict[:structural_parameters][a] = dict[:kwargs][a] = Dict(:value => nothing) +--- +> dict[:kwargs][a] = nothing +521c351 +< dict[:kwargs][x] = Dict(:value => nothing) +--- +> dict[:kwargs][x] = nothing +525c355 +< dict[:kwargs][x] = Dict(:value => nothing) +--- +> dict[:kwargs][x] = nothing +531c361 +< dict[:kwargs][x] = Dict(:value => nothing) +--- +> dict[:kwargs][x] = nothing +601,602c431,432 +< function parse_variable_arg!(exprs, vs, dict, mod, arg, varclass, kwargs, where_types) +< name, ex = parse_variable_arg(dict, mod, arg, varclass, kwargs, where_types) +--- +> function parse_variable_arg!(exprs, vs, dict, mod, arg, varclass, kwargs) +> name, ex = parse_variable_arg(dict, mod, arg, varclass, kwargs) +607,608c437,438 +< function parse_variable_arg(dict, mod, arg, varclass, kwargs, where_types) +< vv, def, metadata_with_exprs = parse_variable_def!(dict, mod, arg, varclass, kwargs, where_types) +--- +> function parse_variable_arg(dict, mod, arg, varclass, kwargs) +> vv, def, metadata_with_exprs = parse_variable_def!(dict, mod, arg, varclass, kwargs) +628,629c458 +< function handle_conditional_vars!( +< arg, conditional_branch, mod, varclass, kwargs, where_types) +--- +> function handle_conditional_vars!(arg, conditional_branch, mod, varclass, kwargs) +634,635c463 +< name, ex = parse_variable_arg( +< conditional_dict, mod, _arg, varclass, kwargs, where_types) +--- +> name, ex = parse_variable_arg(conditional_dict, mod, _arg, varclass, kwargs) +693c521 +< function parse_variables!(exprs, vs, dict, mod, body, varclass, kwargs, where_types) +--- +> function parse_variables!(exprs, vs, dict, mod, body, varclass, kwargs) +705,706c533 +< kwargs, +< where_types) +--- +> kwargs) +716,717c543 +< kwargs, +< where_types) +--- +> kwargs) +722c548 +< kwargs, where_types) +--- +> kwargs) +731,732c557 +< _ => parse_variable_arg!( +< exprs, vs, dict, mod, arg, varclass, kwargs, where_types) +--- +> _ => parse_variable_arg!(exprs, vs, dict, mod, arg, varclass, kwargs) +737c562 +< function handle_y_vars(y, dict, mod, varclass, kwargs, where_types) +--- +> function handle_y_vars(y, dict, mod, varclass, kwargs) +744,747c569,570 +< kwargs, +< where_types) +< _y_expr, _conditional_dict = handle_y_vars( +< y.args[end], dict, mod, varclass, kwargs, where_types) +--- +> kwargs) +> _y_expr, _conditional_dict = handle_y_vars(y.args[end], dict, mod, varclass, kwargs) +752c575 +< handle_conditional_vars!(y, conditional_y_expr, mod, varclass, kwargs, where_types) +--- +> handle_conditional_vars!(y, conditional_y_expr, mod, varclass, kwargs) +813,830d635 +< function parse_continuous_events!(c_evts, dict, body) +< dict[:continuous_events] = [] +< Base.remove_linenums!(body) +< for arg in body.args +< push!(c_evts, arg) +< push!(dict[:continuous_events], readable_code.(c_evts)...) +< end +< 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) +< push!(dict[:discrete_events], readable_code.(d_evts)...) +< end +< end +< +856c661 +< function component_args!(a, b, varexpr, kwargs; index_name = nothing) +--- +> function component_args!(a, b, expr, varexpr, kwargs) +865,876c670,674 +< varname, _varname = _rename(a, x) +< b.args[i] = Expr(:kw, x, _varname) +< push!(varexpr.args, :((if $varname !== nothing +< $_varname = $varname +< elseif @isdefined $x +< # Allow users to define a var in `structural_parameters` and set +< # that as positional arg of subcomponents; it is useful for cases +< # where it needs to be passed to multiple subcomponents. +< $_varname = $x +< end))) +< push!(kwargs, Expr(:kw, varname, nothing)) +< # dict[:kwargs][varname] = nothing +--- +> _v = _rename(a, x) +> b.args[i] = Expr(:kw, x, _v) +> push!(varexpr.args, :((@isdefined $x) && ($_v = $x))) +> push!(kwargs, Expr(:kw, _v, nothing)) +> # dict[:kwargs][_v] = nothing +879c677 +< component_args!(a, arg, varexpr, kwargs) +--- +> component_args!(a, arg, expr, varexpr, kwargs) +882,891c680,684 +< varname, _varname = _rename(a, x) +< b.args[i] = Expr(:kw, x, _varname) +< if isnothing(index_name) +< push!(varexpr.args, :($_varname = $varname === nothing ? $y : $varname)) +< else +< push!(varexpr.args, +< :($_varname = $varname === nothing ? $y : $varname[$index_name])) +< end +< push!(kwargs, Expr(:kw, varname, nothing)) +< # dict[:kwargs][varname] = nothing +--- +> _v = _rename(a, x) +> b.args[i] = Expr(:kw, x, _v) +> push!(varexpr.args, :($_v = $_v === nothing ? $y : $_v)) +> push!(kwargs, Expr(:kw, _v, nothing)) +> # dict[:kwargs][_v] = nothing +898,901c691,692 +< model_name(name, range) = Symbol.(name, :_, collect(range)) +< +< function _parse_components!(body, kwargs) +< local expr +--- +> function _parse_components!(exprs, body, kwargs) +> expr = Expr(:block) +903c694,695 +< comps = Vector{Union{Union{Expr, Symbol}, Expr}}[] +--- +> # push!(exprs, varexpr) +> comps = Vector{Union{Symbol, Expr}}[] +906,908c698,699 +< Base.remove_linenums!(body) +< arg = body.args[end] +< +--- +> for arg in body.args +> arg isa LineNumberNode && continue +910,927d700 +< Expr(:(=), a, Expr(:comprehension, Expr(:generator, b, Expr(:(=), c, d)))) => begin +< array_varexpr = Expr(:block) +< +< push!(comp_names, :($a...)) +< push!(comps, [a, b.args[1], d]) +< b = deepcopy(b) +< +< component_args!(a, b, array_varexpr, kwargs; index_name = c) +< +< expr = _named_idxs(a, d, :($c -> $b); extra_args = array_varexpr) +< end +< Expr(:(=), a, Expr(:comprehension, Expr(:generator, b, Expr(:filter, e, Expr(:(=), c, d))))) => begin +< error("List comprehensions with conditional statements aren't supported.") +< end +< Expr(:(=), a, Expr(:comprehension, Expr(:generator, b, Expr(:(=), c, d), e...))) => begin +< # Note that `e` is of the form `Tuple{Expr(:(=), c, d)}` +< error("More than one index isn't supported while building component array") +< end +932,943d704 +< Expr(:(=), a, Expr(:for, Expr(:(=), c, d), b)) => begin +< Base.remove_linenums!(b) +< array_varexpr = Expr(:block) +< push!(array_varexpr.args, b.args[1:(end - 1)]...) +< push!(comp_names, :($a...)) +< push!(comps, [a, b.args[end].args[1], d]) +< b = deepcopy(b) +< +< component_args!(a, b.args[end], array_varexpr, kwargs; index_name = c) +< +< expr = _named_idxs(a, d, :($c -> $(b.args[end])); extra_args = array_varexpr) +< end +948c709 +< component_args!(a, b, varexpr, kwargs) +--- +> component_args!(a, b, expr, varexpr, kwargs) +951c712 +< expr = :(@named $arg) +--- +> push!(expr.args, arg) +959c720 +< +--- +> end +966c727,729 +< push!(blk.args, expr_vec) +--- +> push!(blk.args, :(@named begin +> $(expr_vec.args...) +> end)) +973c736 +< comp_names, comps, expr_vec, varexpr = _parse_components!(x, kwargs) +--- +> comp_names, comps, expr_vec, varexpr = _parse_components!(ifexpr, x, kwargs) +989c752 +< comp_names, comps, expr_vec, varexpr = _parse_components!(y, kwargs) +--- +> comp_names, comps, expr_vec, varexpr = _parse_components!(exprs, y, kwargs) +1014,1016c777,779 +< # Either the arg is top level component declaration or an invalid cause - both are handled by `_parse_components` +< _ => begin +< comp_names, comps, expr_vec, varexpr = _parse_components!(:(begin +--- +> Expr(:(=), a, b) => begin +> comp_names, comps, expr_vec, varexpr = _parse_components!(exprs, +> :(begin +1022c785,787 +< push!(exprs, varexpr, expr_vec) +--- +> push!(exprs, varexpr, :(@named begin +> $(expr_vec.args...) +> end)) +1023a789 +> _ => error("Couldn't parse the component body $compbody") +1030d795 +< (compname, Symbol(:_, compname)) +1091c856 +< ps, vs, where_types, component_blk, equations_blk, parameter_blk, variable_blk) +--- +> ps, vs, component_blk, equations_blk, parameter_blk, variable_blk) +1096c861 +< end), :parameters, kwargs, where_types) +--- +> end), :parameters, kwargs) +1102c867 +< end), :variables, kwargs, where_types) +--- +> end), :variables, kwargs) +1114,1126d878 +< end +< +< function _type_check!(val, a, type, class) +< if val isa type +< return val +< else +< try +< return convert(type, val) +< catch e +< throw(TypeError(Symbol("`@mtkmodel`"), +< "`$class`, while assigning to `$a`", type, typeof(val))) +< end +< end diff --git a/test/model_parsing.jl b/test/model_parsing.jl index 50cbd64270..2583083968 100644 --- a/test/model_parsing.jl +++ b/test/model_parsing.jl @@ -1,8 +1,7 @@ using ModelingToolkit, Test using ModelingToolkit: get_connector_type, get_defaults, get_gui_metadata, - get_systems, get_ps, getdefault, getname, scalarize, symtype, - VariableDescription, - RegularConnector + get_systems, get_ps, getdefault, getname, readable_code, + scalarize, symtype, VariableDescription, RegularConnector using URIs: URI using Distributions using DynamicQuantities, OrdinaryDiffEq @@ -769,3 +768,28 @@ end @testset "Parent module of Models" begin @test parentmodule(MyMockModule.Ground) == MyMockModule end + +@testset "Guesses with expression" begin + @mtkmodel GuessModel begin + @variables begin + k(t) + l(t) = 10, [guess = k, unit = u"A"] + i(t), [guess = k, unit = u"A"] + j(t), [guess = k + l / i] + end + end + + @named guess_model = GuessModel() + + j_guess = getguess(guess_model.j) + @test typeof(j_guess) == Num + @test readable_code(j_guess) == "l(t) / i(t) + k(t)" + + i_guess = getguess(guess_model.i) + @test typeof(i_guess) == Num + @test readable_code(i_guess) == "k(t)" + + l_guess = getguess(guess_model.l) + @test typeof(l_guess) == Num + @test readable_code(l_guess) == "k(t)" +end From cf514bae0812e941d2654f26ab1b9b7d312df388 Mon Sep 17 00:00:00 2001 From: Yingbo Ma Date: Wed, 1 May 2024 18:02:54 -0400 Subject: [PATCH 078/316] Fix namespace_guesses --- 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 ee527dc820..a7093f22f0 100644 --- a/src/systems/abstractsystem.jl +++ b/src/systems/abstractsystem.jl @@ -867,7 +867,10 @@ end function namespace_guesses(sys) guess = guesses(sys) - Dict(unknowns(sys, k) => namespace_expr(v, sys) for (k, v) in guess) + Dict((vv = unwrap(v); + kk = unwrap(k); + unknowns(sys, kk) => vv isa Symbolic ? namespace_expr(v, sys) : vv) + for (k, v) in guess) end function namespace_equations(sys::AbstractSystem, ivs = independent_variables(sys)) From ad51d1711274452fab57a5d66d53451b0684086d Mon Sep 17 00:00:00 2001 From: Yingbo Ma Date: Wed, 1 May 2024 18:11:17 -0400 Subject: [PATCH 079/316] We need to unwrap guesses --- src/systems/abstractsystem.jl | 5 +---- src/systems/diffeqs/odesystem.jl | 1 + 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/systems/abstractsystem.jl b/src/systems/abstractsystem.jl index a7093f22f0..ee527dc820 100644 --- a/src/systems/abstractsystem.jl +++ b/src/systems/abstractsystem.jl @@ -867,10 +867,7 @@ end function namespace_guesses(sys) guess = guesses(sys) - Dict((vv = unwrap(v); - kk = unwrap(k); - unknowns(sys, kk) => vv isa Symbolic ? namespace_expr(v, sys) : vv) - for (k, v) in guess) + Dict(unknowns(sys, k) => namespace_expr(v, sys) for (k, v) in guess) end function namespace_equations(sys::AbstractSystem, ivs = independent_variables(sys)) diff --git a/src/systems/diffeqs/odesystem.jl b/src/systems/diffeqs/odesystem.jl index d2a112e9a9..409c9c441e 100644 --- a/src/systems/diffeqs/odesystem.jl +++ b/src/systems/diffeqs/odesystem.jl @@ -247,6 +247,7 @@ function ODESystem(deqs::AbstractVector{<:Equation}, iv, dvs, ps; var_guesses = dvs′[hasaguess] .=> sysguesses[hasaguess] sysguesses = isempty(var_guesses) ? Dict() : todict(var_guesses) guesses = merge(sysguesses, todict(guesses)) + guesses = Dict{Any, Any}(value(k) => value(v) for (k, v) in pairs(guesses)) isempty(observed) || collect_var_to_name!(var_to_name, (eq.lhs for eq in observed)) From 2e23c80c1d8945ffa9ea74bb71ad0e935792cfa1 Mon Sep 17 00:00:00 2001 From: Fredrik Bagge Carlson Date: Thu, 2 May 2024 12:39:32 +0200 Subject: [PATCH 080/316] preserve argument order for structural parameters closes #2688 --- Project.toml | 2 ++ src/ModelingToolkit.jl | 1 + src/systems/model_parsing.jl | 2 +- test/model_parsing.jl | 24 ++++++++++++++++++++++++ 4 files changed, 28 insertions(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 6a7a0bcecf..2cd10c8400 100644 --- a/Project.toml +++ b/Project.toml @@ -32,6 +32,7 @@ Libdl = "8f399da3-3557-5675-b5ff-fb832c97cbdb" LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" MLStyle = "d8e11817-5142-5d16-987a-aa16d5891078" NaNMath = "77ba4419-2d1f-58cd-9bb1-8ffee604a2e3" +OrderedCollections = "bac558e1-5e72-5ebc-8fee-abe8a469f55d" OrdinaryDiffEq = "1dea7af3-3e70-54e6-95c3-0bf5283fa5ed" PrecompileTools = "aea7be01-6a6a-4083-8856-8a6e6704d82a" RecursiveArrayTools = "731186ca-8d62-57ce-b412-fbd966d074cd" @@ -89,6 +90,7 @@ Libdl = "1" LinearAlgebra = "1" MLStyle = "0.4.17" NaNMath = "0.3, 1" +OrderedCollections = "1" OrdinaryDiffEq = "6.73.0" PrecompileTools = "1" RecursiveArrayTools = "2.3, 3" diff --git a/src/ModelingToolkit.jl b/src/ModelingToolkit.jl index 643ac5ae73..c70db8e64a 100644 --- a/src/ModelingToolkit.jl +++ b/src/ModelingToolkit.jl @@ -31,6 +31,7 @@ using PrecompileTools, Reexport import FunctionWrappersWrappers using URIs: URI using SciMLStructures + import OrderedCollections using RecursiveArrayTools diff --git a/src/systems/model_parsing.jl b/src/systems/model_parsing.jl index d6fac17616..d79282ae27 100644 --- a/src/systems/model_parsing.jl +++ b/src/systems/model_parsing.jl @@ -50,7 +50,7 @@ function _model_macro(mod, name, expr, isconnector) ps, sps, vs, = [], [], [] c_evts = [] d_evts = [] - kwargs = Set() + kwargs = OrderedCollections.OrderedSet() where_types = Expr[] push!(exprs.args, :(variables = [])) diff --git a/test/model_parsing.jl b/test/model_parsing.jl index 2583083968..d8665190c8 100644 --- a/test/model_parsing.jl +++ b/test/model_parsing.jl @@ -793,3 +793,27 @@ end @test typeof(l_guess) == Num @test readable_code(l_guess) == "k(t)" end + +@testset "Argument order" begin + @mtkmodel OrderModel begin + @structural_parameters begin + b = 1 # reverse alphabetical order to test that the order is preserved + a = b + end + @parameters begin + c = a + d = b + end + end + @named ordermodel = OrderModel() + ordermodel = complete(ordermodel) + defs = ModelingToolkit.defaults(ordermodel) + @test defs[ordermodel.c] == 1 + @test defs[ordermodel.d] == 1 + + @test_nowarn @named ordermodel = OrderModel(a = 2) + ordermodel = complete(ordermodel) + defs = ModelingToolkit.defaults(ordermodel) + @test defs[ordermodel.c] == 2 + @test defs[ordermodel.d] == 1 +end From 2e58e18274f9bb268446631aed3d2f9ca16bf363 Mon Sep 17 00:00:00 2001 From: Fredrik Bagge Carlson Date: Thu, 2 May 2024 13:56:29 +0200 Subject: [PATCH 081/316] add sampletime operator --- src/ModelingToolkit.jl | 2 +- src/clock.jl | 3 ++- src/discretedomain.jl | 19 ++++++++++++++----- src/systems/systemstructure.jl | 12 ++++++++++++ test/clock.jl | 17 +++++++---------- 5 files changed, 36 insertions(+), 17 deletions(-) diff --git a/src/ModelingToolkit.jl b/src/ModelingToolkit.jl index c70db8e64a..d1b081c3e0 100644 --- a/src/ModelingToolkit.jl +++ b/src/ModelingToolkit.jl @@ -269,7 +269,7 @@ export debug_system #export Continuous, Discrete, sampletime, input_timedomain, output_timedomain #export has_discrete_domain, has_continuous_domain #export is_discrete_domain, is_continuous_domain, is_hybrid_domain -export Sample, Hold, Shift, ShiftIndex +export Sample, Hold, Shift, ShiftIndex, sampletime export Clock #, InferredDiscrete, end # module diff --git a/src/clock.jl b/src/clock.jl index 7ca1707724..9cde8ad8e8 100644 --- a/src/clock.jl +++ b/src/clock.jl @@ -117,7 +117,8 @@ end Clock(dt::Real) = Clock(nothing, dt) Clock() = Clock(nothing, nothing) -sampletime(c) = isdefined(c, :dt) ? c.dt : nothing +sampletime() = InferredSampleTime() +sampletime(c) = something(isdefined(c, :dt) ? c.dt : nothing, InferredSampleTime()) Base.hash(c::Clock, seed::UInt) = hash(c.dt, seed ⊻ 0x953d7a9a18874b90) function Base.:(==)(c1::Clock, c2::Clock) ((c1.t === nothing || c2.t === nothing) || isequal(c1.t, c2.t)) && c1.dt == c2.dt diff --git a/src/discretedomain.jl b/src/discretedomain.jl index 68e8e17b03..74ab0fbb1f 100644 --- a/src/discretedomain.jl +++ b/src/discretedomain.jl @@ -1,5 +1,14 @@ using Symbolics: Operator, Num, Term, value, recursive_hasoperator +struct InferredSampleTime <: Operator end +function SymbolicUtils.promote_symtype(::Type{InferredSampleTime}, t...) + Real +end +function InferredSampleTime() + # Term{Real}(InferredSampleTime, Any[]) + SymbolicUtils.term(InferredSampleTime, type = Real) +end + # Shift """ @@ -15,8 +24,6 @@ $(FIELDS) ```jldoctest julia> using Symbolics -julia> @variables t; - julia> Δ = Shift(t) (::Shift) (generic function with 2 methods) ``` @@ -176,7 +183,6 @@ end function (xn::Num)(k::ShiftIndex) @unpack clock, steps = k x = value(xn) - t = clock.t # Verify that the independent variables of k and x match and that the expression doesn't have multiple variables vars = Symbolics.get_variables(x) length(vars) == 1 || @@ -184,8 +190,11 @@ function (xn::Num)(k::ShiftIndex) args = Symbolics.arguments(vars[]) # args should be one element vector with the t in x(t) length(args) == 1 || error("Cannot shift an expression with multiple independent variables $x.") - isequal(args[], t) || - error("Independent variable of $xn is not the same as that of the ShiftIndex $(k.t)") + t = args[] + if hasfield(typeof(clock), :t) + isequal(t, clock.t) || + error("Independent variable of $xn is not the same as that of the ShiftIndex $(k.t)") + end # d, _ = propagate_time_domain(xn) # if d != clock # this is only required if the variable has another clock diff --git a/src/systems/systemstructure.jl b/src/systems/systemstructure.jl index 54285a431f..bfd5246199 100644 --- a/src/systems/systemstructure.jl +++ b/src/systems/systemstructure.jl @@ -652,6 +652,18 @@ function structural_simplify!(state::TearingState, io = nothing; simplify = fals append!(appended_parameters, inputs[i], unknowns(ss)) discrete_subsystems[i] = ss end + for i in eachindex(discrete_subsystems) + discsys = discrete_subsystems[i] + eqs = collect(discsys.eqs) + for eqi in eachindex(eqs) + clock = id_to_clock[i] + clock isa AbstractDiscrete || continue + Ts = sampletime(clock) + eqs[eqi] = substitute(eqs[eqi], InferredSampleTime() => Ts) + end + @set discsys.eqs = eqs + discrete_subsystems[i] = discsys + end @set! sys.discrete_subsystems = discrete_subsystems, inputs, continuous_id, id_to_clock @set! sys.ps = appended_parameters diff --git a/test/clock.jl b/test/clock.jl index b7964909d0..0bf85617b8 100644 --- a/test/clock.jl +++ b/test/clock.jl @@ -330,7 +330,7 @@ using ModelingToolkitStandardLibrary.Blocks dt = 0.05 d = Clock(t, dt) -k = ShiftIndex(d) +k = ShiftIndex() @mtkmodel DiscretePI begin @components begin @@ -347,7 +347,7 @@ k = ShiftIndex(d) y(t) end @equations begin - x(k) ~ x(k - 1) + ki * u(k) + x(k) ~ x(k - 1) + ki * u(k) * sampletime() / dt output.u(k) ~ y(k) input.u(k) ~ u(k) y(k) ~ x(k - 1) + kp * u(k) @@ -364,21 +364,18 @@ end end end -@mtkmodel Holder begin - @components begin - input = RealInput() - output = RealOutput() - end +@mtkmodel ZeroOrderHold begin + @extend u, y = siso = Blocks.SISO() @equations begin - output.u ~ Hold(input.u) + y ~ Hold(u) end end @mtkmodel ClosedLoop begin @components begin plant = FirstOrder(k = 0.3, T = 1) - sampler = Sampler() - holder = Holder() + sampler = Blocks.Sampler(; clock = d) + holder = ZeroOrderHold() controller = DiscretePI(kp = 2, ki = 2) feedback = Feedback() ref = Constant(k = 0.5) From cadd012f1bb44f8a913e5cef98aadb01905512b6 Mon Sep 17 00:00:00 2001 From: Yingbo Ma Date: Thu, 2 May 2024 09:26:44 -0400 Subject: [PATCH 082/316] Update Project.toml --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 2cd10c8400..8d807d04e9 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.12.1" +version = "9.12.2" [deps] AbstractTrees = "1520ce14-60c1-5f80-bbc7-55ef81b5835c" From b95fb3df6d978b9298550faad1d48396f0c5b4e8 Mon Sep 17 00:00:00 2001 From: Yingbo Ma Date: Thu, 2 May 2024 10:34:04 -0400 Subject: [PATCH 083/316] Infer SampleTime --- src/ModelingToolkit.jl | 2 +- src/clock.jl | 3 +-- src/discretedomain.jl | 11 +++----- src/systems/clock_inference.jl | 47 ++++++++++++++++++++++++++++++++++ src/systems/systemstructure.jl | 14 +--------- test/clock.jl | 6 ++--- 6 files changed, 57 insertions(+), 26 deletions(-) diff --git a/src/ModelingToolkit.jl b/src/ModelingToolkit.jl index d1b081c3e0..29da34148a 100644 --- a/src/ModelingToolkit.jl +++ b/src/ModelingToolkit.jl @@ -269,7 +269,7 @@ export debug_system #export Continuous, Discrete, sampletime, input_timedomain, output_timedomain #export has_discrete_domain, has_continuous_domain #export is_discrete_domain, is_continuous_domain, is_hybrid_domain -export Sample, Hold, Shift, ShiftIndex, sampletime +export Sample, Hold, Shift, ShiftIndex, sampletime, SampleTime export Clock #, InferredDiscrete, end # module diff --git a/src/clock.jl b/src/clock.jl index 9cde8ad8e8..7ca1707724 100644 --- a/src/clock.jl +++ b/src/clock.jl @@ -117,8 +117,7 @@ end Clock(dt::Real) = Clock(nothing, dt) Clock() = Clock(nothing, nothing) -sampletime() = InferredSampleTime() -sampletime(c) = something(isdefined(c, :dt) ? c.dt : nothing, InferredSampleTime()) +sampletime(c) = isdefined(c, :dt) ? c.dt : nothing Base.hash(c::Clock, seed::UInt) = hash(c.dt, seed ⊻ 0x953d7a9a18874b90) function Base.:(==)(c1::Clock, c2::Clock) ((c1.t === nothing || c2.t === nothing) || isequal(c1.t, c2.t)) && c1.dt == c2.dt diff --git a/src/discretedomain.jl b/src/discretedomain.jl index 74ab0fbb1f..988624f233 100644 --- a/src/discretedomain.jl +++ b/src/discretedomain.jl @@ -1,12 +1,9 @@ using Symbolics: Operator, Num, Term, value, recursive_hasoperator -struct InferredSampleTime <: Operator end -function SymbolicUtils.promote_symtype(::Type{InferredSampleTime}, t...) - Real -end -function InferredSampleTime() - # Term{Real}(InferredSampleTime, Any[]) - SymbolicUtils.term(InferredSampleTime, type = Real) +struct SampleTime <: Operator end +SymbolicUtils.promote_symtype(::Type{SampleTime}, t...) = Real +function SampleTime() + SymbolicUtils.term(SampleTime, type = Real) end # Shift diff --git a/src/systems/clock_inference.jl b/src/systems/clock_inference.jl index c4c18d5bdb..88a83fed6b 100644 --- a/src/systems/clock_inference.jl +++ b/src/systems/clock_inference.jl @@ -21,6 +21,52 @@ function ClockInference(ts::TransformationState) ClockInference(ts, eq_domain, var_domain, inferred) end +struct NotInferedTimeDomain end +function error_sample_time(eq) + error("$eq\ncontains `SampleTime` but it is not an infered discrete equation.") +end +function substitute_sample_time(ci::ClockInference) + @unpack ts, eq_domain = ci + eqs = copy(equations(ts)) + @assert length(eqs) == length(eq_domain) + for i in eachindex(eqs) + eq = eqs[i] + domain = eq_domain[i] + dt = sampletime(domain) + neweq = substitute_sample_time(eq, dt) + if neweq isa NotInferedTimeDomain + error_sample_time(eq) + end + eqs[i] = neweq + end + @set! ts.sys.eqs = eqs + @set! ci.ts = ts +end + +function substitute_sample_time(eq::Equation, dt) + substitute_sample_time(eq.lhs, dt) ~ substitute_sample_time(eq.rhs, dt) +end + +function substitute_sample_time(ex, dt) + istree(ex) || return ex + op = operation(ex) + args = arguments(ex) + if op == SampleTime + dt === nothing && return NotInferedTimeDomain() + return dt + else + new_args = similar(args) + for (i, arg) in enumerate(args) + ex_arg = substitute_sample_time(arg, dt) + if ex_arg isa NotInferedTimeDomain + return ex_arg + end + new_args[i] = ex_arg + end + similarterm(ex, op, new_args; metadata = metadata(ex)) + end +end + function infer_clocks!(ci::ClockInference) @unpack ts, eq_domain, var_domain, inferred = ci @unpack var_to_diff, graph = ts.structure @@ -66,6 +112,7 @@ function infer_clocks!(ci::ClockInference) end end + ci = substitute_sample_time(ci) return ci end diff --git a/src/systems/systemstructure.jl b/src/systems/systemstructure.jl index bfd5246199..1c5f3ca28b 100644 --- a/src/systems/systemstructure.jl +++ b/src/systems/systemstructure.jl @@ -627,7 +627,7 @@ function structural_simplify!(state::TearingState, io = nothing; simplify = fals kwargs...) if state.sys isa ODESystem ci = ModelingToolkit.ClockInference(state) - ModelingToolkit.infer_clocks!(ci) + 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) @@ -652,18 +652,6 @@ function structural_simplify!(state::TearingState, io = nothing; simplify = fals append!(appended_parameters, inputs[i], unknowns(ss)) discrete_subsystems[i] = ss end - for i in eachindex(discrete_subsystems) - discsys = discrete_subsystems[i] - eqs = collect(discsys.eqs) - for eqi in eachindex(eqs) - clock = id_to_clock[i] - clock isa AbstractDiscrete || continue - Ts = sampletime(clock) - eqs[eqi] = substitute(eqs[eqi], InferredSampleTime() => Ts) - end - @set discsys.eqs = eqs - discrete_subsystems[i] = discsys - end @set! sys.discrete_subsystems = discrete_subsystems, inputs, continuous_id, id_to_clock @set! sys.ps = appended_parameters diff --git a/test/clock.jl b/test/clock.jl index 0bf85617b8..f847f94188 100644 --- a/test/clock.jl +++ b/test/clock.jl @@ -347,7 +347,7 @@ k = ShiftIndex() y(t) end @equations begin - x(k) ~ x(k - 1) + ki * u(k) * sampletime() / dt + x(k) ~ x(k - 1) + ki * u(k) * SampleTime() / dt output.u(k) ~ y(k) input.u(k) ~ u(k) y(k) ~ x(k - 1) + kp * u(k) @@ -374,7 +374,7 @@ end @mtkmodel ClosedLoop begin @components begin plant = FirstOrder(k = 0.3, T = 1) - sampler = Blocks.Sampler(; clock = d) + sampler = Sampler() holder = ZeroOrderHold() controller = DiscretePI(kp = 2, ki = 2) feedback = Feedback() @@ -441,7 +441,7 @@ prob = ODEProblem(ssys, [model.plant.x => 0.0; model.controller.kp => 2.0; model.controller.ki => 2.0], (0.0, Tf)) int = init(prob, Tsit5(); kwargshandle = KeywordArgSilent) -@test int.ps[Hold(ssys.holder.input.u)] == 2 # constant output * kp issue https://github.com/SciML/ModelingToolkit.jl/issues/2356 +@test_broken int.ps[Hold(ssys.holder.input.u)] == 2 # constant output * kp issue https://github.com/SciML/ModelingToolkit.jl/issues/2356 @test int.ps[ssys.controller.x] == 1 # c2d @test int.ps[Sample(d)(ssys.sampler.input.u)] == 0 # disc state sol = solve(prob, From 5384e586e9b794f2f985ee20ba9e87c2b41f3570 Mon Sep 17 00:00:00 2001 From: Sathvik Bhagavan Date: Thu, 2 May 2024 17:05:54 +0000 Subject: [PATCH 084/316] refactor: remove check_length in the error message of unbalanced system --- 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 ee527dc820..ad1c1581fd 100644 --- a/src/systems/abstractsystem.jl +++ b/src/systems/abstractsystem.jl @@ -2219,13 +2219,13 @@ function check_eqs_u0(eqs, dvs, u0; check_length = true, kwargs...) if u0 !== nothing if check_length if !(length(eqs) == length(dvs) == length(u0)) - throw(ArgumentError("Equations ($(length(eqs))), unknowns ($(length(dvs))), and initial conditions ($(length(u0))) are of different lengths. To allow a different number of equations than unknowns use kwarg check_length=false.")) + throw(ArgumentError("Equations ($(length(eqs))), unknowns ($(length(dvs))), and initial conditions ($(length(u0))) are of different lengths.")) end elseif length(dvs) != length(u0) throw(ArgumentError("Unknowns ($(length(dvs))) and initial conditions ($(length(u0))) are of different lengths.")) end elseif check_length && (length(eqs) != length(dvs)) - throw(ArgumentError("Equations ($(length(eqs))) and Unknowns ($(length(dvs))) are of different lengths. To allow these to differ use kwarg check_length=false.")) + throw(ArgumentError("Equations ($(length(eqs))) and Unknowns ($(length(dvs))) are of different lengths.")) end return nothing end From b4c97d024111827a8a3434dd4da6982dca8019b7 Mon Sep 17 00:00:00 2001 From: Yingbo Ma Date: Fri, 3 May 2024 15:27:05 -0400 Subject: [PATCH 085/316] Fix method redefinition --- src/discretedomain.jl | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/discretedomain.jl b/src/discretedomain.jl index 988624f233..723612b083 100644 --- a/src/discretedomain.jl +++ b/src/discretedomain.jl @@ -1,10 +1,9 @@ using Symbolics: Operator, Num, Term, value, recursive_hasoperator -struct SampleTime <: Operator end -SymbolicUtils.promote_symtype(::Type{SampleTime}, t...) = Real -function SampleTime() - SymbolicUtils.term(SampleTime, type = Real) +struct SampleTime <: Operator + SampleTime() = SymbolicUtils.term(SampleTime, type = Real) end +SymbolicUtils.promote_symtype(::Type{<:SampleTime}, t...) = Real # Shift From 52d7b11ef68d959c03be10b75713afdc10ce019a Mon Sep 17 00:00:00 2001 From: Yingbo Ma Date: Fri, 3 May 2024 15:49:18 -0400 Subject: [PATCH 086/316] Workaround for https://github.com/JuliaSymbolics/Symbolics.jl/issues/1128 --- src/systems/clock_inference.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/systems/clock_inference.jl b/src/systems/clock_inference.jl index 88a83fed6b..d78625e340 100644 --- a/src/systems/clock_inference.jl +++ b/src/systems/clock_inference.jl @@ -63,7 +63,7 @@ function substitute_sample_time(ex, dt) end new_args[i] = ex_arg end - similarterm(ex, op, new_args; metadata = metadata(ex)) + similarterm(ex, op, new_args, symtype(ex); metadata = metadata(ex)) end end From fd73c5767349d4caad5e4df41cba2e8a20251e78 Mon Sep 17 00:00:00 2001 From: Yingbo Ma Date: Thu, 9 May 2024 20:48:23 -0400 Subject: [PATCH 087/316] Generalize `substitute_sample_time` --- src/systems/clock_inference.jl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/systems/clock_inference.jl b/src/systems/clock_inference.jl index d78625e340..66af67d026 100644 --- a/src/systems/clock_inference.jl +++ b/src/systems/clock_inference.jl @@ -25,8 +25,8 @@ struct NotInferedTimeDomain end function error_sample_time(eq) error("$eq\ncontains `SampleTime` but it is not an infered discrete equation.") end -function substitute_sample_time(ci::ClockInference) - @unpack ts, eq_domain = ci +function substitute_sample_time(ci::ClockInference, ts::TearingState) + @unpack eq_domain = ci eqs = copy(equations(ts)) @assert length(eqs) == length(eq_domain) for i in eachindex(eqs) @@ -112,7 +112,7 @@ function infer_clocks!(ci::ClockInference) end end - ci = substitute_sample_time(ci) + ci = substitute_sample_time(ci, ts) return ci end From 4e45712ccae3475a2e9f83e310735d8745cd995a Mon Sep 17 00:00:00 2001 From: Yingbo Ma Date: Thu, 9 May 2024 21:12:46 -0400 Subject: [PATCH 088/316] Update Project.toml --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 8d807d04e9..17a5f17deb 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.12.2" +version = "9.12.3" [deps] AbstractTrees = "1520ce14-60c1-5f80-bbc7-55ef81b5835c" From e16c18091ac71ad45b2e87d4028e015deb70f713 Mon Sep 17 00:00:00 2001 From: Yingbo Ma Date: Thu, 9 May 2024 21:13:09 -0400 Subject: [PATCH 089/316] Update Project.toml --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 17a5f17deb..d0e3ba4854 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.12.3" +version = "9.13.0" [deps] AbstractTrees = "1520ce14-60c1-5f80-bbc7-55ef81b5835c" From f6df5dc8aa3bd9b36c7999ed2b6e20595892a85b Mon Sep 17 00:00:00 2001 From: David Widmann Date: Thu, 16 May 2024 02:23:15 +0200 Subject: [PATCH 090/316] Forward system in `ODEFunction` expression --- Project.toml | 2 +- src/systems/diffeqs/abstractodesystem.jl | 1 + test/odesystem.jl | 3 +++ 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index d0e3ba4854..68f6a2acc7 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.13.0" +version = "9.14.0" [deps] AbstractTrees = "1520ce14-60c1-5f80-bbc7-55ef81b5835c" diff --git a/src/systems/diffeqs/abstractodesystem.jl b/src/systems/diffeqs/abstractodesystem.jl index 59b2e8462d..525643880d 100644 --- a/src/systems/diffeqs/abstractodesystem.jl +++ b/src/systems/diffeqs/abstractodesystem.jl @@ -752,6 +752,7 @@ function ODEFunctionExpr{iip}(sys::AbstractODESystem, dvs = unknowns(sys), $_jac M = $_M ODEFunction{$iip}($fsym, + sys = $sys, jac = $jacsym, tgrad = $tgradsym, mass_matrix = M, diff --git a/test/odesystem.jl b/test/odesystem.jl index f11ab62a06..7f95044cfb 100644 --- a/test/odesystem.jl +++ b/test/odesystem.jl @@ -58,6 +58,9 @@ for f in [ ODEFunction(de, [x, y, z], [σ, ρ, β], tgrad = true, jac = true), eval(ODEFunctionExpr(de, [x, y, z], [σ, ρ, β], tgrad = true, jac = true)) ] + # system + @test f.sys === de + # iip du = zeros(3) u = collect(1:3) From 81c68fa02bada31e067b2603fc58427475aa38e7 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Tue, 30 Apr 2024 20:56:58 +0530 Subject: [PATCH 091/316] fix: create and solve initialization problem in linearization_function --- src/systems/abstractsystem.jl | 28 ++++++++--------- src/systems/nonlinear/initializesystem.jl | 37 ++++++++++++++--------- test/downstream/linearize.jl | 12 +++++--- test/input_output_handling.jl | 4 ++- 4 files changed, 45 insertions(+), 36 deletions(-) diff --git a/src/systems/abstractsystem.jl b/src/systems/abstractsystem.jl index ad1c1581fd..8b59b3c8a4 100644 --- a/src/systems/abstractsystem.jl +++ b/src/systems/abstractsystem.jl @@ -1756,24 +1756,18 @@ function linearization_function(sys::AbstractSystem, inputs, op = merge(defs, op) end sys = ssys - x0 = merge(defaults(sys), Dict(missing_variable_defaults(sys)), op) - u0, _p, _ = get_u0_p(sys, x0, p; use_union = false, tofloat = true) + initsys = complete(generate_initializesystem( + sys, guesses = guesses(sys), algebraic_only = true)) + initfn = NonlinearFunction(initsys) + initprobmap = getu(initsys, unknowns(sys)) ps = parameters(sys) - if has_index_cache(sys) && get_index_cache(sys) !== nothing - p = MTKParameters(sys, p, u0) - else - p = _p - p, split_idxs = split_parameters_by_type(p) - if p isa Tuple - ps = Base.Fix1(getindex, ps).(split_idxs) - ps = (ps...,) #if p is Tuple, ps should be Tuple - end - end lin_fun = let diff_idxs = diff_idxs, alge_idxs = alge_idxs, input_idxs = input_idxs, sts = unknowns(sys), - fun = ODEFunction{true, SciMLBase.FullSpecialize}(sys, unknowns(sys), ps; p = p), + fun = ODEFunction{true, SciMLBase.FullSpecialize}( + sys, unknowns(sys), ps; initializeprobmap = initprobmap), + initfn = initfn, h = build_explicit_observed_function(sys, outputs), chunk = ForwardDiff.Chunk(input_idxs) @@ -1784,6 +1778,8 @@ function linearization_function(sys::AbstractSystem, inputs, if initialize && !isempty(alge_idxs) # This is expensive and can be omitted if the user knows that the system is already initialized residual = fun(u, p, t) if norm(residual[alge_idxs]) > √(eps(eltype(residual))) + initprob = NonlinearLeastSquaresProblem(initfn, u, p) + @set! fun.initializeprob = initprob prob = ODEProblem(fun, u, (t, t + 1), p) integ = init(prob, OrdinaryDiffEq.Rodas5P()) u = integ.u @@ -2051,8 +2047,8 @@ lsys_sym, _ = ModelingToolkit.linearize_symbolic(cl, [f.u], [p.x]) """ function linearize(sys, lin_fun; t = 0.0, op = Dict(), allow_input_derivatives = false, p = DiffEqBase.NullParameters()) - x0 = merge(defaults(sys), op) - u0, p2, _ = get_u0_p(sys, x0, p; use_union = false, tofloat = true) + x0 = merge(defaults(sys), Dict(missing_variable_defaults(sys)), op) + u0, defs = get_u0(sys, x0, p) if has_index_cache(sys) && get_index_cache(sys) !== nothing if p isa SciMLBase.NullParameters p = op @@ -2063,7 +2059,7 @@ function linearize(sys, lin_fun; t = 0.0, op = Dict(), allow_input_derivatives = elseif p isa Vector p = merge(Dict(parameters(sys) .=> p), op) end - p2 = MTKParameters(sys, p, Dict(unknowns(sys) .=> u0)) + p2 = MTKParameters(sys, p, merge(Dict(unknowns(sys) .=> u0), x0, guesses(sys))) end linres = lin_fun(u0, p2, t) f_x, f_z, g_x, g_z, f_u, g_u, h_x, h_z, h_u = linres diff --git a/src/systems/nonlinear/initializesystem.jl b/src/systems/nonlinear/initializesystem.jl index 86921a1af0..2421f20bf2 100644 --- a/src/systems/nonlinear/initializesystem.jl +++ b/src/systems/nonlinear/initializesystem.jl @@ -8,6 +8,7 @@ function generate_initializesystem(sys::ODESystem; name = nameof(sys), guesses = Dict(), check_defguess = false, default_dd_value = 0.0, + algebraic_only = false, kwargs...) sts, eqs = unknowns(sys), equations(sys) idxs_diff = isdiffeq.(eqs) @@ -68,28 +69,34 @@ function generate_initializesystem(sys::ODESystem; defs = merge(defaults(sys), filtered_u0) guesses = merge(get_guesses(sys), todict(guesses), dd_guess) - for st in full_states - if st ∈ keys(defs) - def = defs[st] + if !algebraic_only + for st in full_states + if st ∈ keys(defs) + def = defs[st] - if def isa Equation - st ∉ keys(guesses) && check_defguess && - error("Invalid setup: unknown $(st) has an initial condition equation with no guess.") - push!(eqs_ics, def) + if def isa Equation + st ∉ keys(guesses) && check_defguess && + error("Invalid setup: unknown $(st) has an initial condition equation with no guess.") + push!(eqs_ics, def) + push!(u0, st => guesses[st]) + else + push!(eqs_ics, st ~ def) + push!(u0, st => def) + end + elseif st ∈ keys(guesses) push!(u0, st => guesses[st]) - else - push!(eqs_ics, st ~ def) - push!(u0, st => def) + elseif check_defguess + error("Invalid setup: unknown $(st) has no default value or initial guess") end - elseif st ∈ keys(guesses) - push!(u0, st => guesses[st]) - elseif check_defguess - error("Invalid setup: unknown $(st) has no default value or initial guess") end end pars = [parameters(sys); get_iv(sys)] - nleqs = [eqs_ics; get_initialization_eqs(sys); observed(sys)] + nleqs = if algebraic_only + [eqs_ics; observed(sys)] + else + [eqs_ics; get_initialization_eqs(sys); observed(sys)] + end sys_nl = NonlinearSystem(nleqs, full_states, diff --git a/test/downstream/linearize.jl b/test/downstream/linearize.jl index c1aa6f6724..d366710809 100644 --- a/test/downstream/linearize.jl +++ b/test/downstream/linearize.jl @@ -120,10 +120,14 @@ lsys = ModelingToolkit.reorder_unknowns(lsys0, unknowns(ssys), desired_order) lsyss, _ = ModelingToolkit.linearize_symbolic(pid, [reference.u, measurement.u], [ctr_output.u]) -@test substitute(lsyss.A, ModelingToolkit.defaults(pid)) == lsys.A -@test substitute(lsyss.B, ModelingToolkit.defaults(pid)) == lsys.B -@test substitute(lsyss.C, ModelingToolkit.defaults(pid)) == lsys.C -@test substitute(lsyss.D, ModelingToolkit.defaults(pid)) == lsys.D +@test substitute( + lsyss.A, merge(ModelingToolkit.defaults(pid), ModelingToolkit.guesses(pid))) == lsys.A +@test substitute( + lsyss.B, merge(ModelingToolkit.defaults(pid), ModelingToolkit.guesses(pid))) == lsys.B +@test substitute( + lsyss.C, merge(ModelingToolkit.defaults(pid), ModelingToolkit.guesses(pid))) == lsys.C +@test substitute( + lsyss.D, merge(ModelingToolkit.defaults(pid), ModelingToolkit.guesses(pid))) == lsys.D # Test with the reverse desired unknown order as well to verify that similarity transform and reoreder_unknowns really works lsys = ModelingToolkit.reorder_unknowns(lsys, unknowns(ssys), reverse(desired_order)) diff --git a/test/input_output_handling.jl b/test/input_output_handling.jl index 41b27aad4a..ec06f69b1d 100644 --- a/test/input_output_handling.jl +++ b/test/input_output_handling.jl @@ -144,7 +144,9 @@ if VERSION >= v"1.8" # :opaque_closure not supported before drop_expr = identity) x = randn(size(A, 1)) u = randn(size(B, 2)) - p = getindex.(Ref(ModelingToolkit.defaults(ssys)), parameters(ssys)) + p = getindex.( + Ref(merge(ModelingToolkit.defaults(ssys), ModelingToolkit.guesses(ssys))), + parameters(ssys)) y1 = obsf(x, u, p, 0) y2 = C * x + D * u @test y1[] ≈ y2[] From 9a98ffaf91e2a705eed11013194c85733f391add Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Tue, 30 Apr 2024 20:55:24 +0530 Subject: [PATCH 092/316] fix: fix namespacing of array variables using `unknowns` --- src/systems/abstractsystem.jl | 4 ++++ test/odesystem.jl | 6 ++++++ 2 files changed, 10 insertions(+) diff --git a/src/systems/abstractsystem.jl b/src/systems/abstractsystem.jl index 8b59b3c8a4..65725d92ce 100644 --- a/src/systems/abstractsystem.jl +++ b/src/systems/abstractsystem.jl @@ -1021,6 +1021,10 @@ function defaults(sys::AbstractSystem) end unknowns(sys::Union{AbstractSystem, Nothing}, v) = renamespace(sys, v) +for vType in [Symbolics.Arr, Symbolics.Symbolic{<:AbstractArray}] + @eval unknowns(sys::AbstractSystem, v::$vType) = renamespace(sys, v) + @eval parameters(sys::AbstractSystem, v::$vType) = toparam(unknowns(sys, v)) +end parameters(sys::Union{AbstractSystem, Nothing}, v) = toparam(unknowns(sys, v)) for f in [:unknowns, :parameters] @eval function $f(sys::AbstractSystem, vs::AbstractArray) diff --git a/test/odesystem.jl b/test/odesystem.jl index 7f95044cfb..8793712a59 100644 --- a/test/odesystem.jl +++ b/test/odesystem.jl @@ -1162,3 +1162,9 @@ for sys in [sys1, sys2] @test variable_index(sys, x[i]) == variable_index(sys, x)[i] 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) From 97d6b272ea17cd4809207f095d1e2c835db63d4d Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Tue, 30 Apr 2024 20:53:10 +0530 Subject: [PATCH 093/316] fix: respect `u0map` in `InitializationProblem` --- src/systems/diffeqs/abstractodesystem.jl | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/systems/diffeqs/abstractodesystem.jl b/src/systems/diffeqs/abstractodesystem.jl index 525643880d..b976945f79 100644 --- a/src/systems/diffeqs/abstractodesystem.jl +++ b/src/systems/diffeqs/abstractodesystem.jl @@ -1632,10 +1632,16 @@ function InitializationProblem{iip, specialize}(sys::AbstractODESystem, parammap = parammap isa DiffEqBase.NullParameters || isempty(parammap) ? [get_iv(sys) => t] : merge(todict(parammap), Dict(get_iv(sys) => t)) - + if isempty(u0map) + u0map = Dict() + end + if isempty(guesses) + guesses = Dict() + end + u0map = merge(todict(guesses), todict(u0map)) if neqs == nunknown - NonlinearProblem(isys, guesses, parammap) + NonlinearProblem(isys, u0map, parammap) else - NonlinearLeastSquaresProblem(isys, guesses, parammap) + NonlinearLeastSquaresProblem(isys, u0map, parammap) end end From 4a20e322fa99eb9bf433aa02812ec538db533d3e Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Tue, 30 Apr 2024 20:53:51 +0530 Subject: [PATCH 094/316] fix: runtime dispatch in `replace!` --- src/systems/parameter_buffer.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/systems/parameter_buffer.jl b/src/systems/parameter_buffer.jl index 203957fd1c..a40baa47ac 100644 --- a/src/systems/parameter_buffer.jl +++ b/src/systems/parameter_buffer.jl @@ -190,7 +190,7 @@ function _update_tuple_helper(buf_v::T, raw, idx) where {T} end function _update_tuple_helper(::Type{<:AbstractArray}, buf_v, raw, idx) - ntuple(i -> _update_tuple_helper(buf_v[i], raw, idx), Val(length(buf_v))) + ntuple(i -> _update_tuple_helper(buf_v[i], raw, idx), length(buf_v)) end function _update_tuple_helper(::Any, buf_v, raw, idx) From c162e005e5c0a25abda2c558b9bf07eb435ff89a Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Fri, 3 May 2024 22:09:36 +0530 Subject: [PATCH 095/316] test: mark mtkparameters tests as no longer broken --- test/mtkparameters.jl | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/test/mtkparameters.jl b/test/mtkparameters.jl index a6308fc9e8..86ae4dd78a 100644 --- a/test/mtkparameters.jl +++ b/test/mtkparameters.jl @@ -23,9 +23,7 @@ ps = MTKParameters(sys, ivs) ivs[a] = 1.0 ps = MTKParameters(sys, ivs) -@test_broken getp(sys, g) # SII bug for (p, val) in ivs - isequal(p, g) && continue # broken if isequal(p, c) val = 3ivs[a] end @@ -67,9 +65,8 @@ setp(sys, e)(ps, 5ones(3)) # with an array setp(sys, f[2, 2])(ps, 42) # with a sub-index @test getp(sys, f[2, 2])(ps) == 42 -# SII bug -@test_broken setp(sys, g)(ps, ones(100)) # with non-fixed-length array -@test_broken getp(sys, g)(ps) == ones(100) +setp(sys, g)(ps, ones(100)) # with non-fixed-length array +@test getp(sys, g)(ps) == ones(100) setp(sys, h)(ps, "bar") # with a non-numeric @test getp(sys, h)(ps) == "bar" @@ -91,8 +88,7 @@ end @test getp(sys, c)(newps) isa Float64 @test getp(sys, d)(newps) isa UInt8 @test getp(sys, f)(newps) isa Matrix{UInt} -# SII bug -@test_broken getp(sys, g)(newps) isa Vector{Float32} +@test getp(sys, g)(newps) isa Vector{Float32} ps = MTKParameters(sys, ivs) function loss(value, sys, ps) From 2c17819bcb10f678c7bf783d6b17fc8cd2dddf5e Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Mon, 6 May 2024 14:48:49 +0530 Subject: [PATCH 096/316] test: fix serialization test observed function expr --- test/serialization.jl | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/test/serialization.jl b/test/serialization.jl index 94577b433a..5e09055a92 100644 --- a/test/serialization.jl +++ b/test/serialization.jl @@ -55,7 +55,10 @@ for var in all_obs push!(obs_exps, ex) end # observedfun expression for ODEFunctionExpr -observedfun_exp = :(function (var, u0, p, t) +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) From c48891cf9d2a9fdd79416c5976c978fad4156806 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Tue, 7 May 2024 13:43:17 +0530 Subject: [PATCH 097/316] feat: add utility function for obtaining defaults and guesses --- src/systems/abstractsystem.jl | 4 ++++ test/downstream/linearize.jl | 8 ++++---- test/input_output_handling.jl | 2 +- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/systems/abstractsystem.jl b/src/systems/abstractsystem.jl index 65725d92ce..e14458176c 100644 --- a/src/systems/abstractsystem.jl +++ b/src/systems/abstractsystem.jl @@ -1020,6 +1020,10 @@ function defaults(sys::AbstractSystem) isempty(systems) ? defs : mapfoldr(namespace_defaults, merge, systems; init = defs) end +function defaults_and_guesses(sys::AbstractSystem) + merge(guesses(sys), defaults(sys)) +end + unknowns(sys::Union{AbstractSystem, Nothing}, v) = renamespace(sys, v) for vType in [Symbolics.Arr, Symbolics.Symbolic{<:AbstractArray}] @eval unknowns(sys::AbstractSystem, v::$vType) = renamespace(sys, v) diff --git a/test/downstream/linearize.jl b/test/downstream/linearize.jl index d366710809..a43e7d6ffe 100644 --- a/test/downstream/linearize.jl +++ b/test/downstream/linearize.jl @@ -121,13 +121,13 @@ lsyss, _ = ModelingToolkit.linearize_symbolic(pid, [reference.u, measurement.u], [ctr_output.u]) @test substitute( - lsyss.A, merge(ModelingToolkit.defaults(pid), ModelingToolkit.guesses(pid))) == lsys.A + lsyss.A, ModelingToolkit.defaults_and_guesses(pid)) == lsys.A @test substitute( - lsyss.B, merge(ModelingToolkit.defaults(pid), ModelingToolkit.guesses(pid))) == lsys.B + lsyss.B, ModelingToolkit.defaults_and_guesses(pid)) == lsys.B @test substitute( - lsyss.C, merge(ModelingToolkit.defaults(pid), ModelingToolkit.guesses(pid))) == lsys.C + lsyss.C, ModelingToolkit.defaults_and_guesses(pid)) == lsys.C @test substitute( - lsyss.D, merge(ModelingToolkit.defaults(pid), ModelingToolkit.guesses(pid))) == lsys.D + lsyss.D, ModelingToolkit.defaults_and_guesses(pid)) == lsys.D # Test with the reverse desired unknown order as well to verify that similarity transform and reoreder_unknowns really works lsys = ModelingToolkit.reorder_unknowns(lsys, unknowns(ssys), reverse(desired_order)) diff --git a/test/input_output_handling.jl b/test/input_output_handling.jl index ec06f69b1d..c63116638b 100644 --- a/test/input_output_handling.jl +++ b/test/input_output_handling.jl @@ -145,7 +145,7 @@ if VERSION >= v"1.8" # :opaque_closure not supported before x = randn(size(A, 1)) u = randn(size(B, 2)) p = getindex.( - Ref(merge(ModelingToolkit.defaults(ssys), ModelingToolkit.guesses(ssys))), + Ref(ModelingToolkit.defaults_and_guesses(ssys)), parameters(ssys)) y1 = obsf(x, u, p, 0) y2 = C * x + D * u From 9f531c4b66242200990019d13dd195ecb6c3da1d Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Wed, 8 May 2024 16:46:33 +0530 Subject: [PATCH 098/316] feat: add `SII.observed` support for `AbstractSystem` --- src/systems/abstractsystem.jl | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/systems/abstractsystem.jl b/src/systems/abstractsystem.jl index e14458176c..7208923fd6 100644 --- a/src/systems/abstractsystem.jl +++ b/src/systems/abstractsystem.jl @@ -494,6 +494,14 @@ function SymbolicIndexingInterface.is_observed(sys::AbstractSystem, sym) !is_independent_variable(sys, sym) && symbolic_type(sym) != NotSymbolic() end +function SymbolicIndexingInterface.observed(sys::AbstractSystem, sym) + return let _fn = build_explicit_observed_function(sys, sym) + fn(u, p, t) = _fn(u, p, t) + fn(u, p::MTKParameters, t) = _fn(u, p..., t) + fn + end +end + function SymbolicIndexingInterface.default_values(sys::AbstractSystem) return merge( Dict(eq.lhs => eq.rhs for eq in observed(sys)), From da7c9fc9a52d64345018557579160c6a649c5be6 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Wed, 8 May 2024 16:46:50 +0530 Subject: [PATCH 099/316] refactor: make dependent and numeric portions singletons --- src/systems/index_cache.jl | 6 ++++-- src/systems/parameter_buffer.jl | 3 ++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/systems/index_cache.jl b/src/systems/index_cache.jl index fb1b3f75c1..e7d4b3d140 100644 --- a/src/systems/index_cache.jl +++ b/src/systems/index_cache.jl @@ -8,8 +8,10 @@ function BufferTemplate(s::Type{<:Symbolics.Struct}, length::Int) BufferTemplate(T, length) end -const DEPENDENT_PORTION = :dependent -const NONNUMERIC_PORTION = :nonnumeric +struct Dependent <: SciMLStructures.AbstractPortion end +struct Nonnumeric <: SciMLStructures.AbstractPortion end +const DEPENDENT_PORTION = Dependent() +const NONNUMERIC_PORTION = Nonnumeric() struct ParameterIndex{P, I} portion::P diff --git a/src/systems/parameter_buffer.jl b/src/systems/parameter_buffer.jl index a40baa47ac..7ccd38c990 100644 --- a/src/systems/parameter_buffer.jl +++ b/src/systems/parameter_buffer.jl @@ -210,7 +210,8 @@ SciMLStructures.ismutablescimlstructure(::MTKParameters) = true for (Portion, field) in [(SciMLStructures.Tunable, :tunable) (SciMLStructures.Discrete, :discrete) - (SciMLStructures.Constants, :constant)] + (SciMLStructures.Constants, :constant) + (Nonnumeric, :nonnumeric)] @eval function SciMLStructures.canonicalize(::$Portion, p::MTKParameters) as_vector = buffer_to_arraypartition(p.$field) repack = let as_vector = as_vector, p = p From 663306579a61beee0fa09232202eb51739cb9769 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Wed, 8 May 2024 16:48:04 +0530 Subject: [PATCH 100/316] fix: properly initialize initialization problem in linearization_function --- src/systems/abstractsystem.jl | 74 ++++++++++++++++++++++++++++- test/downstream/linearization_dd.jl | 62 ++++++++++++++++++++++++ test/downstream/linearize.jl | 64 ------------------------- test/runtests.jl | 1 + 4 files changed, 135 insertions(+), 66 deletions(-) create mode 100644 test/downstream/linearization_dd.jl diff --git a/src/systems/abstractsystem.jl b/src/systems/abstractsystem.jl index 7208923fd6..f0d1da8588 100644 --- a/src/systems/abstractsystem.jl +++ b/src/systems/abstractsystem.jl @@ -1774,6 +1774,73 @@ function linearization_function(sys::AbstractSystem, inputs, sys = ssys initsys = complete(generate_initializesystem( sys, guesses = guesses(sys), algebraic_only = true)) + if p isa SciMLBase.NullParameters + p = Dict() + else + p = todict(p) + end + p[get_iv(sys)] = 0.0 + if has_index_cache(initsys) && get_index_cache(initsys) !== nothing + oldps = MTKParameters(initsys, p, merge(guesses(sys), defaults(sys), op)) + initsys_ps = parameters(initsys) + initsys_idxs = [parameter_index(initsys, param) for param in initsys_ps] + tunable_ps = [initsys_ps[i] + for i in eachindex(initsys_ps) + if initsys_idxs[i].portion == SciMLStructures.Tunable()] + tunable_getter = isempty(tunable_ps) ? nothing : getu(sys, tunable_ps) + discrete_ps = [initsys_ps[i] + for i in eachindex(initsys_ps) + if initsys_idxs[i].portion == SciMLStructures.Discrete()] + disc_getter = isempty(discrete_ps) ? nothing : getu(sys, discrete_ps) + constant_ps = [initsys_ps[i] + for i in eachindex(initsys_ps) + if initsys_idxs[i].portion == SciMLStructures.Constants()] + const_getter = isempty(constant_ps) ? nothing : getu(sys, constant_ps) + nonnum_ps = [initsys_ps[i] + for i in eachindex(initsys_ps) + if initsys_idxs[i].portion == NONNUMERIC_PORTION] + nonnum_getter = isempty(nonnum_ps) ? nothing : getu(sys, nonnum_ps) + u_getter = isempty(unknowns(initsys)) ? (_...) -> nothing : + getu(sys, unknowns(initsys)) + get_initprob_u_p = let tunable_getter = tunable_getter, + disc_getter = disc_getter, + const_getter = const_getter, + nonnum_getter = nonnum_getter, + oldps = oldps, + u_getter = u_getter + + function (u, p, t) + state = ProblemState(; u, p, t) + if tunable_getter !== nothing + oldps = SciMLStructures.replace!( + SciMLStructures.Tunable(), oldps, tunable_getter(state)) + end + if disc_getter !== nothing + oldps = SciMLStructures.replace!( + SciMLStructures.Discrete(), oldps, disc_getter(state)) + end + if const_getter !== nothing + oldps = SciMLStructures.replace!( + SciMLStructures.Constants(), oldps, const_getter(state)) + end + if nonnum_getter !== nothing + oldps = SciMLStructures.replace!( + NONNUMERIC_PORTION, oldps, nonnum_getter(state)) + end + newu = u_getter(state) + return newu, oldps + end + end + else + get_initprob_u_p = let p_getter = getu(sys, parameters(initsys)), + u_getter = getu(sys, unknowns(initsys)) + + function (u, p, t) + state = ProblemState(; u, p, t) + return u_getter(state), p_getter(state) + end + end + end initfn = NonlinearFunction(initsys) initprobmap = getu(initsys, unknowns(sys)) ps = parameters(sys) @@ -1781,11 +1848,13 @@ function linearization_function(sys::AbstractSystem, inputs, alge_idxs = alge_idxs, input_idxs = input_idxs, sts = unknowns(sys), + get_initprob_u_p = get_initprob_u_p, fun = ODEFunction{true, SciMLBase.FullSpecialize}( sys, unknowns(sys), ps; initializeprobmap = initprobmap), initfn = initfn, h = build_explicit_observed_function(sys, outputs), - chunk = ForwardDiff.Chunk(input_idxs) + chunk = ForwardDiff.Chunk(input_idxs), + initialize = initialize function (u, p, t) if u !== nothing # Handle systems without unknowns @@ -1794,7 +1863,8 @@ function linearization_function(sys::AbstractSystem, inputs, if initialize && !isempty(alge_idxs) # This is expensive and can be omitted if the user knows that the system is already initialized residual = fun(u, p, t) if norm(residual[alge_idxs]) > √(eps(eltype(residual))) - initprob = NonlinearLeastSquaresProblem(initfn, u, p) + initu0, initp = get_initprob_u_p(u, p, t) + initprob = NonlinearLeastSquaresProblem(initfn, initu0, initp) @set! fun.initializeprob = initprob prob = ODEProblem(fun, u, (t, t + 1), p) integ = init(prob, OrdinaryDiffEq.Rodas5P()) diff --git a/test/downstream/linearization_dd.jl b/test/downstream/linearization_dd.jl new file mode 100644 index 0000000000..01dd6f87a0 --- /dev/null +++ b/test/downstream/linearization_dd.jl @@ -0,0 +1,62 @@ +## Test that dummy_derivatives can be set to zero +# The call to Link(; m = 0.2, l = 10, I = 1, g = -9.807) hangs forever on Julia v1.6 +using LinearAlgebra +using ModelingToolkit +using ModelingToolkitStandardLibrary +using ModelingToolkitStandardLibrary.Blocks +using ModelingToolkitStandardLibrary.Mechanical.MultiBody2D +using ModelingToolkitStandardLibrary.Mechanical.TranslationalPosition +using Test + +using ControlSystemsMTK +using ControlSystemsMTK.ControlSystemsBase: sminreal, minreal, poles +connect = ModelingToolkit.connect + +@parameters t +D = Differential(t) + +@named link1 = Link(; m = 0.2, l = 10, I = 1, g = -9.807) +@named cart = TranslationalPosition.Mass(; m = 1, s = 0) +@named fixed = Fixed() +@named force = Force(use_support = false) + +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]) +def = ModelingToolkit.defaults(model) +def[cart.s] = 10 +def[cart.v] = 0 +def[link1.A] = -pi / 2 +def[link1.dA] = 0 +lin_outputs = [cart.s, cart.v, link1.A, link1.dA] +lin_inputs = [force.f.u] + +@info "named_ss" +G = named_ss(model, lin_inputs, lin_outputs, allow_symbolic = true, op = def, + allow_input_derivatives = true, zero_dummy_der = true) +G = sminreal(G) +@info "minreal" +G = minreal(G) +@info "poles" +ps = poles(G) + +@test minimum(abs, ps) < 1e-6 +@test minimum(abs, complex(0, 1.3777260367206716) .- ps) < 1e-10 + +lsys, syss = linearize(model, lin_inputs, lin_outputs, allow_symbolic = true, op = def, + allow_input_derivatives = true, zero_dummy_der = true) +lsyss, sysss = ModelingToolkit.linearize_symbolic(model, lin_inputs, lin_outputs; + allow_input_derivatives = true) + +dummyder = setdiff(unknowns(sysss), unknowns(model)) +def = merge(ModelingToolkit.guesses(model), def, Dict(x => 0.0 for x in dummyder)) +def[link1.fy1] = -def[link1.g] * def[link1.m] + +@test substitute(lsyss.A, def) ≈ lsys.A +# We cannot pivot symbolically, so the part where a linear solve is required +# is not reliable. +@test substitute(lsyss.B, def)[1:6, 1] ≈ lsys.B[1:6, 1] +@test substitute(lsyss.C, def) ≈ lsys.C +@test substitute(lsyss.D, def) ≈ lsys.D diff --git a/test/downstream/linearize.jl b/test/downstream/linearize.jl index a43e7d6ffe..38df060332 100644 --- a/test/downstream/linearize.jl +++ b/test/downstream/linearize.jl @@ -195,67 +195,3 @@ lsys, ssys = linearize(sat, [u], [y]; op = Dict(u => 2)) @test isempty(lsys.B) @test isempty(lsys.C) @test lsys.D[] == 0 - -## Test that dummy_derivatives can be set to zero -if VERSION >= v"1.8" - # The call to Link(; m = 0.2, l = 10, I = 1, g = -9.807) hangs forever on Julia v1.6 - using LinearAlgebra - using ModelingToolkit - using ModelingToolkitStandardLibrary - using ModelingToolkitStandardLibrary.Blocks - using ModelingToolkitStandardLibrary.Mechanical.MultiBody2D - using ModelingToolkitStandardLibrary.Mechanical.TranslationalPosition - - using ControlSystemsMTK - using ControlSystemsMTK.ControlSystemsBase: sminreal, minreal, poles - connect = ModelingToolkit.connect - - @parameters t - D = Differential(t) - - @named link1 = Link(; m = 0.2, l = 10, I = 1, g = -9.807) - @named cart = TranslationalPosition.Mass(; m = 1, s = 0) - @named fixed = Fixed() - @named force = Force(use_support = false) - - 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]) - def = ModelingToolkit.defaults(model) - def[cart.s] = 10 - def[cart.v] = 0 - def[link1.A] = -pi / 2 - def[link1.dA] = 0 - lin_outputs = [cart.s, cart.v, link1.A, link1.dA] - lin_inputs = [force.f.u] - - @info "named_ss" - G = named_ss(model, lin_inputs, lin_outputs, allow_symbolic = true, op = def, - allow_input_derivatives = true, zero_dummy_der = true) - G = sminreal(G) - @info "minreal" - G = minreal(G) - @info "poles" - ps = poles(G) - - @test minimum(abs, ps) < 1e-6 - @test minimum(abs, complex(0, 1.3777260367206716) .- ps) < 1e-10 - - lsys, syss = linearize(model, lin_inputs, lin_outputs, allow_symbolic = true, op = def, - allow_input_derivatives = true, zero_dummy_der = true) - lsyss, sysss = ModelingToolkit.linearize_symbolic(model, lin_inputs, lin_outputs; - allow_input_derivatives = true) - - dummyder = setdiff(unknowns(sysss), unknowns(model)) - def = merge(def, Dict(x => 0.0 for x in dummyder)) - def[link1.fy1] = -def[link1.g] * def[link1.m] - - @test substitute(lsyss.A, def) ≈ lsys.A - # We cannot pivot symbolically, so the part where a linear solve is required - # is not reliable. - @test substitute(lsyss.B, def)[1:6, 1] ≈ lsys.B[1:6, 1] - @test substitute(lsyss.C, def) ≈ lsys.C - @test substitute(lsyss.D, def) ≈ lsys.D -end diff --git a/test/runtests.jl b/test/runtests.jl index 0be5b22ceb..cec0a4bc0b 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -92,6 +92,7 @@ end if GROUP == "All" || GROUP == "Downstream" activate_downstream_env() @safetestset "Linearization Tests" include("downstream/linearize.jl") + @safetestset "Linearization Dummy Derivative Tests" include("downstream/linearization_dd.jl") @safetestset "Inverse Models Test" include("downstream/inversemodel.jl") end From 0ec683a7d8c60e6a3f94519515391f70a9438ffa Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Wed, 8 May 2024 18:48:48 +0530 Subject: [PATCH 101/316] fix: improve performance of linearization --- src/systems/abstractsystem.jl | 36 +++++++++++++++++++++++++---------- 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/src/systems/abstractsystem.jl b/src/systems/abstractsystem.jl index f0d1da8588..739451509b 100644 --- a/src/systems/abstractsystem.jl +++ b/src/systems/abstractsystem.jl @@ -1779,7 +1779,13 @@ function linearization_function(sys::AbstractSystem, inputs, else p = todict(p) end - p[get_iv(sys)] = 0.0 + x0 = merge(defaults_and_guesses(sys), op) + if has_index_cache(sys) && get_index_cache(sys) !== nothing + sys_ps = MTKParameters(sys, p, x0) + else + sys_ps = varmap_to_vars(p, parameters(sys); defaults = x0) + end + p[get_iv(sys)] = NaN if has_index_cache(initsys) && get_index_cache(initsys) !== nothing oldps = MTKParameters(initsys, p, merge(guesses(sys), defaults(sys), op)) initsys_ps = parameters(initsys) @@ -1812,19 +1818,19 @@ function linearization_function(sys::AbstractSystem, inputs, function (u, p, t) state = ProblemState(; u, p, t) if tunable_getter !== nothing - oldps = SciMLStructures.replace!( + SciMLStructures.replace!( SciMLStructures.Tunable(), oldps, tunable_getter(state)) end if disc_getter !== nothing - oldps = SciMLStructures.replace!( + SciMLStructures.replace!( SciMLStructures.Discrete(), oldps, disc_getter(state)) end if const_getter !== nothing - oldps = SciMLStructures.replace!( + SciMLStructures.replace!( SciMLStructures.Constants(), oldps, const_getter(state)) end if nonnum_getter !== nothing - oldps = SciMLStructures.replace!( + SciMLStructures.replace!( NONNUMERIC_PORTION, oldps, nonnum_getter(state)) end newu = u_getter(state) @@ -1843,7 +1849,7 @@ function linearization_function(sys::AbstractSystem, inputs, end initfn = NonlinearFunction(initsys) initprobmap = getu(initsys, unknowns(sys)) - ps = parameters(sys) + ps = full_parameters(sys) lin_fun = let diff_idxs = diff_idxs, alge_idxs = alge_idxs, input_idxs = input_idxs, @@ -1854,9 +1860,20 @@ function linearization_function(sys::AbstractSystem, inputs, initfn = initfn, h = build_explicit_observed_function(sys, outputs), chunk = ForwardDiff.Chunk(input_idxs), - initialize = initialize + sys_ps = sys_ps, + initialize = initialize, + sys = sys function (u, p, t) + if !isa(p, MTKParameters) + p = todict(p) + newps = deepcopy(sys_ps) + for (k, v) in p + setp(sys, k)(newps, v) + end + p = newps + end + if u !== nothing # Handle systems without unknowns length(sts) == length(u) || error("Number of unknown variables ($(length(sts))) does not match the number of input unknowns ($(length(u)))") @@ -2137,7 +2154,7 @@ function linearize(sys, lin_fun; t = 0.0, op = Dict(), allow_input_derivatives = u0, defs = get_u0(sys, x0, p) if has_index_cache(sys) && get_index_cache(sys) !== nothing if p isa SciMLBase.NullParameters - p = op + p = Dict() elseif p isa Dict p = merge(p, op) elseif p isa Vector && eltype(p) <: Pair @@ -2145,9 +2162,8 @@ function linearize(sys, lin_fun; t = 0.0, op = Dict(), allow_input_derivatives = elseif p isa Vector p = merge(Dict(parameters(sys) .=> p), op) end - p2 = MTKParameters(sys, p, merge(Dict(unknowns(sys) .=> u0), x0, guesses(sys))) end - linres = lin_fun(u0, p2, t) + linres = lin_fun(u0, p, t) f_x, f_z, g_x, g_z, f_u, g_u, h_x, h_z, h_u = linres nx, nu = size(f_u) From 556067cd181d3c585a37dbf2402028f80978c79f Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Mon, 13 May 2024 20:44:28 +0530 Subject: [PATCH 102/316] test: mark linearization dummy derivatives test as broken --- test/downstream/linearization_dd.jl | 56 +++++++++++++++-------------- 1 file changed, 29 insertions(+), 27 deletions(-) diff --git a/test/downstream/linearization_dd.jl b/test/downstream/linearization_dd.jl index 01dd6f87a0..fd29e28bbc 100644 --- a/test/downstream/linearization_dd.jl +++ b/test/downstream/linearization_dd.jl @@ -33,30 +33,32 @@ def[link1.dA] = 0 lin_outputs = [cart.s, cart.v, link1.A, link1.dA] lin_inputs = [force.f.u] -@info "named_ss" -G = named_ss(model, lin_inputs, lin_outputs, allow_symbolic = true, op = def, - allow_input_derivatives = true, zero_dummy_der = true) -G = sminreal(G) -@info "minreal" -G = minreal(G) -@info "poles" -ps = poles(G) - -@test minimum(abs, ps) < 1e-6 -@test minimum(abs, complex(0, 1.3777260367206716) .- ps) < 1e-10 - -lsys, syss = linearize(model, lin_inputs, lin_outputs, allow_symbolic = true, op = def, - allow_input_derivatives = true, zero_dummy_der = true) -lsyss, sysss = ModelingToolkit.linearize_symbolic(model, lin_inputs, lin_outputs; - allow_input_derivatives = true) - -dummyder = setdiff(unknowns(sysss), unknowns(model)) -def = merge(ModelingToolkit.guesses(model), def, Dict(x => 0.0 for x in dummyder)) -def[link1.fy1] = -def[link1.g] * def[link1.m] - -@test substitute(lsyss.A, def) ≈ lsys.A -# We cannot pivot symbolically, so the part where a linear solve is required -# is not reliable. -@test substitute(lsyss.B, def)[1:6, 1] ≈ lsys.B[1:6, 1] -@test substitute(lsyss.C, def) ≈ lsys.C -@test substitute(lsyss.D, def) ≈ lsys.D +@test_broken begin + @info "named_ss" + G = named_ss(model, lin_inputs, lin_outputs, allow_symbolic = true, op = def, + allow_input_derivatives = true, zero_dummy_der = true) + G = sminreal(G) + @info "minreal" + G = minreal(G) + @info "poles" + ps = poles(G) + + @test minimum(abs, ps) < 1e-6 + @test minimum(abs, complex(0, 1.3777260367206716) .- ps) < 1e-10 + + lsys, syss = linearize(model, lin_inputs, lin_outputs, allow_symbolic = true, op = def, + allow_input_derivatives = true, zero_dummy_der = true) + lsyss, sysss = ModelingToolkit.linearize_symbolic(model, lin_inputs, lin_outputs; + allow_input_derivatives = true) + + dummyder = setdiff(unknowns(sysss), unknowns(model)) + def = merge(ModelingToolkit.guesses(model), def, Dict(x => 0.0 for x in dummyder)) + def[link1.fy1] = -def[link1.g] * def[link1.m] + + @test substitute(lsyss.A, def) ≈ lsys.A + # We cannot pivot symbolically, so the part where a linear solve is required + # is not reliable. + @test substitute(lsyss.B, def)[1:6, 1] ≈ lsys.B[1:6, 1] + @test substitute(lsyss.C, def) ≈ lsys.C + @test substitute(lsyss.D, def) ≈ lsys.D +end From d6befb78acf939cc90f8ae86176dc36458e8e371 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Tue, 14 May 2024 11:35:30 +0530 Subject: [PATCH 103/316] build: upper bound SymbolicUtils to <1.6 --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 68f6a2acc7..f7e472e779 100644 --- a/Project.toml +++ b/Project.toml @@ -105,7 +105,7 @@ SparseArrays = "1" SpecialFunctions = "0.7, 0.8, 0.9, 0.10, 1.0, 2" StaticArrays = "0.10, 0.11, 0.12, 1.0" SymbolicIndexingInterface = "0.3.12" -SymbolicUtils = "1.0" +SymbolicUtils = "<1.6" Symbolics = "5.26" URIs = "1" UnPack = "0.1, 1.0" From d7fa540cbe233a6689a72a7fec14483362fdf1f9 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Wed, 22 May 2024 15:15:00 +0530 Subject: [PATCH 104/316] test: mark some inversemodel tests as broken --- test/downstream/inversemodel.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/downstream/inversemodel.jl b/test/downstream/inversemodel.jl index 66cf46eb96..c1eb85ca31 100644 --- a/test/downstream/inversemodel.jl +++ b/test/downstream/inversemodel.jl @@ -150,7 +150,7 @@ x, _ = ModelingToolkit.get_u0_p(simplified_sys, op) p = ModelingToolkit.MTKParameters(simplified_sys, op) matrices1 = Sf(x, p, 0) matrices2, _ = Blocks.get_sensitivity(model, :y; op) # Test that we get the same result when calling the higher-level API -@test matrices1.f_x ≈ matrices2.A[1:7, 1:7] +@test_broken matrices1.f_x ≈ matrices2.A[1:7, 1:7] nsys = get_named_sensitivity(model, :y; op) # Test that we get the same result when calling an even higher-level API @test matrices2.A ≈ nsys.A @@ -161,6 +161,6 @@ x, _ = ModelingToolkit.get_u0_p(simplified_sys, op) p = ModelingToolkit.MTKParameters(simplified_sys, op) matrices1 = Sf(x, p, 0) matrices2, _ = Blocks.get_comp_sensitivity(model, :y; op) # Test that we get the same result when calling the higher-level API -@test matrices1.f_x ≈ matrices2.A[1:7, 1:7] +@test_broken matrices1.f_x ≈ matrices2.A[1:7, 1:7] nsys = get_named_comp_sensitivity(model, :y; op) # Test that we get the same result when calling an even higher-level API @test matrices2.A ≈ nsys.A From a793c21b0bcceca76a5c9aca210a516eedb0ecaf Mon Sep 17 00:00:00 2001 From: Oscar Smith Date: Thu, 23 May 2024 20:09:57 -0400 Subject: [PATCH 105/316] Drastically reduce the number of imports that are inside `@recompile_invalidations` This should significantly improve precompile time and might mitigate https://discourse.julialang.org/t/modelingtoolkit-takes-forever-to-precompile-on-windows-11/114492/66. --- src/ModelingToolkit.jl | 74 ++++++++++++++++++++---------------------- 1 file changed, 36 insertions(+), 38 deletions(-) diff --git a/src/ModelingToolkit.jl b/src/ModelingToolkit.jl index 29da34148a..85dc65e14e 100644 --- a/src/ModelingToolkit.jl +++ b/src/ModelingToolkit.jl @@ -4,38 +4,9 @@ $(DocStringExtensions.README) module ModelingToolkit using PrecompileTools, Reexport @recompile_invalidations begin - using DocStringExtensions - using Compat - using AbstractTrees - using DiffEqBase, SciMLBase, ForwardDiff - using SciMLBase: StandardODEProblem, StandardNonlinearProblem, handle_varmap - using Distributed - using StaticArrays, LinearAlgebra, SparseArrays, LabelledArrays - using InteractiveUtils - using Latexify, Unitful, ArrayInterface - using Setfield, ConstructionBase - using JumpProcesses - using DataStructures - using SpecialFunctions, NaNMath + using StaticArrays using RuntimeGeneratedFunctions using RuntimeGeneratedFunctions: drop_expr - using Base.Threads - using DiffEqCallbacks - using Graphs - import ExprTools: splitdef, combinedef - import Libdl - using DocStringExtensions - using Base: RefValue - using Combinatorics - import Distributions - import FunctionWrappersWrappers - using URIs: URI - using SciMLStructures - import OrderedCollections - - using RecursiveArrayTools - - using SymbolicIndexingInterface export independent_variables, unknowns, parameters, full_parameters, continuous_events, discrete_events import SymbolicUtils @@ -46,11 +17,6 @@ using PrecompileTools, Reexport using SymbolicUtils.Code import SymbolicUtils.Code: toexpr import SymbolicUtils.Rewriters: Chain, Postwalk, Prewalk, Fixpoint - import JuliaFormatter - - using MLStyle - - using Reexport using Symbolics using Symbolics: degree using Symbolics: _parse_vars, value, @derivatives, get_variables, @@ -69,11 +35,43 @@ using PrecompileTools, Reexport substituter, scalarize, getparent, hasderiv, hasdiff import DiffEqBase: @add_kwonly - import OrdinaryDiffEq - - import Graphs: SimpleDiGraph, add_edge!, incidence_matrix end +using DocStringExtensions +using SpecialFunctions, NaNMath +using DiffEqCallbacks +using Graphs +import ExprTools: splitdef, combinedef +import OrderedCollections + +using SymbolicIndexingInterface +using LinearAlgebra, SparseArrays, LabelledArrays +using InteractiveUtils +using JumpProcesses +using DataStructures +using Base.Threads +using Latexify, Unitful, ArrayInterface +using Setfield, ConstructionBase +import Libdl +using DocStringExtensions +using Base: RefValue +using Combinatorics +import Distributions +import FunctionWrappersWrappers +using URIs: URI +using SciMLStructures +using Compat +using AbstractTrees +using DiffEqBase, SciMLBase, ForwardDiff +using SciMLBase: StandardODEProblem, StandardNonlinearProblem, handle_varmap +using Distributed +import JuliaFormatter +using MLStyle +import OrdinaryDiffEq +using Reexport +using RecursiveArrayTools +import Graphs: SimpleDiGraph, add_edge!, incidence_matrix + @reexport using Symbolics @reexport using UnPack RuntimeGeneratedFunctions.init(@__MODULE__) From 492c143b6452ff27deefbb9796e9cd4ba3a66e91 Mon Sep 17 00:00:00 2001 From: Oscar Smith Date: Thu, 23 May 2024 20:23:38 -0400 Subject: [PATCH 106/316] Update ModelingToolkit.jl --- src/ModelingToolkit.jl | 59 +++++++++++++++++++++--------------------- 1 file changed, 30 insertions(+), 29 deletions(-) diff --git a/src/ModelingToolkit.jl b/src/ModelingToolkit.jl index 85dc65e14e..7d85f05336 100644 --- a/src/ModelingToolkit.jl +++ b/src/ModelingToolkit.jl @@ -5,38 +5,17 @@ module ModelingToolkit using PrecompileTools, Reexport @recompile_invalidations begin using StaticArrays - using RuntimeGeneratedFunctions - using RuntimeGeneratedFunctions: drop_expr - export independent_variables, unknowns, parameters, full_parameters, continuous_events, - discrete_events - import SymbolicUtils - import SymbolicUtils: istree, arguments, operation, similarterm, promote_symtype, - Symbolic, isadd, ismul, ispow, issym, FnType, - @rule, Rewriters, substitute, metadata, BasicSymbolic, - Sym, Term - using SymbolicUtils.Code - import SymbolicUtils.Code: toexpr - import SymbolicUtils.Rewriters: Chain, Postwalk, Prewalk, Fixpoint using Symbolics - using Symbolics: degree - using Symbolics: _parse_vars, value, @derivatives, get_variables, - exprs_occur_in, solve_for, build_expr, unwrap, wrap, - VariableSource, getname, variable, Connection, connect, - NAMESPACE_SEPARATOR, set_scalar_metadata, setdefaultval, - initial_state, transition, activeState, entry, - ticksInState, timeInState, fixpoint_sub, fast_substitute - import Symbolics: rename, get_variables!, _solve, hessian_sparsity, - jacobian_sparsity, isaffine, islinear, _iszero, _isone, - tosymbol, lower_varname, diff2term, var_from_nested_derivative, - BuildTargets, JuliaTarget, StanTarget, CTarget, MATLABTarget, - ParallelForm, SerialForm, MultithreadedForm, build_function, - rhss, lhss, prettify_expr, gradient, - jacobian, hessian, derivative, sparsejacobian, sparsehessian, - substituter, scalarize, getparent, hasderiv, hasdiff - - import DiffEqBase: @add_kwonly end +import SymbolicUtils +import SymbolicUtils: istree, arguments, operation, similarterm, promote_symtype, + Symbolic, isadd, ismul, ispow, issym, FnType, + @rule, Rewriters, substitute, metadata, BasicSymbolic, + Sym, Term +using SymbolicUtils.Code +import SymbolicUtils.Code: toexpr +import SymbolicUtils.Rewriters: Chain, Postwalk, Prewalk, Fixpoint using DocStringExtensions using SpecialFunctions, NaNMath using DiffEqCallbacks @@ -72,6 +51,28 @@ using Reexport using RecursiveArrayTools import Graphs: SimpleDiGraph, add_edge!, incidence_matrix +using RuntimeGeneratedFunctions +using RuntimeGeneratedFunctions: drop_expr + +using Symbolics: degree +using Symbolics: _parse_vars, value, @derivatives, get_variables, + exprs_occur_in, solve_for, build_expr, unwrap, wrap, + VariableSource, getname, variable, Connection, connect, + NAMESPACE_SEPARATOR, set_scalar_metadata, setdefaultval, + initial_state, transition, activeState, entry, + ticksInState, timeInState, fixpoint_sub, fast_substitute +import Symbolics: rename, get_variables!, _solve, hessian_sparsity, + jacobian_sparsity, isaffine, islinear, _iszero, _isone, + tosymbol, lower_varname, diff2term, var_from_nested_derivative, + BuildTargets, JuliaTarget, StanTarget, CTarget, MATLABTarget, + ParallelForm, SerialForm, MultithreadedForm, build_function, + rhss, lhss, prettify_expr, gradient, + jacobian, hessian, derivative, sparsejacobian, sparsehessian, + substituter, scalarize, getparent, hasderiv, hasdiff + +import DiffEqBase: @add_kwonly +export independent_variables, unknowns, parameters, full_parameters, continuous_events, + discrete_events @reexport using Symbolics @reexport using UnPack RuntimeGeneratedFunctions.init(@__MODULE__) From 174d806c523feba50299ff44938550c969cba93f Mon Sep 17 00:00:00 2001 From: Christopher Rackauckas Date: Thu, 23 May 2024 19:36:38 -0500 Subject: [PATCH 107/316] Update Project.toml --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 68f6a2acc7..b4e9a76852 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.14.0" +version = "9.15.0" [deps] AbstractTrees = "1520ce14-60c1-5f80-bbc7-55ef81b5835c" From 7ee923e221fb4d46aa85dc3214a616a2102b6983 Mon Sep 17 00:00:00 2001 From: CompatHelper Julia Date: Sat, 25 May 2024 00:18:27 +0000 Subject: [PATCH 108/316] CompatHelper: add new compat entry for DeepDiffs in [weakdeps] at version 1, (keep existing compat) --- Project.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/Project.toml b/Project.toml index b4e9a76852..78cd76e54f 100644 --- a/Project.toml +++ b/Project.toml @@ -68,6 +68,7 @@ Combinatorics = "1" Compat = "3.42, 4" ConstructionBase = "1" DataStructures = "0.17, 0.18" +DeepDiffs = "1" DiffEqBase = "6.103.0" DiffEqCallbacks = "2.16, 3" DiffRules = "0.1, 1.0" From 816fe679ae692368db5e53140fa966b585d818c9 Mon Sep 17 00:00:00 2001 From: CompatHelper Julia Date: Sat, 25 May 2024 00:18:31 +0000 Subject: [PATCH 109/316] CompatHelper: add new compat entry for BifurcationKit in [weakdeps] at version 0.3, (keep existing compat) --- Project.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/Project.toml b/Project.toml index b4e9a76852..68e1f42dad 100644 --- a/Project.toml +++ b/Project.toml @@ -64,6 +64,7 @@ MTKDeepDiffsExt = "DeepDiffs" [compat] AbstractTrees = "0.3, 0.4" ArrayInterface = "6, 7" +BifurcationKit = "0.3" Combinatorics = "1" Compat = "3.42, 4" ConstructionBase = "1" From ada31d0b5c0ee9abae78b10d5f3124bbd6b762cf Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Thu, 2 May 2024 10:30:26 +0530 Subject: [PATCH 110/316] feat: relax type restrictions in `MTKParameters` construction --- src/systems/index_cache.jl | 10 ++-------- src/systems/parameter_buffer.jl | 35 ++++++++++++++++++++++++++++----- test/odesystem.jl | 16 ++++++++++++++- 3 files changed, 47 insertions(+), 14 deletions(-) diff --git a/src/systems/index_cache.jl b/src/systems/index_cache.jl index e7d4b3d140..07bef4b450 100644 --- a/src/systems/index_cache.jl +++ b/src/systems/index_cache.jl @@ -79,7 +79,7 @@ function IndexCache(sys::AbstractSystem) function insert_by_type!(buffers::Dict{Any, Set{BasicSymbolic}}, sym) sym = unwrap(sym) - ctype = concrete_symtype(sym) + ctype = symtype(sym) buf = get!(buffers, ctype, Set{BasicSymbolic}()) push!(buf, sym) end @@ -116,7 +116,7 @@ function IndexCache(sys::AbstractSystem) for p in parameters(sys) p = unwrap(p) - ctype = concrete_symtype(p) + ctype = symtype(p) haskey(disc_buffers, ctype) && p in disc_buffers[ctype] && continue haskey(dependent_buffers, ctype) && p in dependent_buffers[ctype] && continue insert_by_type!( @@ -312,9 +312,3 @@ function reorder_parameters(ic::IndexCache, ps; drop_missing = false) end return result end - -concrete_symtype(x::BasicSymbolic) = concrete_symtype(symtype(x)) -concrete_symtype(::Type{Real}) = Float64 -concrete_symtype(::Type{Integer}) = Int -concrete_symtype(::Type{A}) where {T, N, A <: Array{T, N}} = Array{concrete_symtype(T), N} -concrete_symtype(::Type{T}) where {T} = T diff --git a/src/systems/parameter_buffer.jl b/src/systems/parameter_buffer.jl index 7ccd38c990..51b772acdb 100644 --- a/src/systems/parameter_buffer.jl +++ b/src/systems/parameter_buffer.jl @@ -1,5 +1,8 @@ symconvert(::Type{Symbolics.Struct{T}}, x) where {T} = convert(T, x) symconvert(::Type{T}, x) where {T} = convert(T, x) +symconvert(::Type{Real}, x::Integer) = convert(Float64, x) +symconvert(::Type{V}, x) where {V <: AbstractArray} = convert(V, symconvert.(eltype(V), x)) + struct MTKParameters{T, D, C, E, N, F, G} tunable::T discrete::D @@ -107,7 +110,7 @@ function MTKParameters( for (sym, val) in p sym = unwrap(sym) val = unwrap(val) - ctype = concrete_symtype(sym) + ctype = symtype(sym) if symbolic_type(val) !== NotSymbolic() continue end @@ -126,19 +129,27 @@ function MTKParameters( end end end + tunable_buffer = narrow_buffer_type.(tunable_buffer) + disc_buffer = narrow_buffer_type.(disc_buffer) + const_buffer = narrow_buffer_type.(const_buffer) + nonnumeric_buffer = narrow_buffer_type.(nonnumeric_buffer) if has_parameter_dependencies(sys) && (pdeps = get_parameter_dependencies(sys)) !== nothing pdeps = Dict(k => fixpoint_sub(v, pdeps) for (k, v) in pdeps) - dep_exprs = ArrayPartition((wrap.(v) for v in dep_buffer)...) + dep_exprs = ArrayPartition((Any[missing for _ in 1:length(v)] for v in dep_buffer)...) for (sym, val) in pdeps i, j = ic.dependent_idx[sym] dep_exprs.x[i][j] = wrap(val) end + dep_exprs = identity.(dep_exprs) p = reorder_parameters(ic, full_parameters(sys)) oop, iip = build_function(dep_exprs, p...) update_function_iip, update_function_oop = RuntimeGeneratedFunctions.@RuntimeGeneratedFunction(iip), RuntimeGeneratedFunctions.@RuntimeGeneratedFunction(oop) + update_function_iip(ArrayPartition(dep_buffer), tunable_buffer..., disc_buffer..., + const_buffer..., nonnumeric_buffer..., dep_buffer...) + dep_buffer = narrow_buffer_type.(dep_buffer) else update_function_iip = update_function_oop = nothing end @@ -148,12 +159,26 @@ function MTKParameters( typeof(dep_buffer), typeof(nonnumeric_buffer), typeof(update_function_iip), typeof(update_function_oop)}(tunable_buffer, disc_buffer, const_buffer, dep_buffer, nonnumeric_buffer, update_function_iip, update_function_oop) - if mtkps.dependent_update_iip !== nothing - mtkps.dependent_update_iip(ArrayPartition(mtkps.dependent), mtkps...) - end return mtkps end +function narrow_buffer_type(buffer::AbstractArray) + type = Union{} + for x in buffer + type = promote_type(type, typeof(x)) + end + return convert.(type, buffer) +end + +function narrow_buffer_type(buffer::AbstractArray{<:AbstractArray}) + buffer = narrow_buffer_type.(buffer) + type = Union{} + for x in buffer + type = promote_type(type, eltype(x)) + end + return broadcast.(convert, type, buffer) +end + function buffer_to_arraypartition(buf) return ArrayPartition(ntuple(i -> _buffer_to_arrp_helper(buf[i]), Val(length(buf)))) end diff --git a/test/odesystem.jl b/test/odesystem.jl index 8793712a59..dade28de81 100644 --- a/test/odesystem.jl +++ b/test/odesystem.jl @@ -6,7 +6,7 @@ using DiffEqBase, SparseArrays using StaticArrays using Test using SymbolicUtils: issym - +using ForwardDiff using ModelingToolkit: value using ModelingToolkit: t_nounits as t, D_nounits as D @@ -1168,3 +1168,17 @@ end @named sys = ODESystem(Equation[], t) @test getname(unknowns(sys, x)) == :sys₊x @test size(unknowns(sys, x)) == size(x) + +# Issue#2667 +@testset "ForwardDiff through ODEProblem constructor" begin + @parameters P + @variables x(t) + sys = structural_simplify(ODESystem([D(x) ~ P], t, [x], [P]; name = :sys)) + + function x_at_1(P) + prob = ODEProblem(sys, [x => 0.0], (0.0, 1.0), [sys.P => P]) + return solve(prob, Tsit5())(1.0) + end + + @test_nowarn ForwardDiff.derivative(P -> x_at_1(P), 1.0) +end From f1076b30cc364de6c046f7f28ee7dcb6714abbf5 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Fri, 3 May 2024 13:34:31 +0530 Subject: [PATCH 111/316] fix: improve error message for missing parameter values --- src/systems/parameter_buffer.jl | 16 +++++++++++++++- test/mtkparameters.jl | 2 +- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/systems/parameter_buffer.jl b/src/systems/parameter_buffer.jl index 51b772acdb..88d55dd8ef 100644 --- a/src/systems/parameter_buffer.jl +++ b/src/systems/parameter_buffer.jl @@ -70,7 +70,7 @@ function MTKParameters( end end - isempty(missing_params) || throw(MissingVariablesError(collect(missing_params))) + isempty(missing_params) || throw(MissingParametersError(collect(missing_params))) tunable_buffer = Tuple(Vector{temp.type}(undef, temp.length) for temp in ic.tunable_buffer_sizes) @@ -575,3 +575,17 @@ function as_duals(p::MTKParameters, dualtype) discrete = dualtype.(p.discrete) return MTKParameters{typeof(tunable), typeof(discrete)}(tunable, discrete) end + +const MISSING_PARAMETERS_MESSAGE = """ + Some parameters are missing from the variable map. + Please provide a value or default for the following variables: + """ + +struct MissingParametersError <: Exception + vars::Any +end + +function Base.showerror(io::IO, e::MissingParametersError) + println(io, MISSING_PARAMETERS_MESSAGE) + println(io, e.vars) +end diff --git a/test/mtkparameters.jl b/test/mtkparameters.jl index 86ae4dd78a..b6eef43c5c 100644 --- a/test/mtkparameters.jl +++ b/test/mtkparameters.jl @@ -130,7 +130,7 @@ u0 = [X => 1.0] tspan = (0.0, 100.0) ps = [p => 1.0] # Value for `d` is missing -@test_throws ModelingToolkit.MissingVariablesError ODEProblem(sys, u0, tspan, ps) +@test_throws ModelingToolkit.MissingParametersError ODEProblem(sys, u0, tspan, ps) @test_nowarn ODEProblem(sys, u0, tspan, [ps..., d => 1.0]) # JET tests From 97bb913fa30f534b1461fdf2201b594e70a94d8b Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Wed, 22 May 2024 14:02:22 +0530 Subject: [PATCH 112/316] fix: fix bug in `remake_buffer` --- src/systems/parameter_buffer.jl | 15 +++++++-------- test/mtkparameters.jl | 17 +++++++++++++++++ 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/src/systems/parameter_buffer.jl b/src/systems/parameter_buffer.jl index 88d55dd8ef..52f5271d96 100644 --- a/src/systems/parameter_buffer.jl +++ b/src/systems/parameter_buffer.jl @@ -140,7 +140,7 @@ function MTKParameters( dep_exprs = ArrayPartition((Any[missing for _ in 1:length(v)] for v in dep_buffer)...) for (sym, val) in pdeps i, j = ic.dependent_idx[sym] - dep_exprs.x[i][j] = wrap(val) + dep_exprs.x[i][j] = unwrap(val) end dep_exprs = identity.(dep_exprs) p = reorder_parameters(ic, full_parameters(sys)) @@ -423,7 +423,10 @@ function SymbolicIndexingInterface.remake_buffer(sys, oldbuf::MTKParameters, val @set! newbuf.nonnumeric = narrow_buffer_type_and_fallback_undefs.( oldbuf.nonnumeric, newbuf.nonnumeric) if newbuf.dependent_update_oop !== nothing - @set! newbuf.dependent = newbuf.dependent_update_oop(newbuf...) + @set! newbuf.dependent = narrow_buffer_type_and_fallback_undefs.( + oldbuf.dependent, + split_into_buffers( + newbuf.dependent_update_oop(newbuf...), oldbuf.dependent, Val(false))) end return newbuf end @@ -447,6 +450,7 @@ _num_subarrays(v::Tuple) = length(v) # getindex indexes the vectors, setindex! linearly indexes values # it's inconsistent, but we need it to be this way function Base.getindex(buf::MTKParameters, i) + i_orig = i if !isempty(buf.tunable) i <= _num_subarrays(buf.tunable) && return _subarrays(buf.tunable)[i] i -= _num_subarrays(buf.tunable) @@ -467,7 +471,7 @@ function Base.getindex(buf::MTKParameters, i) i <= _num_subarrays(buf.dependent) && return _subarrays(buf.dependent)[i] i -= _num_subarrays(buf.dependent) end - throw(BoundsError(buf, i)) + throw(BoundsError(buf, i_orig)) end function Base.setindex!(p::MTKParameters, val, i) function _helper(buf) @@ -551,9 +555,6 @@ function jacobian_wrt_vars(pf::F, p::MTKParameters, input_idxs, chunk::C) where for (i, val) in zip(input_idxs, p_small_inner) _set_parameter_unchecked!(p_big, val, i) end - # tunable, repack, _ = SciMLStructures.canonicalize(SciMLStructures.Tunable(), p_big) - # tunable[input_idxs] .= p_small_inner - # p_big = repack(tunable) return if pf isa SciMLBase.ParamJacobianWrapper buffer = Array{dualtype}(undef, size(pf.u)) pf(buffer, p_big) @@ -563,8 +564,6 @@ function jacobian_wrt_vars(pf::F, p::MTKParameters, input_idxs, chunk::C) where end end end - # tunable, _, _ = SciMLStructures.canonicalize(SciMLStructures.Tunable(), p) - # p_small = tunable[input_idxs] p_small = parameter_values.((p,), input_idxs) cfg = ForwardDiff.JacobianConfig(p_closure, p_small, chunk, tag) ForwardDiff.jacobian(p_closure, p_small, cfg, Val(false)) diff --git a/test/mtkparameters.jl b/test/mtkparameters.jl index b6eef43c5c..d3707f3db9 100644 --- a/test/mtkparameters.jl +++ b/test/mtkparameters.jl @@ -224,3 +224,20 @@ function loss(x) end @test_nowarn ForwardDiff.gradient(loss, collect(tunables)) + +# Ensure dependent parameters are `Tuple{...}` and not `ArrayPartition` when using +# `remake_buffer`. +@parameters p1 p2 p3[1:2] p4[1:2] +@named sys = ODESystem( + Equation[], t, [], [p1, p2, p3, p4]; parameter_dependencies = [p2 => 2p1, p4 => 3p3]) +sys = complete(sys) +ps = MTKParameters(sys, [p1 => 1.0, p3 => [2.0, 3.0]]) +@test ps[parameter_index(sys, p2)] == 2.0 +@test ps[parameter_index(sys, p4)] == [6.0, 9.0] + +newps = remake_buffer( + sys, ps, Dict(p1 => ForwardDiff.Dual(2.0), p3 => ForwardDiff.Dual.([3.0, 4.0]))) + +VDual = Vector{<:ForwardDiff.Dual} +VVDual = Vector{<:Vector{<:ForwardDiff.Dual}} +@test newps.dependent isa Union{Tuple{VDual, VVDual}, Tuple{VVDual, VDual}} From 1677d82208d69d0ec52b84b8bb098272382aed47 Mon Sep 17 00:00:00 2001 From: Christopher Rackauckas Date: Mon, 3 Jun 2024 23:26:35 -0400 Subject: [PATCH 113/316] Update to SymbolicUtils v2 --- Project.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Project.toml b/Project.toml index f5db4005f1..508a53af1c 100644 --- a/Project.toml +++ b/Project.toml @@ -107,8 +107,8 @@ SparseArrays = "1" SpecialFunctions = "0.7, 0.8, 0.9, 0.10, 1.0, 2" StaticArrays = "0.10, 0.11, 0.12, 1.0" SymbolicIndexingInterface = "0.3.12" -SymbolicUtils = "<1.6" -Symbolics = "5.26" +SymbolicUtils = "2" +Symbolics = "5.29" URIs = "1" UnPack = "0.1, 1.0" Unitful = "1.1" From 40008f08773867febba274d12a6a76a5d3916858 Mon Sep 17 00:00:00 2001 From: Christopher Rackauckas Date: Tue, 4 Jun 2024 00:48:45 -0400 Subject: [PATCH 114/316] depwarn fixes --- src/ModelingToolkit.jl | 2 +- src/clock.jl | 4 +- src/debugging.jl | 4 +- src/discretedomain.jl | 2 +- src/inputoutput.jl | 4 +- src/parameters.jl | 4 +- .../StructuralTransformations.jl | 2 +- .../symbolics_tearing.jl | 4 +- src/structural_transformation/utils.jl | 4 +- src/systems/abstractsystem.jl | 69 ++++++++++--------- src/systems/clock_inference.jl | 6 +- src/systems/connectors.jl | 4 +- src/systems/diffeqs/abstractodesystem.jl | 12 ++-- src/systems/diffeqs/odesystem.jl | 12 ++-- .../discrete_system/discrete_system.jl | 4 +- src/systems/index_cache.jl | 12 ++-- src/systems/jumps/jumpsystem.jl | 4 +- src/systems/nonlinear/nonlinearsystem.jl | 2 +- src/systems/parameter_buffer.jl | 4 +- src/systems/systemstructure.jl | 15 ++-- src/systems/unit_check.jl | 8 +-- src/systems/validation.jl | 8 +-- src/utils.jl | 50 +++++++------- src/variables.jl | 4 +- 24 files changed, 122 insertions(+), 122 deletions(-) diff --git a/src/ModelingToolkit.jl b/src/ModelingToolkit.jl index 7d85f05336..c8945cf56a 100644 --- a/src/ModelingToolkit.jl +++ b/src/ModelingToolkit.jl @@ -9,7 +9,7 @@ using PrecompileTools, Reexport end import SymbolicUtils -import SymbolicUtils: istree, arguments, operation, similarterm, promote_symtype, +import SymbolicUtils: iscall, arguments, operation, maketerm, promote_symtype, Symbolic, isadd, ismul, ispow, issym, FnType, @rule, Rewriters, substitute, metadata, BasicSymbolic, Sym, Term diff --git a/src/clock.jl b/src/clock.jl index 7ca1707724..4cf5b9170b 100644 --- a/src/clock.jl +++ b/src/clock.jl @@ -21,7 +21,7 @@ function is_continuous_domain(x) end function get_time_domain(x) - if istree(x) && operation(x) isa Operator + if iscall(x) && operation(x) isa Operator output_timedomain(x) else getmetadata(x, TimeDomain, nothing) @@ -130,7 +130,7 @@ is_concrete_time_domain(x) = x isa Union{AbstractClock, Continuous} SolverStepClock() SolverStepClock(t) -A clock that ticks at each solver step (sometimes referred to as "continuous sample time"). This clock **does generally not have equidistant tick intervals**, instead, the tick interval depends on the adaptive step-size slection of the continuous solver, as well as any continuous event handling. If adaptivity of the solver is turned off and there are no continuous events, the tick interval will be given by the fixed solver time step `dt`. +A clock that ticks at each solver step (sometimes referred to as "continuous sample time"). This clock **does generally not have equidistant tick intervals**, instead, the tick interval depends on the adaptive step-size slection of the continuous solver, as well as any continuous event handling. If adaptivity of the solver is turned off and there are no continuous events, the tick interval will be given by the fixed solver time step `dt`. Due to possibly non-equidistant tick intervals, this clock should typically not be used with discrete-time systems that assume a fixed sample time, such as PID controllers and digital filters. """ diff --git a/src/debugging.jl b/src/debugging.jl index 6fd75052d0..3e72fdd0e5 100644 --- a/src/debugging.jl +++ b/src/debugging.jl @@ -28,9 +28,9 @@ end debug_sub(eq::Equation) = debug_sub(eq.lhs) ~ debug_sub(eq.rhs) function debug_sub(ex) - istree(ex) || return ex + iscall(ex) || return ex f = operation(ex) args = map(debug_sub, arguments(ex)) f in LOGGED_FUN ? logged_fun(f, args...) : - similarterm(ex, f, args, metadata = metadata(ex)) + maketerm(typeof(ex), f, args, symtype(t), metadata(ex)) end diff --git a/src/discretedomain.jl b/src/discretedomain.jl index 723612b083..401b8e6f46 100644 --- a/src/discretedomain.jl +++ b/src/discretedomain.jl @@ -39,7 +39,7 @@ end function (D::Shift)(x::Num, allow_zero = false) !allow_zero && D.steps == 0 && return x vt = value(x) - if istree(vt) + if iscall(vt) op = operation(vt) if op isa Sample error("Cannot shift a `Sample`. Create a variable to represent the sampled value and shift that instead") diff --git a/src/inputoutput.jl b/src/inputoutput.jl index b486bb664c..f9aa2e920c 100644 --- a/src/inputoutput.jl +++ b/src/inputoutput.jl @@ -19,8 +19,8 @@ function outputs(sys) lhss = [eq.lhs for eq in o] unique([filter(isoutput, unknowns(sys)) filter(isoutput, parameters(sys)) - filter(x -> istree(x) && isoutput(x), rhss) # observed can return equations with complicated expressions, we are only looking for single Terms - filter(x -> istree(x) && isoutput(x), lhss)]) + filter(x -> iscall(x) && isoutput(x), rhss) # observed can return equations with complicated expressions, we are only looking for single Terms + filter(x -> iscall(x) && isoutput(x), lhss)]) end """ diff --git a/src/parameters.jl b/src/parameters.jl index dfaca86d95..f330942046 100644 --- a/src/parameters.jl +++ b/src/parameters.jl @@ -15,9 +15,9 @@ function isparameter(x) isparameter(p) || (hasmetadata(p, Symbolics.VariableSource) && getmetadata(p, Symbolics.VariableSource)[1] == :parameters) - elseif istree(x) && operation(x) isa Symbolic + elseif iscall(x) && operation(x) isa Symbolic varT === PARAMETER || isparameter(operation(x)) - elseif istree(x) && operation(x) == (getindex) + elseif iscall(x) && operation(x) == (getindex) isparameter(arguments(x)[1]) elseif x isa Symbolic varT === PARAMETER diff --git a/src/structural_transformation/StructuralTransformations.jl b/src/structural_transformation/StructuralTransformations.jl index b9aaca3cb6..2682f1f561 100644 --- a/src/structural_transformation/StructuralTransformations.jl +++ b/src/structural_transformation/StructuralTransformations.jl @@ -7,7 +7,7 @@ using Symbolics: unwrap, linear_expansion, fast_substitute using SymbolicUtils using SymbolicUtils.Code using SymbolicUtils.Rewriters -using SymbolicUtils: similarterm, istree +using SymbolicUtils: maketerm, iscall using ModelingToolkit using ModelingToolkit: ODESystem, AbstractSystem, var_from_nested_derivative, Differential, diff --git a/src/structural_transformation/symbolics_tearing.jl b/src/structural_transformation/symbolics_tearing.jl index f2f27e8606..1b46ba2e80 100644 --- a/src/structural_transformation/symbolics_tearing.jl +++ b/src/structural_transformation/symbolics_tearing.jl @@ -92,7 +92,7 @@ function full_equations(sys::AbstractSystem; simplify = false) @unpack subs = substitutions solved = Dict(eq.lhs => eq.rhs for eq in subs) neweqs = map(equations(sys)) do eq - if istree(eq.lhs) && operation(eq.lhs) isa Union{Shift, Differential} + if iscall(eq.lhs) && operation(eq.lhs) isa Union{Shift, Differential} return tearing_sub(eq.lhs, solved, simplify) ~ tearing_sub(eq.rhs, solved, simplify) else @@ -568,7 +568,7 @@ function tearing_reassemble(state::TearingState, var_eq_matching, for eq in obs lhs = eq.lhs - istree(lhs) || continue + iscall(lhs) || continue operation(lhs) === getindex || continue Symbolics.shape(lhs) !== Symbolics.Unknown() || continue arg1 = arguments(lhs)[1] diff --git a/src/structural_transformation/utils.jl b/src/structural_transformation/utils.jl index 3ae8fb224f..cb46599178 100644 --- a/src/structural_transformation/utils.jl +++ b/src/structural_transformation/utils.jl @@ -445,7 +445,7 @@ function simplify_shifts(var) t2 = op2.t return simplify_shifts(ModelingToolkit.Shift(t1 === nothing ? t2 : t1, s1 + s2)(vv2)) else - return similarterm(var, operation(var), simplify_shifts.(arguments(var)), - Symbolics.symtype(var); metadata = unwrap(var).metadata) + return maketerm(typeof(var), operation(var), simplify_shifts.(arguments(var)), + Symbolics.symtype(var), unwrap(var).metadata) end end diff --git a/src/systems/abstractsystem.jl b/src/systems/abstractsystem.jl index 739451509b..974eb95d7a 100644 --- a/src/systems/abstractsystem.jl +++ b/src/systems/abstractsystem.jl @@ -205,7 +205,7 @@ function wrap_array_vars(sys::AbstractSystem, exprs; dvs = unknowns(sys)) isscalar = !(exprs isa AbstractArray) array_vars = Dict{Any, AbstractArray{Int}}() for (j, x) in enumerate(dvs) - if istree(x) && operation(x) == getindex + if iscall(x) && operation(x) == getindex arg = arguments(x)[1] inds = get!(() -> Int[], array_vars, arg) push!(inds, j) @@ -352,7 +352,7 @@ function SymbolicIndexingInterface.is_variable(sys::AbstractSystem, sym) end if has_index_cache(sys) && (ic = get_index_cache(sys)) !== nothing return is_variable(ic, sym) || - istree(sym) && operation(sym) === getindex && + iscall(sym) && operation(sym) === getindex && is_variable(ic, first(arguments(sym))) end return any(isequal(sym), variable_symbols(sys)) || @@ -378,7 +378,7 @@ function SymbolicIndexingInterface.variable_index(sys::AbstractSystem, sym) if has_index_cache(sys) && (ic = get_index_cache(sys)) !== nothing return if (idx = variable_index(ic, sym)) !== nothing idx - elseif istree(sym) && operation(sym) === getindex && + elseif iscall(sym) && operation(sym) === getindex && (idx = variable_index(ic, first(arguments(sym)))) !== nothing idx[arguments(sym)[(begin + 1):end]...] else @@ -416,7 +416,7 @@ function SymbolicIndexingInterface.is_parameter(sys::AbstractSystem, sym) sym = unwrap(sym) if has_index_cache(sys) && (ic = get_index_cache(sys)) !== nothing return is_parameter(ic, sym) || - istree(sym) && operation(sym) === getindex && + iscall(sym) && operation(sym) === getindex && is_parameter(ic, first(arguments(sym))) end if unwrap(sym) isa Int @@ -441,7 +441,7 @@ function SymbolicIndexingInterface.parameter_index(sys::AbstractSystem, sym) if has_index_cache(sys) && (ic = get_index_cache(sys)) !== nothing return if (idx = parameter_index(ic, sym)) !== nothing idx - elseif istree(sym) && operation(sym) === getindex && + elseif iscall(sym) && operation(sym) === getindex && (idx = parameter_index(ic, first(arguments(sym)))) !== nothing ParameterIndex(idx.portion, (idx.idx..., arguments(sym)[(begin + 1):end]...)) else @@ -745,10 +745,10 @@ function _apply_to_variables(f::F, ex) where {F} if isvariable(ex) return f(ex) end - istree(ex) || return ex - similarterm(ex, _apply_to_variables(f, operation(ex)), + iscall(ex) || return ex + maketerm(typeof(ex), _apply_to_variables(f, operation(ex)), map(Base.Fix1(_apply_to_variables, f), arguments(ex)), - metadata = metadata(ex)) + symtype(ex), metadata(ex)) end abstract type SymScope end @@ -756,11 +756,11 @@ abstract type SymScope end struct LocalScope <: SymScope end function LocalScope(sym::Union{Num, Symbolic, Symbolics.Arr{Num}}) apply_to_variables(sym) do sym - if istree(sym) && operation(sym) === getindex + if iscall(sym) && operation(sym) === getindex args = arguments(sym) a1 = setmetadata(args[1], SymScope, LocalScope()) - similarterm(sym, operation(sym), [a1, args[2:end]...]; - metadata = metadata(sym)) + maketerm(typeof(sym), operation(sym), [a1, args[2:end]...]; + symtype(sym), metadata(sym)) else setmetadata(sym, SymScope, LocalScope()) end @@ -772,12 +772,12 @@ struct ParentScope <: SymScope end function ParentScope(sym::Union{Num, Symbolic, Symbolics.Arr{Num}}) apply_to_variables(sym) do sym - if istree(sym) && operation(sym) === getindex + if iscall(sym) && operation(sym) === getindex args = arguments(sym) a1 = setmetadata(args[1], SymScope, ParentScope(getmetadata(value(args[1]), SymScope, LocalScope()))) - similarterm(sym, operation(sym), [a1, args[2:end]...]; - metadata = metadata(sym)) + maketerm(typeof(sym), operation(sym), [a1, args[2:end]...]; + symtype(sym), metadata(sym)) else setmetadata(sym, SymScope, ParentScope(getmetadata(value(sym), SymScope, LocalScope()))) @@ -791,12 +791,12 @@ struct DelayParentScope <: SymScope end function DelayParentScope(sym::Union{Num, Symbolic, Symbolics.Arr{Num}}, N) apply_to_variables(sym) do sym - if istree(sym) && operation(sym) == getindex + if iscall(sym) && operation(sym) == getindex args = arguments(sym) a1 = setmetadata(args[1], SymScope, DelayParentScope(getmetadata(value(args[1]), SymScope, LocalScope()), N)) - similarterm(sym, operation(sym), [a1, args[2:end]...]; - metadata = metadata(sym)) + maketerm(typeof(sym), operation(sym), [a1, args[2:end]...]; + symtype(sym), metadata(sym)) else setmetadata(sym, SymScope, DelayParentScope(getmetadata(value(sym), SymScope, LocalScope()), N)) @@ -808,11 +808,11 @@ DelayParentScope(sym::Union{Num, Symbolic, Symbolics.Arr{Num}}) = DelayParentSco struct GlobalScope <: SymScope end function GlobalScope(sym::Union{Num, Symbolic, Symbolics.Arr{Num}}) apply_to_variables(sym) do sym - if istree(sym) && operation(sym) == getindex + if iscall(sym) && operation(sym) == getindex args = arguments(sym) a1 = setmetadata(args[1], SymScope, GlobalScope()) - similarterm(sym, operation(sym), [a1, args[2:end]...]; - metadata = metadata(sym)) + maketerm(typeof(sym), operation(sym), [a1, args[2:end]...]; + symtype(sym), metadata(sym)) else setmetadata(sym, SymScope, GlobalScope()) end @@ -827,16 +827,16 @@ function renamespace(sys, x) x = unwrap(x) if x isa Symbolic T = typeof(x) - if istree(x) && operation(x) isa Operator - return similarterm(x, operation(x), + if iscall(x) && operation(x) isa Operator + return maketerm(typeof(x), operation(x), Any[renamespace(sys, only(arguments(x)))]; - metadata = metadata(x))::T + symtype(x), metadata(x))::T end - if istree(x) && operation(x) === getindex + if iscall(x) && operation(x) === getindex args = arguments(x) - return similarterm( - x, operation(x), vcat(renamespace(sys, args[1]), args[2:end]); - metadata = metadata(x))::T + return maketerm( + typeof(x), operation(x), vcat(renamespace(sys, args[1]), args[2:end]); + symtype(x), metadata(x))::T end let scope = getmetadata(x, SymScope, LocalScope()) if scope isa LocalScope @@ -904,7 +904,7 @@ function namespace_expr( O = unwrap(O) if any(isequal(O), ivs) return O - elseif istree(O) + elseif iscall(O) T = typeof(O) renamed = let sys = sys, n = n, T = T map(a -> namespace_expr(a, sys, n; ivs)::Any, arguments(O)) @@ -913,13 +913,14 @@ function namespace_expr( # Use renamespace so the scope is correct, and make sure to use the # metadata from the rescoped variable rescoped = renamespace(n, O) - similarterm(O, operation(rescoped), renamed, + maketerm(typeof(rescoped), operation(rescoped), renamed, + symtype(rescoped) metadata = metadata(rescoped)) elseif Symbolics.isarraysymbolic(O) # promote_symtype doesn't work for array symbolics - similarterm(O, operation(O), renamed, symtype(O), metadata = metadata(O)) + maketerm(typeof(O), operation(O), renamed, symtype(O), metadata(O)) else - similarterm(O, operation(O), renamed, metadata = metadata(O)) + maketerm(typeof(O), operation(O), renamed, symtype(O), metadata(O)) end elseif isvariable(O) renamespace(n, O) @@ -1097,7 +1098,7 @@ function time_varying_as_func(x, sys::AbstractTimeDependentSystem) # than pass in a value in place of x(t). # # This is done by just making `x` the argument of the function. - if istree(x) && + if iscall(x) && issym(operation(x)) && !(length(arguments(x)) == 1 && isequal(arguments(x)[1], get_iv(sys))) return operation(x) @@ -1125,7 +1126,7 @@ function push_vars!(stmt, name, typ, vars) isempty(vars) && return vars_expr = Expr(:macrocall, typ, nothing) for s in vars - if istree(s) + if iscall(s) f = nameof(operation(s)) args = arguments(s) ex = :($f($(args...))) @@ -1142,7 +1143,7 @@ function round_trip_expr(t, var2name) name = get(var2name, t, nothing) name !== nothing && return name issym(t) && return nameof(t) - istree(t) || return t + iscall(t) || return t f = round_trip_expr(operation(t), var2name) args = map(Base.Fix2(round_trip_expr, var2name), arguments(t)) return :($f($(args...))) diff --git a/src/systems/clock_inference.jl b/src/systems/clock_inference.jl index 66af67d026..120daccb70 100644 --- a/src/systems/clock_inference.jl +++ b/src/systems/clock_inference.jl @@ -48,7 +48,7 @@ function substitute_sample_time(eq::Equation, dt) end function substitute_sample_time(ex, dt) - istree(ex) || return ex + iscall(ex) || return ex op = operation(ex) args = arguments(ex) if op == SampleTime @@ -63,7 +63,7 @@ function substitute_sample_time(ex, dt) end new_args[i] = ex_arg end - similarterm(ex, op, new_args, symtype(ex); metadata = metadata(ex)) + maketerm(typeof(ex), op, new_args, symtype(ex), metadata(ex)) end end @@ -128,7 +128,7 @@ function resize_or_push!(v, val, idx) end function is_time_domain_conversion(v) - istree(v) && (o = operation(v)) isa Operator && + iscall(v) && (o = operation(v)) isa Operator && input_timedomain(o) != output_timedomain(o) end diff --git a/src/systems/connectors.jl b/src/systems/connectors.jl index 88df41d94f..f4fd5116e8 100644 --- a/src/systems/connectors.jl +++ b/src/systems/connectors.jl @@ -14,7 +14,7 @@ end function get_connection_type(s) s = unwrap(s) - if istree(s) && operation(s) === getindex + if iscall(s) && operation(s) === getindex s = arguments(s)[1] end getmetadata(s, VariableConnectType, Equality) @@ -82,7 +82,7 @@ function collect_instream!(set, eq::Equation) end function collect_instream!(set, expr, occurs = false) - istree(expr) || return occurs + iscall(expr) || return occurs op = operation(expr) op === instream && (push!(set, expr); occurs = true) for a in SymbolicUtils.unsorted_arguments(expr) diff --git a/src/systems/diffeqs/abstractodesystem.jl b/src/systems/diffeqs/abstractodesystem.jl index b976945f79..13af9f6b3d 100644 --- a/src/systems/diffeqs/abstractodesystem.jl +++ b/src/systems/diffeqs/abstractodesystem.jl @@ -202,7 +202,7 @@ end function isdelay(var, iv) iv === nothing && return false isvariable(var) || return false - if istree(var) && !ModelingToolkit.isoperator(var, Symbolics.Operator) + if iscall(var) && !ModelingToolkit.isoperator(var, Symbolics.Operator) args = arguments(var) length(args) == 1 || return false isequal(args[1], iv) || return true @@ -229,11 +229,11 @@ function delay_to_function(expr, iv, sts, ps, h) time = arguments(expr)[1] idx = sts[v] return term(getindex, h(Sym{Any}(:ˍ₋arg3), time), idx, type = Real) # BIG BIG HACK - elseif istree(expr) - return similarterm(expr, + elseif iscall(expr) + return maketerm(typeof(expr), operation(expr), map(x -> delay_to_function(x, iv, sts, ps, h), arguments(expr)); - metadata = metadata(expr)) + symtype(expr), metadata(expr)) else return expr end @@ -244,7 +244,7 @@ function calculate_massmatrix(sys::AbstractODESystem; simplify = false) dvs = unknowns(sys) M = zeros(length(eqs), length(eqs)) for (i, eq) in enumerate(eqs) - if istree(eq.lhs) && operation(eq.lhs) isa Differential + 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 @@ -1553,7 +1553,7 @@ InitializationProblem{iip}(sys::AbstractODESystem, u0map, tspan, kwargs...) where {iip} ``` -Generates a NonlinearProblem or NonlinearLeastSquaresProblem from an ODESystem +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. """ diff --git a/src/systems/diffeqs/odesystem.jl b/src/systems/diffeqs/odesystem.jl index 409c9c441e..b1d7f7f55c 100644 --- a/src/systems/diffeqs/odesystem.jl +++ b/src/systems/diffeqs/odesystem.jl @@ -315,7 +315,7 @@ function ODESystem(eqs, iv; kwargs...) end new_ps = OrderedSet() for p in ps - if istree(p) && operation(p) === getindex + 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)) @@ -402,16 +402,16 @@ function build_explicit_observed_function(sys, ts; sts = Set(unknowns(sys)) sts = union(sts, - Set(arguments(st)[1] for st in sts if istree(st) && operation(st) === getindex)) + Set(arguments(st)[1] for st in sts if iscall(st) && operation(st) === getindex)) observed_idx = Dict(x.lhs => i for (i, x) in enumerate(obs)) param_set = Set(parameters(sys)) param_set = union(param_set, - Set(arguments(p)[1] for p in param_set if istree(p) && operation(p) === getindex)) + Set(arguments(p)[1] for p in param_set if iscall(p) && operation(p) === getindex)) param_set_ns = Set(unknowns(sys, p) for p in parameters(sys)) param_set_ns = union(param_set_ns, Set(arguments(p)[1] - for p in param_set_ns if istree(p) && operation(p) === getindex)) + for p in param_set_ns if iscall(p) && operation(p) === getindex)) 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)) @@ -516,7 +516,7 @@ function convert_system(::Type{<:ODESystem}, sys, t; name = nameof(sys)) sts = unknowns(sys) newsts = similar(sts, Any) for (i, s) in enumerate(sts) - if istree(s) + 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)`.")) @@ -525,7 +525,7 @@ function convert_system(::Type{<:ODESystem}, sys, t; name = nameof(sys)) newsts[i] = s continue end - ns = similarterm(s, operation(s), Any[t]; metadata = SymbolicUtils.metadata(s)) + ns = maketerm(typeof(s), operation(s), Any[t], SymbolicUtils.symtype(s), SymbolicUtils.metadata(s)) newsts[i] = ns varmap[s] = ns else diff --git a/src/systems/discrete_system/discrete_system.jl b/src/systems/discrete_system/discrete_system.jl index bd72c533d0..984429a504 100644 --- a/src/systems/discrete_system/discrete_system.jl +++ b/src/systems/discrete_system/discrete_system.jl @@ -173,7 +173,7 @@ function DiscreteSystem(eqs, iv; kwargs...) for eq in eqs collect_vars!(allunknowns, ps, eq.lhs, iv; op = Shift) collect_vars!(allunknowns, ps, eq.rhs, iv; op = Shift) - if istree(eq.lhs) && operation(eq.lhs) isa 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 && @@ -183,7 +183,7 @@ function DiscreteSystem(eqs, iv; kwargs...) end new_ps = OrderedSet() for p in ps - if istree(p) && operation(p) === getindex + 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)) diff --git a/src/systems/index_cache.jl b/src/systems/index_cache.jl index 07bef4b450..c8d1e362e9 100644 --- a/src/systems/index_cache.jl +++ b/src/systems/index_cache.jl @@ -49,14 +49,14 @@ function IndexCache(sys::AbstractSystem) end unk_idxs[usym] = sym_idx - if hasname(sym) && (!istree(sym) || operation(sym) !== getindex) + if hasname(sym) && (!iscall(sym) || operation(sym) !== getindex) unk_idxs[getname(usym)] = sym_idx end idx += length(sym) end for sym in unks usym = unwrap(sym) - istree(sym) && operation(sym) === getindex || continue + iscall(sym) && operation(sym) === getindex || continue arrsym = arguments(sym)[1] all(haskey(unk_idxs, arrsym[i]) for i in eachindex(arrsym)) || continue @@ -142,7 +142,7 @@ function IndexCache(sys::AbstractSystem) for (j, p) in enumerate(buf) idxs[p] = (i, j) idxs[default_toterm(p)] = (i, j) - if hasname(p) && (!istree(p) || operation(p) !== getindex) + if hasname(p) && (!iscall(p) || operation(p) !== getindex) idxs[getname(p)] = (i, j) idxs[getname(default_toterm(p))] = (i, j) end @@ -208,7 +208,7 @@ end function check_index_map(idxmap, sym) if (idx = get(idxmap, sym, nothing)) !== nothing return idx - elseif !isa(sym, Symbol) && (!istree(sym) || operation(sym) !== getindex) && + elseif !isa(sym, Symbol) && (!iscall(sym) || operation(sym) !== getindex) && hasname(sym) && (idx = get(idxmap, getname(sym), nothing)) !== nothing return idx end @@ -216,7 +216,7 @@ function check_index_map(idxmap, sym) isequal(sym, dsym) && return nothing if (idx = get(idxmap, dsym, nothing)) !== nothing idx - elseif !isa(dsym, Symbol) && (!istree(dsym) || operation(dsym) !== getindex) && + elseif !isa(dsym, Symbol) && (!iscall(dsym) || operation(dsym) !== getindex) && hasname(dsym) && (idx = get(idxmap, getname(dsym), nothing)) !== nothing idx else @@ -236,7 +236,7 @@ function ParameterIndex(ic::IndexCache, p, sub_idx = ()) ParameterIndex(DEPENDENT_PORTION, (ic.dependent_idx[p]..., sub_idx...)) elseif haskey(ic.nonnumeric_idx, p) ParameterIndex(NONNUMERIC_PORTION, (ic.nonnumeric_idx[p]..., sub_idx...)) - elseif istree(p) && operation(p) === getindex + elseif iscall(p) && operation(p) === getindex _p, sub_idx... = arguments(p) ParameterIndex(ic, _p, sub_idx) else diff --git a/src/systems/jumps/jumpsystem.jl b/src/systems/jumps/jumpsystem.jl index 4feb7a1da7..36b8719e1e 100644 --- a/src/systems/jumps/jumpsystem.jl +++ b/src/systems/jumps/jumpsystem.jl @@ -271,7 +271,7 @@ 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 !istree(spec) && _iszero(spec) + if !iscall(spec) && _iszero(spec) push!(rs, 0 => stoich) else push!(rs, unknowntoid[spec] => stoich) @@ -285,7 +285,7 @@ function numericnstoich(mtrs::Vector{Pair{V, W}}, unknowntoid) where {V, W} ns = Vector{Pair{Int, W}}() for (wspec, stoich) in mtrs spec = value(wspec) - !istree(spec) && _iszero(spec) && + !iscall(spec) && _iszero(spec) && error("Net stoichiometry can not have a species labelled 0.") push!(ns, unknowntoid[spec] => stoich) end diff --git a/src/systems/nonlinear/nonlinearsystem.jl b/src/systems/nonlinear/nonlinearsystem.jl index 8035add4b7..dd6243ef00 100644 --- a/src/systems/nonlinear/nonlinearsystem.jl +++ b/src/systems/nonlinear/nonlinearsystem.jl @@ -171,7 +171,7 @@ function NonlinearSystem(eqs; kwargs...) end new_ps = OrderedSet() for p in ps - if istree(p) && operation(p) === getindex + 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)) diff --git a/src/systems/parameter_buffer.jl b/src/systems/parameter_buffer.jl index 52f5271d96..d8ba8fa1df 100644 --- a/src/systems/parameter_buffer.jl +++ b/src/systems/parameter_buffer.jl @@ -48,7 +48,7 @@ function MTKParameters( Dict(default_toterm(unwrap(k)) => v for (k, v) in p)) p = Dict(unwrap(k) => fixpoint_sub(v, p) for (k, v) in p) for (sym, _) in p - if istree(sym) && operation(sym) === getindex && + if iscall(sym) && operation(sym) === getindex && first(arguments(sym)) in all_ps error("Scalarized parameter values ($sym) are not supported. Instead of `[p[1] => 1.0, p[2] => 2.0]` use `[p => [1.0, 2.0]]`") end @@ -64,7 +64,7 @@ function MTKParameters( haskey(p, ttsym) && continue hasname(ttsym) && haskey(p, getname(ttsym)) && continue - istree(sym) && operation(sym) === getindex && haskey(p, arguments(sym)[1]) && + iscall(sym) && operation(sym) === getindex && haskey(p, arguments(sym)[1]) && continue push!(missing_params, sym) end diff --git a/src/systems/systemstructure.jl b/src/systems/systemstructure.jl index 1c5f3ca28b..4fac0f8914 100644 --- a/src/systems/systemstructure.jl +++ b/src/systems/systemstructure.jl @@ -1,7 +1,7 @@ using DataStructures using Symbolics: linear_expansion, unwrap, Connection -using SymbolicUtils: istree, operation, arguments, Symbolic -using SymbolicUtils: quick_cancel, similarterm +using SymbolicUtils: iscall, operation, arguments, Symbolic +using SymbolicUtils: quick_cancel, maketerm using ..ModelingToolkit import ..ModelingToolkit: isdiffeq, var_from_nested_derivative, vars!, flatten, value, InvalidSystemException, isdifferential, _iszero, @@ -18,9 +18,8 @@ using SparseArrays function quick_cancel_expr(expr) Rewriters.Postwalk(quick_cancel, - similarterm = (x, f, args; kws...) -> similarterm(x, f, args, - SymbolicUtils.symtype(x); - metadata = SymbolicUtils.metadata(x), + similarterm = (x, f, args; kws...) -> maketerm(typeof(x), f, args, + SymbolicUtils.symtype(x), SymbolicUtils.metadata(x), kws...))(expr) end @@ -288,7 +287,7 @@ function TearingState(sys; quick_cancel = false, check = true) _var, _ = var_from_nested_derivative(v) any(isequal(_var), ivs) && continue if isparameter(_var) || - (istree(_var) && isparameter(operation(_var)) || isconstant(_var)) + (iscall(_var) && isparameter(operation(_var)) || isconstant(_var)) continue end v = scalarize(v) @@ -308,7 +307,7 @@ function TearingState(sys; quick_cancel = false, check = true) _var, _ = var_from_nested_derivative(var) any(isequal(_var), ivs) && continue if isparameter(_var) || - (istree(_var) && isparameter(operation(_var)) || isconstant(_var)) + (iscall(_var) && isparameter(operation(_var)) || isconstant(_var)) continue end varidx = addvar!(var) @@ -328,7 +327,7 @@ function TearingState(sys; quick_cancel = false, check = true) dvar = var idx = varidx - if istree(var) && operation(var) isa Symbolics.Operator && + if iscall(var) && operation(var) isa Symbolics.Operator && !isdifferential(var) && (it = input_timedomain(var)) !== nothing set_incidence = false var = only(arguments(var)) diff --git a/src/systems/unit_check.jl b/src/systems/unit_check.jl index 677d29895f..f053d9cf46 100644 --- a/src/systems/unit_check.jl +++ b/src/systems/unit_check.jl @@ -147,17 +147,17 @@ function get_unit(x::Symbolic) else pargs[2] isa Number ? base^pargs[2] : (1 * base)^pargs[2] end - elseif istree(x) + elseif iscall(x) op = operation(x) - if issym(op) || (istree(op) && istree(operation(op))) # Dependent variables, not function calls + if issym(op) || (iscall(op) && iscall(operation(op))) # Dependent variables, not function calls return screen_unit(getmetadata(x, VariableUnit, unitless)) # Like x(t) or x[i] - elseif istree(op) && !istree(operation(op)) + elseif iscall(op) && !iscall(operation(op)) gp = getmetadata(x, Symbolics.GetindexParent, nothing) # Like x[1](t) return screen_unit(getmetadata(gp, VariableUnit, unitless)) end # Actual function calls: args = arguments(x) return get_unit(op, args) - else # This function should only be reached by Terms, for which `istree` is true + else # This function should only be reached by Terms, for which `iscall` is true throw(ArgumentError("Unsupported value $x.")) end end diff --git a/src/systems/validation.jl b/src/systems/validation.jl index ea458d8b7e..84dd3b07e5 100644 --- a/src/systems/validation.jl +++ b/src/systems/validation.jl @@ -124,17 +124,17 @@ function get_unit(x::Symbolic) else pargs[2] isa Number ? base^pargs[2] : (1 * base)^pargs[2] end - elseif istree(x) + elseif iscall(x) op = operation(x) - if issym(op) || (istree(op) && istree(operation(op))) # Dependent variables, not function calls + if issym(op) || (iscall(op) && iscall(operation(op))) # Dependent variables, not function calls return screen_unit(getmetadata(x, VariableUnit, unitless)) # Like x(t) or x[i] - elseif istree(op) && !istree(operation(op)) + elseif iscall(op) && !iscall(operation(op)) gp = getmetadata(x, Symbolics.GetindexParent, nothing) # Like x[1](t) return screen_unit(getmetadata(gp, VariableUnit, unitless)) end # Actual function calls: args = arguments(x) return get_unit(op, args) - else # This function should only be reached by Terms, for which `istree` is true + else # This function should only be reached by Terms, for which `iscall` is true throw(ArgumentError("Unsupported value $x.")) end end diff --git a/src/utils.jl b/src/utils.jl index 547399c832..2994b8e511 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -16,21 +16,21 @@ function make_operation(@nospecialize(op), args) end function detime_dvs(op) - if !istree(op) + if !iscall(op) op elseif issym(operation(op)) Sym{Real}(nameof(operation(op))) else - similarterm(op, operation(op), detime_dvs.(arguments(op)); - metadata = metadata(op)) + maketerm(typeof(op), operation(op), detime_dvs.(arguments(op)), + symtype(op), metadata(op)) end end function retime_dvs(op, dvs, iv) issym(op) && return Sym{FnType{Tuple{symtype(iv)}, Real}}(nameof(op))(iv) - istree(op) ? - similarterm(op, operation(op), retime_dvs.(arguments(op), (dvs,), (iv,)); - metadata = metadata(op)) : + iscall(op) ? + maketerm(typeof(op), operation(op), retime_dvs.(arguments(op), (dvs,), (iv,)); + symtype(op), metadata(op)) : op end @@ -188,7 +188,7 @@ end Get all the independent variables with respect to which differentials are taken. """ function collect_ivs_from_nested_operator!(ivs, x, target_op) - if !istree(x) + if !iscall(x) return end op = operation(unwrap(x)) @@ -204,9 +204,9 @@ function collect_ivs_from_nested_operator!(ivs, x, target_op) end function iv_from_nested_derivative(x, op = Differential) - if istree(x) && operation(x) == getindex + if iscall(x) && operation(x) == getindex iv_from_nested_derivative(arguments(x)[1], op) - elseif istree(x) + elseif iscall(x) operation(x) isa op ? iv_from_nested_derivative(arguments(x)[1], op) : arguments(x)[1] elseif issym(x) @@ -248,7 +248,7 @@ function collect_var_to_name!(vars, xs) hasname(xarr) || continue vars[Symbolics.getname(xarr)] = xarr else - if istree(x) && operation(x) === getindex + if iscall(x) && operation(x) === getindex x = arguments(x)[1] end x = unwrap(x) @@ -276,7 +276,7 @@ end Check if difference/derivative operation occurs in the R.H.S. of an equation """ function _check_operator_variables(eq, op::T, expr = eq.rhs) where {T} - istree(expr) || return nothing + iscall(expr) || return nothing if operation(expr) isa op throw_invalid_operator(expr, eq, op) end @@ -297,10 +297,10 @@ function check_operator_variables(eqs, op::T) where {T} if op === Differential is_tmp_fine = isdifferential(x) else - is_tmp_fine = istree(x) && !(operation(x) isa op) + is_tmp_fine = iscall(x) && !(operation(x) isa op) end else - nd = count(x -> istree(x) && !(operation(x) isa op), tmp) + nd = count(x -> iscall(x) && !(operation(x) isa op), tmp) is_tmp_fine = iszero(nd) end is_tmp_fine || @@ -314,7 +314,7 @@ function check_operator_variables(eqs, op::T) where {T} end end -isoperator(expr, op) = istree(expr) && operation(expr) isa op +isoperator(expr, op) = iscall(expr) && operation(expr) isa op isoperator(op) = expr -> isoperator(expr, op) isdifferential(expr) = isoperator(expr, Differential) @@ -345,7 +345,7 @@ v == Set([D(y), u]) ``` """ function vars(exprs::Symbolic; op = Differential) - istree(exprs) ? vars([exprs]; op = op) : Set([exprs]) + iscall(exprs) ? vars([exprs]; op = op) : Set([exprs]) end vars(exprs::Num; op = Differential) = vars(unwrap(exprs); op) vars(exprs::Symbolics.Arr; op = Differential) = vars(unwrap(exprs); op) @@ -358,13 +358,13 @@ function vars!(vars, O; op = Differential) if isvariable(O) return push!(vars, O) end - !istree(O) && return vars + !iscall(O) && return vars operation(O) isa op && return push!(vars, O) if operation(O) === (getindex) arr = first(arguments(O)) - istree(arr) && operation(arr) isa op && return push!(vars, O) + iscall(arr) && operation(arr) isa op && return push!(vars, O) isvariable(arr) && return push!(vars, O) end @@ -422,7 +422,7 @@ function collect_applied_operators(x, op) v = vars(x, op = op) filter(v) do x issym(x) && return false - istree(x) && return operation(x) isa op + iscall(x) && return operation(x) isa op false end end @@ -431,7 +431,7 @@ function find_derivatives!(vars, expr::Equation, f = identity) (find_derivatives!(vars, expr.lhs, f); find_derivatives!(vars, expr.rhs, f); vars) end function find_derivatives!(vars, expr, f) - !istree(O) && return vars + !iscall(O) && return vars operation(O) isa Differential && push!(vars, f(O)) for arg in arguments(O) vars!(vars, arg) @@ -444,7 +444,7 @@ function collect_vars!(unknowns, parameters, expr, iv; op = Differential) collect_var!(unknowns, parameters, expr, iv) else for var in vars(expr; op) - if istree(var) && operation(var) isa Differential + if iscall(var) && operation(var) isa Differential var, _ = var_from_nested_derivative(var) end collect_var!(unknowns, parameters, var, iv) @@ -455,7 +455,7 @@ end function collect_var!(unknowns, parameters, var, iv) isequal(var, iv) && return nothing - if isparameter(var) || (istree(var) && isparameter(operation(var))) + if isparameter(var) || (iscall(var) && isparameter(operation(var))) push!(parameters, var) elseif !isconstant(var) push!(unknowns, var) @@ -634,7 +634,7 @@ function promote_to_concrete(vs; tofloat = true, use_union = true) if Base.isconcretetype(T) && (!tofloat || T === float(T)) # nothing to do return vs else - sym_vs = filter(x -> SymbolicUtils.issym(x) || SymbolicUtils.istree(x), vs) + sym_vs = filter(x -> SymbolicUtils.issym(x) || SymbolicUtils.iscall(x), vs) isempty(sym_vs) || throw_missingvars_in_sys(sym_vs) C = nothing @@ -794,9 +794,9 @@ function jacobian_wrt_vars(pf::F, p, input_idxs, chunk::C) where {F, C} end function fold_constants(ex) - if istree(ex) - similarterm(ex, operation(ex), map(fold_constants, arguments(ex)), - symtype(ex); metadata = metadata(ex)) + if iscall(ex) + maketerm(typeof(ex), operation(ex), map(fold_constants, arguments(ex)), + symtype(ex), metadata(ex)) elseif issym(ex) && isconstant(ex) getdefault(ex) else diff --git a/src/variables.jl b/src/variables.jl index 7fce2f5ad4..dc707dff75 100644 --- a/src/variables.jl +++ b/src/variables.jl @@ -102,7 +102,7 @@ isirreducible(x) = isvarkind(VariableIrreducible, x) state_priority(x) = convert(Float64, getmetadata(x, VariableStatePriority, 0.0))::Float64 function default_toterm(x) - if istree(x) && (op = operation(x)) isa Operator + if iscall(x) && (op = operation(x)) isa Operator if !(op isa Differential) if op isa Shift && op.steps < 0 return x @@ -232,7 +232,7 @@ ishistory(x) = ishistory(unwrap(x)) ishistory(x::Symbolic) = getmetadata(x, IsHistory, false) hist(x, t) = wrap(hist(unwrap(x), t)) function hist(x::Symbolic, t) - setmetadata(toparam(similarterm(x, operation(x), [unwrap(t)], metadata = metadata(x))), + setmetadata(toparam(maketerm(typeof(x), operation(x), [unwrap(t)], symtype(x), metadata(x))), IsHistory, true) end From bd81f70c6aa4f1894e1505370c486fcc666ef5ae Mon Sep 17 00:00:00 2001 From: Christopher Rackauckas Date: Tue, 4 Jun 2024 00:56:53 -0400 Subject: [PATCH 115/316] format --- src/systems/diffeqs/odesystem.jl | 3 ++- src/variables.jl | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/systems/diffeqs/odesystem.jl b/src/systems/diffeqs/odesystem.jl index b1d7f7f55c..4b22602c8a 100644 --- a/src/systems/diffeqs/odesystem.jl +++ b/src/systems/diffeqs/odesystem.jl @@ -525,7 +525,8 @@ function convert_system(::Type{<:ODESystem}, sys, t; name = nameof(sys)) newsts[i] = s continue end - ns = maketerm(typeof(s), operation(s), Any[t], SymbolicUtils.symtype(s), SymbolicUtils.metadata(s)) + ns = maketerm(typeof(s), operation(s), Any[t], + SymbolicUtils.symtype(s), SymbolicUtils.metadata(s)) newsts[i] = ns varmap[s] = ns else diff --git a/src/variables.jl b/src/variables.jl index dc707dff75..fb3e321f0c 100644 --- a/src/variables.jl +++ b/src/variables.jl @@ -232,7 +232,8 @@ ishistory(x) = ishistory(unwrap(x)) ishistory(x::Symbolic) = getmetadata(x, IsHistory, false) hist(x, t) = wrap(hist(unwrap(x), t)) function hist(x::Symbolic, t) - setmetadata(toparam(maketerm(typeof(x), operation(x), [unwrap(t)], symtype(x), metadata(x))), + setmetadata( + toparam(maketerm(typeof(x), operation(x), [unwrap(t)], symtype(x), metadata(x))), IsHistory, true) end From ed3be0d15f43777a06f5c5323e7983cdb0d3ab95 Mon Sep 17 00:00:00 2001 From: Christopher Rackauckas Date: Tue, 4 Jun 2024 01:19:28 -0400 Subject: [PATCH 116/316] typo --- src/utils.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils.jl b/src/utils.jl index 2994b8e511..9476cded7d 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -29,7 +29,7 @@ end function retime_dvs(op, dvs, iv) issym(op) && return Sym{FnType{Tuple{symtype(iv)}, Real}}(nameof(op))(iv) iscall(op) ? - maketerm(typeof(op), operation(op), retime_dvs.(arguments(op), (dvs,), (iv,)); + maketerm(typeof(op), operation(op), retime_dvs.(arguments(op), (dvs,), (iv,)), symtype(op), metadata(op)) : op end From 71c4cab647b59cdcdc9c70682d3309ab3684b9b3 Mon Sep 17 00:00:00 2001 From: Christopher Rackauckas Date: Tue, 4 Jun 2024 03:46:30 -0400 Subject: [PATCH 117/316] typo --- 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 974eb95d7a..e4d8b9bb98 100644 --- a/src/systems/abstractsystem.jl +++ b/src/systems/abstractsystem.jl @@ -759,7 +759,7 @@ function LocalScope(sym::Union{Num, Symbolic, Symbolics.Arr{Num}}) if iscall(sym) && operation(sym) === getindex args = arguments(sym) a1 = setmetadata(args[1], SymScope, LocalScope()) - maketerm(typeof(sym), operation(sym), [a1, args[2:end]...]; + maketerm(typeof(sym), operation(sym), [a1, args[2:end]...], symtype(sym), metadata(sym)) else setmetadata(sym, SymScope, LocalScope()) From 822505044636d333801abf5821fb27fd22c6fb0a Mon Sep 17 00:00:00 2001 From: Christopher Rackauckas Date: Tue, 4 Jun 2024 04:14:59 -0400 Subject: [PATCH 118/316] fix more typos --- src/systems/abstractsystem.jl | 14 +++++++------- src/systems/diffeqs/abstractodesystem.jl | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/systems/abstractsystem.jl b/src/systems/abstractsystem.jl index e4d8b9bb98..d16506a8d9 100644 --- a/src/systems/abstractsystem.jl +++ b/src/systems/abstractsystem.jl @@ -776,7 +776,7 @@ function ParentScope(sym::Union{Num, Symbolic, Symbolics.Arr{Num}}) args = arguments(sym) a1 = setmetadata(args[1], SymScope, ParentScope(getmetadata(value(args[1]), SymScope, LocalScope()))) - maketerm(typeof(sym), operation(sym), [a1, args[2:end]...]; + maketerm(typeof(sym), operation(sym), [a1, args[2:end]...], symtype(sym), metadata(sym)) else setmetadata(sym, SymScope, @@ -795,7 +795,7 @@ function DelayParentScope(sym::Union{Num, Symbolic, Symbolics.Arr{Num}}, N) 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]...]; + maketerm(typeof(sym), operation(sym), [a1, args[2:end]...], symtype(sym), metadata(sym)) else setmetadata(sym, SymScope, @@ -811,7 +811,7 @@ function GlobalScope(sym::Union{Num, Symbolic, Symbolics.Arr{Num}}) if iscall(sym) && operation(sym) == getindex args = arguments(sym) a1 = setmetadata(args[1], SymScope, GlobalScope()) - maketerm(typeof(sym), operation(sym), [a1, args[2:end]...]; + maketerm(typeof(sym), operation(sym), [a1, args[2:end]...], symtype(sym), metadata(sym)) else setmetadata(sym, SymScope, GlobalScope()) @@ -829,13 +829,13 @@ function renamespace(sys, x) T = typeof(x) if iscall(x) && operation(x) isa Operator return maketerm(typeof(x), operation(x), - Any[renamespace(sys, only(arguments(x)))]; + Any[renamespace(sys, only(arguments(x)))], symtype(x), metadata(x))::T end if iscall(x) && operation(x) === getindex args = arguments(x) return maketerm( - typeof(x), operation(x), vcat(renamespace(sys, args[1]), args[2:end]); + typeof(x), operation(x), vcat(renamespace(sys, args[1]), args[2:end]), symtype(x), metadata(x))::T end let scope = getmetadata(x, SymScope, LocalScope()) @@ -914,8 +914,8 @@ function namespace_expr( # metadata from the rescoped variable rescoped = renamespace(n, O) maketerm(typeof(rescoped), operation(rescoped), renamed, - symtype(rescoped) - metadata = metadata(rescoped)) + symtype(rescoped), + metadata(rescoped)) elseif Symbolics.isarraysymbolic(O) # promote_symtype doesn't work for array symbolics maketerm(typeof(O), operation(O), renamed, symtype(O), metadata(O)) diff --git a/src/systems/diffeqs/abstractodesystem.jl b/src/systems/diffeqs/abstractodesystem.jl index 13af9f6b3d..8cdef1ce6d 100644 --- a/src/systems/diffeqs/abstractodesystem.jl +++ b/src/systems/diffeqs/abstractodesystem.jl @@ -232,7 +232,7 @@ function delay_to_function(expr, iv, sts, ps, h) elseif iscall(expr) return maketerm(typeof(expr), operation(expr), - map(x -> delay_to_function(x, iv, sts, ps, h), arguments(expr)); + map(x -> delay_to_function(x, iv, sts, ps, h), arguments(expr)), symtype(expr), metadata(expr)) else return expr From 71e81cc4bc5077845e51b084a633bf4c427f8f5a Mon Sep 17 00:00:00 2001 From: Fredrik Bagge Carlson Date: Wed, 5 Jun 2024 13:08:59 +0200 Subject: [PATCH 119/316] error for hybrid continuous/discrete systems --- src/systems/systemstructure.jl | 3 +++ test/runtests.jl | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/systems/systemstructure.jl b/src/systems/systemstructure.jl index 1c5f3ca28b..04d25ef0c4 100644 --- a/src/systems/systemstructure.jl +++ b/src/systems/systemstructure.jl @@ -636,6 +636,9 @@ function structural_simplify!(state::TearingState, io = nothing; simplify = fals check_consistency, fully_determined, kwargs...) if length(tss) > 1 + if continuous_id > 0 + error("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 diff --git a/test/runtests.jl b/test/runtests.jl index cec0a4bc0b..39cfcdb71c 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -26,7 +26,7 @@ end @safetestset "Direct Usage Test" include("direct.jl") @safetestset "System Linearity Test" include("linearity.jl") @safetestset "Input Output Test" include("input_output_handling.jl") - @safetestset "Clock Test" include("clock.jl") + # @safetestset "Clock Test" include("clock.jl") @safetestset "ODESystem Test" include("odesystem.jl") @safetestset "Dynamic Quantities Test" include("dq_units.jl") @safetestset "Unitful Quantities Test" include("units.jl") From 03a7b0b209c5719327fdf542ad92dcb7206114a7 Mon Sep 17 00:00:00 2001 From: Fredrik Bagge Carlson Date: Wed, 5 Jun 2024 13:54:19 +0200 Subject: [PATCH 120/316] custom exception type --- src/systems/abstractsystem.jl | 7 + src/systems/systemstructure.jl | 2 +- test/clock.jl | 784 +++++++++++++++++---------------- test/runtests.jl | 2 +- 4 files changed, 402 insertions(+), 393 deletions(-) diff --git a/src/systems/abstractsystem.jl b/src/systems/abstractsystem.jl index 739451509b..7d73a834fa 100644 --- a/src/systems/abstractsystem.jl +++ b/src/systems/abstractsystem.jl @@ -2300,6 +2300,13 @@ function Base.showerror(io::IO, e::ExtraEquationsSystemException) print(io, "ExtraEquationsSystemException: ", e.msg) end +struct HybridSystemNotSupportedExcpetion <: Exception + msg::String +end +function Base.showerror(io::IO, e::HybridSystemNotSupportedExcpetion) + print(io, "HybridSystemNotSupportedExcpetion: ", e.msg) +end + function AbstractTrees.children(sys::ModelingToolkit.AbstractSystem) ModelingToolkit.get_systems(sys) end diff --git a/src/systems/systemstructure.jl b/src/systems/systemstructure.jl index 04d25ef0c4..801864a344 100644 --- a/src/systems/systemstructure.jl +++ b/src/systems/systemstructure.jl @@ -637,7 +637,7 @@ function structural_simplify!(state::TearingState, io = nothing; simplify = fals kwargs...) if length(tss) > 1 if continuous_id > 0 - error("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/") + throw(HybridSystemNotSupportedExcpetion("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)) diff --git a/test/clock.jl b/test/clock.jl index f847f94188..9429da1951 100644 --- a/test/clock.jl +++ b/test/clock.jl @@ -105,426 +105,428 @@ eqs = [yd ~ Sample(t, dt)(y) D(x) ~ -x + u y ~ x] @named sys = ODESystem(eqs, t) -ss = structural_simplify(sys); - -Tf = 1.0 -prob = ODEProblem(ss, [x => 0.1], (0.0, Tf), - [kp => 1.0; ud(k - 1) => 2.1; ud(k - 2) => 2.0]) -# create integrator so callback is evaluated at t=0 and we can test correct param values -int = init(prob, Tsit5(); kwargshandle = KeywordArgSilent) -@test sort(vcat(int.p...)) == [0.1, 1.0, 2.1, 2.1, 2.1] # yd, kp, ud(k-1), ud, Hold(ud) -prob = ODEProblem(ss, [x => 0.1], (0.0, Tf), - [kp => 1.0; ud(k - 1) => 2.1; ud(k - 2) => 2.0]) # recreate problem to empty saved values -sol = solve(prob, Tsit5(), kwargshandle = KeywordArgSilent) - -ss_nosplit = structural_simplify(sys; split = false) -prob_nosplit = ODEProblem(ss_nosplit, [x => 0.1], (0.0, Tf), - [kp => 1.0; ud(k - 1) => 2.1; ud(k - 2) => 2.0]) -int = init(prob_nosplit, Tsit5(); kwargshandle = KeywordArgSilent) -@test sort(int.p) == [0.1, 1.0, 2.1, 2.1, 2.1] # yd, kp, ud(k-1), ud, Hold(ud) -prob_nosplit = ODEProblem(ss_nosplit, [x => 0.1], (0.0, Tf), - [kp => 1.0; ud(k - 1) => 2.1; ud(k - 2) => 2.0]) # recreate problem to empty saved values -sol_nosplit = solve(prob_nosplit, Tsit5(), kwargshandle = KeywordArgSilent) -# For all inputs in parameters, just initialize them to 0.0, and then set them -# in the callback. - -# kp is the only real parameter -function foo!(du, u, p, t) - x = u[1] - ud = p[2] - du[1] = -x + ud -end -function affect!(integrator, saved_values) - yd = integrator.u[1] - kp = integrator.p[1] - ud = integrator.p[2] - udd = integrator.p[3] - - integrator.p[2] = kp * yd + udd - integrator.p[3] = ud - - push!(saved_values.t, integrator.t) - push!(saved_values.saveval, [integrator.p[2], integrator.p[3]]) - - nothing -end -saved_values = SavedValues(Float64, Vector{Float64}) -cb = PeriodicCallback( - Base.Fix2(affect!, saved_values), 0.1; final_affect = true, initial_affect = true) -# kp ud -prob = ODEProblem(foo!, [0.1], (0.0, Tf), [1.0, 2.1, 2.0], callback = cb) -sol2 = solve(prob, Tsit5()) -@test sol.u == sol2.u -@test sol_nosplit.u == sol2.u -@test saved_values.t == sol.prob.kwargs[:disc_saved_values][1].t -@test saved_values.t == sol_nosplit.prob.kwargs[:disc_saved_values][1].t -@test saved_values.saveval == sol.prob.kwargs[:disc_saved_values][1].saveval -@test saved_values.saveval == sol_nosplit.prob.kwargs[:disc_saved_values][1].saveval - -@info "Testing multi-rate hybrid system" -dt = 0.1 -dt2 = 0.2 -@variables x(t) y(t) u(t) r(t) yd1(t) ud1(t) yd2(t) ud2(t) -@parameters kp - -eqs = [ - # controller (time discrete part `dt=0.1`) - yd1 ~ Sample(t, dt)(y) - ud1 ~ kp * (Sample(t, dt)(r) - yd1) - yd2 ~ Sample(t, dt2)(y) - ud2 ~ kp * (Sample(t, dt2)(r) - yd2) - - # plant (time continuous part) - u ~ Hold(ud1) + Hold(ud2) - D(x) ~ -x + u - y ~ x] -@named sys = ODESystem(eqs, t) -ci, varmap = infer_clocks(sys) - -d = Clock(t, dt) -d2 = Clock(t, dt2) -#@test get_eq_domain(eqs[1]) == d -#@test get_eq_domain(eqs[3]) == d2 - -@test varmap[yd1] == d -@test varmap[ud1] == d -@test varmap[yd2] == d2 -@test varmap[ud2] == d2 -@test varmap[r] == Continuous() -@test varmap[x] == Continuous() -@test varmap[y] == Continuous() -@test varmap[u] == Continuous() - -@info "test composed systems" - -dt = 0.5 -d = Clock(t, dt) -k = ShiftIndex(d) -timevec = 0:0.1:4 - -function plant(; name) - @variables x(t)=1 u(t)=0 y(t)=0 - eqs = [D(x) ~ -x + u - y ~ x] - ODESystem(eqs, t; name = name) -end - -function filt(; name) - @variables x(t)=0 u(t)=0 y(t)=0 - a = 1 / exp(dt) - eqs = [x ~ a * x(k - 1) + (1 - a) * u(k - 1) - y ~ x] - ODESystem(eqs, t, name = name) -end - -function controller(kp; name) - @variables y(t)=0 r(t)=0 ud(t)=0 yd(t)=0 - @parameters kp = kp - eqs = [yd ~ Sample(y) - ud ~ kp * (r - yd)] - ODESystem(eqs, t; name = name) -end - -@named f = filt() -@named c = controller(1) -@named p = plant() - -connections = [f.u ~ -1#(t >= 1) # step input - f.y ~ c.r # filtered reference to controller reference - Hold(c.ud) ~ p.u # controller output to plant input - p.y ~ c.y] - -@named cl = ODESystem(connections, t, systems = [f, c, p]) - -ci, varmap = infer_clocks(cl) - -@test varmap[f.x] == Clock(t, 0.5) -@test varmap[p.x] == Continuous() -@test varmap[p.y] == Continuous() -@test varmap[c.ud] == Clock(t, 0.5) -@test varmap[c.yd] == Clock(t, 0.5) -@test varmap[c.y] == Continuous() -@test varmap[f.y] == Clock(t, 0.5) -@test varmap[f.u] == Clock(t, 0.5) -@test varmap[p.u] == Continuous() -@test varmap[c.r] == Clock(t, 0.5) +@test_throws ModelingToolkit.HybridSystemNotSupportedExcpetion ss=structural_simplify(sys); -## Multiple clock rates -@info "Testing multi-rate hybrid system" -dt = 0.1 -dt2 = 0.2 -@variables x(t)=0 y(t)=0 u(t)=0 yd1(t)=0 ud1(t)=0 yd2(t)=0 ud2(t)=0 -@parameters kp=1 r=1 - -eqs = [ - # controller (time discrete part `dt=0.1`) - yd1 ~ Sample(t, dt)(y) - ud1 ~ kp * (r - yd1) - # controller (time discrete part `dt=0.2`) - yd2 ~ Sample(t, dt2)(y) - ud2 ~ kp * (r - yd2) - - # plant (time continuous part) - u ~ Hold(ud1) + Hold(ud2) - D(x) ~ -x + u - y ~ x] - -@named cl = ODESystem(eqs, t) - -d = Clock(t, dt) -d2 = Clock(t, dt2) - -ci, varmap = infer_clocks(cl) -@test varmap[yd1] == d -@test varmap[ud1] == d -@test varmap[yd2] == d2 -@test varmap[ud2] == d2 -@test varmap[x] == Continuous() -@test varmap[y] == Continuous() -@test varmap[u] == Continuous() - -ss = structural_simplify(cl) -ss_nosplit = structural_simplify(cl; split = false) - -if VERSION >= v"1.7" - prob = ODEProblem(ss, [x => 0.0], (0.0, 1.0), [kp => 1.0]) - prob_nosplit = ODEProblem(ss_nosplit, [x => 0.0], (0.0, 1.0), [kp => 1.0]) +@test_skip begin + Tf = 1.0 + prob = ODEProblem(ss, [x => 0.1], (0.0, Tf), + [kp => 1.0; ud(k - 1) => 2.1; ud(k - 2) => 2.0]) + # create integrator so callback is evaluated at t=0 and we can test correct param values + int = init(prob, Tsit5(); kwargshandle = KeywordArgSilent) + @test sort(vcat(int.p...)) == [0.1, 1.0, 2.1, 2.1, 2.1] # yd, kp, ud(k-1), ud, Hold(ud) + prob = ODEProblem(ss, [x => 0.1], (0.0, Tf), + [kp => 1.0; ud(k - 1) => 2.1; ud(k - 2) => 2.0]) # recreate problem to empty saved values sol = solve(prob, Tsit5(), kwargshandle = KeywordArgSilent) - sol_nosplit = solve(prob_nosplit, Tsit5(), kwargshandle = KeywordArgSilent) - - function foo!(dx, x, p, t) - kp, ud1, ud2 = p - dx[1] = -x[1] + ud1 + ud2 - end - function affect1!(integrator) - kp = integrator.p[1] - y = integrator.u[1] - r = 1.0 - ud1 = kp * (r - y) - integrator.p[2] = ud1 - nothing + ss_nosplit = structural_simplify(sys; split = false) + prob_nosplit = ODEProblem(ss_nosplit, [x => 0.1], (0.0, Tf), + [kp => 1.0; ud(k - 1) => 2.1; ud(k - 2) => 2.0]) + int = init(prob_nosplit, Tsit5(); kwargshandle = KeywordArgSilent) + @test sort(int.p) == [0.1, 1.0, 2.1, 2.1, 2.1] # yd, kp, ud(k-1), ud, Hold(ud) + prob_nosplit = ODEProblem(ss_nosplit, [x => 0.1], (0.0, Tf), + [kp => 1.0; ud(k - 1) => 2.1; ud(k - 2) => 2.0]) # recreate problem to empty saved values + sol_nosplit = solve(prob_nosplit, Tsit5(), kwargshandle = KeywordArgSilent) + # For all inputs in parameters, just initialize them to 0.0, and then set them + # in the callback. + + # kp is the only real parameter + function foo!(du, u, p, t) + x = u[1] + ud = p[2] + du[1] = -x + ud end - function affect2!(integrator) + function affect!(integrator, saved_values) + yd = integrator.u[1] kp = integrator.p[1] - y = integrator.u[1] - r = 1.0 - ud2 = kp * (r - y) - integrator.p[3] = ud2 - nothing - end - cb1 = PeriodicCallback(affect1!, dt; final_affect = true, initial_affect = true) - cb2 = PeriodicCallback(affect2!, dt2; final_affect = true, initial_affect = true) - cb = CallbackSet(cb1, cb2) - # kp ud1 ud2 - prob = ODEProblem(foo!, [0.0], (0.0, 1.0), [1.0, 1.0, 1.0], callback = cb) - sol2 = solve(prob, Tsit5()) + ud = integrator.p[2] + udd = integrator.p[3] - @test sol.u≈sol2.u atol=1e-6 - @test sol_nosplit.u≈sol2.u atol=1e-6 -end - -## -@info "Testing hybrid system with components" -using ModelingToolkitStandardLibrary.Blocks + integrator.p[2] = kp * yd + udd + integrator.p[3] = ud -dt = 0.05 -d = Clock(t, dt) -k = ShiftIndex() + push!(saved_values.t, integrator.t) + push!(saved_values.saveval, [integrator.p[2], integrator.p[3]]) -@mtkmodel DiscretePI begin - @components begin - input = RealInput() - output = RealOutput() - end - @parameters begin - kp = 1, [description = "Proportional gain"] - ki = 1, [description = "Integral gain"] + nothing end - @variables begin - x(t) = 0, [description = "Integral state"] - u(t) - y(t) + saved_values = SavedValues(Float64, Vector{Float64}) + cb = PeriodicCallback( + Base.Fix2(affect!, saved_values), 0.1; final_affect = true, initial_affect = true) + # kp ud + prob = ODEProblem(foo!, [0.1], (0.0, Tf), [1.0, 2.1, 2.0], callback = cb) + sol2 = solve(prob, Tsit5()) + @test sol.u == sol2.u + @test sol_nosplit.u == sol2.u + @test saved_values.t == sol.prob.kwargs[:disc_saved_values][1].t + @test saved_values.t == sol_nosplit.prob.kwargs[:disc_saved_values][1].t + @test saved_values.saveval == sol.prob.kwargs[:disc_saved_values][1].saveval + @test saved_values.saveval == sol_nosplit.prob.kwargs[:disc_saved_values][1].saveval + + @info "Testing multi-rate hybrid system" + dt = 0.1 + dt2 = 0.2 + @variables x(t) y(t) u(t) r(t) yd1(t) ud1(t) yd2(t) ud2(t) + @parameters kp + + eqs = [ + # controller (time discrete part `dt=0.1`) + yd1 ~ Sample(t, dt)(y) + ud1 ~ kp * (Sample(t, dt)(r) - yd1) + yd2 ~ Sample(t, dt2)(y) + ud2 ~ kp * (Sample(t, dt2)(r) - yd2) + + # plant (time continuous part) + u ~ Hold(ud1) + Hold(ud2) + D(x) ~ -x + u + y ~ x] + @named sys = ODESystem(eqs, t) + ci, varmap = infer_clocks(sys) + + d = Clock(t, dt) + d2 = Clock(t, dt2) + #@test get_eq_domain(eqs[1]) == d + #@test get_eq_domain(eqs[3]) == d2 + + @test varmap[yd1] == d + @test varmap[ud1] == d + @test varmap[yd2] == d2 + @test varmap[ud2] == d2 + @test varmap[r] == Continuous() + @test varmap[x] == Continuous() + @test varmap[y] == Continuous() + @test varmap[u] == Continuous() + + @info "test composed systems" + + dt = 0.5 + d = Clock(t, dt) + k = ShiftIndex(d) + timevec = 0:0.1:4 + + function plant(; name) + @variables x(t)=1 u(t)=0 y(t)=0 + eqs = [D(x) ~ -x + u + y ~ x] + ODESystem(eqs, t; name = name) end - @equations begin - x(k) ~ x(k - 1) + ki * u(k) * SampleTime() / dt - output.u(k) ~ y(k) - input.u(k) ~ u(k) - y(k) ~ x(k - 1) + kp * u(k) + + function filt(; name) + @variables x(t)=0 u(t)=0 y(t)=0 + a = 1 / exp(dt) + eqs = [x ~ a * x(k - 1) + (1 - a) * u(k - 1) + y ~ x] + ODESystem(eqs, t, name = name) end -end -@mtkmodel Sampler begin - @components begin - input = RealInput() - output = RealOutput() + function controller(kp; name) + @variables y(t)=0 r(t)=0 ud(t)=0 yd(t)=0 + @parameters kp = kp + eqs = [yd ~ Sample(y) + ud ~ kp * (r - yd)] + ODESystem(eqs, t; name = name) end - @equations begin - output.u ~ Sample(t, dt)(input.u) + + @named f = filt() + @named c = controller(1) + @named p = plant() + + connections = [f.u ~ -1#(t >= 1) # step input + f.y ~ c.r # filtered reference to controller reference + Hold(c.ud) ~ p.u # controller output to plant input + p.y ~ c.y] + + @named cl = ODESystem(connections, t, systems = [f, c, p]) + + ci, varmap = infer_clocks(cl) + + @test varmap[f.x] == Clock(t, 0.5) + @test varmap[p.x] == Continuous() + @test varmap[p.y] == Continuous() + @test varmap[c.ud] == Clock(t, 0.5) + @test varmap[c.yd] == Clock(t, 0.5) + @test varmap[c.y] == Continuous() + @test varmap[f.y] == Clock(t, 0.5) + @test varmap[f.u] == Clock(t, 0.5) + @test varmap[p.u] == Continuous() + @test varmap[c.r] == Clock(t, 0.5) + + ## Multiple clock rates + @info "Testing multi-rate hybrid system" + dt = 0.1 + dt2 = 0.2 + @variables x(t)=0 y(t)=0 u(t)=0 yd1(t)=0 ud1(t)=0 yd2(t)=0 ud2(t)=0 + @parameters kp=1 r=1 + + eqs = [ + # controller (time discrete part `dt=0.1`) + yd1 ~ Sample(t, dt)(y) + ud1 ~ kp * (r - yd1) + # controller (time discrete part `dt=0.2`) + yd2 ~ Sample(t, dt2)(y) + ud2 ~ kp * (r - yd2) + + # plant (time continuous part) + u ~ Hold(ud1) + Hold(ud2) + D(x) ~ -x + u + y ~ x] + + @named cl = ODESystem(eqs, t) + + d = Clock(t, dt) + d2 = Clock(t, dt2) + + ci, varmap = infer_clocks(cl) + @test varmap[yd1] == d + @test varmap[ud1] == d + @test varmap[yd2] == d2 + @test varmap[ud2] == d2 + @test varmap[x] == Continuous() + @test varmap[y] == Continuous() + @test varmap[u] == Continuous() + + ss = structural_simplify(cl) + ss_nosplit = structural_simplify(cl; split = false) + + if VERSION >= v"1.7" + prob = ODEProblem(ss, [x => 0.0], (0.0, 1.0), [kp => 1.0]) + prob_nosplit = ODEProblem(ss_nosplit, [x => 0.0], (0.0, 1.0), [kp => 1.0]) + sol = solve(prob, Tsit5(), kwargshandle = KeywordArgSilent) + sol_nosplit = solve(prob_nosplit, Tsit5(), kwargshandle = KeywordArgSilent) + + function foo!(dx, x, p, t) + kp, ud1, ud2 = p + dx[1] = -x[1] + ud1 + ud2 + end + + function affect1!(integrator) + kp = integrator.p[1] + y = integrator.u[1] + r = 1.0 + ud1 = kp * (r - y) + integrator.p[2] = ud1 + nothing + end + function affect2!(integrator) + kp = integrator.p[1] + y = integrator.u[1] + r = 1.0 + ud2 = kp * (r - y) + integrator.p[3] = ud2 + nothing + end + cb1 = PeriodicCallback(affect1!, dt; final_affect = true, initial_affect = true) + cb2 = PeriodicCallback(affect2!, dt2; final_affect = true, initial_affect = true) + cb = CallbackSet(cb1, cb2) + # kp ud1 ud2 + prob = ODEProblem(foo!, [0.0], (0.0, 1.0), [1.0, 1.0, 1.0], callback = cb) + sol2 = solve(prob, Tsit5()) + + @test sol.u≈sol2.u atol=1e-6 + @test sol_nosplit.u≈sol2.u atol=1e-6 end -end -@mtkmodel ZeroOrderHold begin - @extend u, y = siso = Blocks.SISO() - @equations begin - y ~ Hold(u) + ## + @info "Testing hybrid system with components" + using ModelingToolkitStandardLibrary.Blocks + + dt = 0.05 + d = Clock(t, dt) + k = ShiftIndex() + + @mtkmodel DiscretePI begin + @components begin + input = RealInput() + output = RealOutput() + end + @parameters begin + kp = 1, [description = "Proportional gain"] + ki = 1, [description = "Integral gain"] + end + @variables begin + x(t) = 0, [description = "Integral state"] + u(t) + y(t) + end + @equations begin + x(k) ~ x(k - 1) + ki * u(k) * SampleTime() / dt + output.u(k) ~ y(k) + input.u(k) ~ u(k) + y(k) ~ x(k - 1) + kp * u(k) + end end -end -@mtkmodel ClosedLoop begin - @components begin - plant = FirstOrder(k = 0.3, T = 1) - sampler = Sampler() - holder = ZeroOrderHold() - controller = DiscretePI(kp = 2, ki = 2) - feedback = Feedback() - ref = Constant(k = 0.5) + @mtkmodel Sampler begin + @components begin + input = RealInput() + output = RealOutput() + end + @equations begin + output.u ~ Sample(t, dt)(input.u) + end end - @equations begin - connect(ref.output, feedback.input1) - connect(feedback.output, controller.input) - connect(controller.output, holder.input) - connect(holder.output, plant.input) - connect(plant.output, sampler.input) - connect(sampler.output, feedback.input2) + + @mtkmodel ZeroOrderHold begin + @extend u, y = siso = Blocks.SISO() + @equations begin + y ~ Hold(u) + end end -end -## -@named model = ClosedLoop() -_model = complete(model) - -ci, varmap = infer_clocks(expand_connections(_model)) - -@test varmap[_model.plant.input.u] == Continuous() -@test varmap[_model.plant.u] == Continuous() -@test varmap[_model.plant.x] == Continuous() -@test varmap[_model.plant.y] == Continuous() -@test varmap[_model.plant.output.u] == Continuous() -@test varmap[_model.holder.output.u] == Continuous() -@test varmap[_model.sampler.input.u] == Continuous() -@test varmap[_model.controller.u] == d -@test varmap[_model.holder.input.u] == d -@test varmap[_model.controller.output.u] == d -@test varmap[_model.controller.y] == d -@test varmap[_model.feedback.input1.u] == d -@test varmap[_model.ref.output.u] == d -@test varmap[_model.controller.input.u] == d -@test varmap[_model.controller.x] == d -@test varmap[_model.sampler.output.u] == d -@test varmap[_model.feedback.output.u] == d -@test varmap[_model.feedback.input2.u] == d - -ssys = structural_simplify(model) - -Tf = 0.2 -timevec = 0:(d.dt):Tf - -import ControlSystemsBase as CS -import ControlSystemsBase: c2d, tf, feedback, lsim -# z = tf('z', d.dt) -# P = c2d(tf(0.3, [1, 1]), d.dt) -P = c2d(CS.ss([-1], [0.3], [1], 0), d.dt) -C = CS.ss([1], [2], [1], [2], d.dt) - -# Test the output of the continuous partition -G = feedback(P * C) -res = lsim(G, (x, t) -> [0.5], timevec) -y = res.y[:] - -# plant = FirstOrder(k = 0.3, T = 1) -# controller = DiscretePI(kp = 2, ki = 2) -# ref = Constant(k = 0.5) - -# ; model.controller.x(k-1) => 0.0 -prob = ODEProblem(ssys, - [model.plant.x => 0.0; model.controller.kp => 2.0; model.controller.ki => 2.0], - (0.0, Tf)) -int = init(prob, Tsit5(); kwargshandle = KeywordArgSilent) -@test_broken int.ps[Hold(ssys.holder.input.u)] == 2 # constant output * kp issue https://github.com/SciML/ModelingToolkit.jl/issues/2356 -@test int.ps[ssys.controller.x] == 1 # c2d -@test int.ps[Sample(d)(ssys.sampler.input.u)] == 0 # disc state -sol = solve(prob, - Tsit5(), - kwargshandle = KeywordArgSilent, - abstol = 1e-8, - reltol = 1e-8) -@test_skip begin - # plot([y sol(timevec, idxs = model.plant.output.u)], m = :o, lab = ["CS" "MTK"]) + @mtkmodel ClosedLoop begin + @components begin + plant = FirstOrder(k = 0.3, T = 1) + sampler = Sampler() + holder = ZeroOrderHold() + controller = DiscretePI(kp = 2, ki = 2) + feedback = Feedback() + ref = Constant(k = 0.5) + end + @equations begin + connect(ref.output, feedback.input1) + connect(feedback.output, controller.input) + connect(controller.output, holder.input) + connect(holder.output, plant.input) + connect(plant.output, sampler.input) + connect(sampler.output, feedback.input2) + end + end ## - - @test sol(timevec, idxs = model.plant.output.u)≈y rtol=1e-8 # The output of the continuous partition is delayed exactly one sample - - # Test the output of the discrete partition - G = feedback(C, P) + @named model = ClosedLoop() + _model = complete(model) + + ci, varmap = infer_clocks(expand_connections(_model)) + + @test varmap[_model.plant.input.u] == Continuous() + @test varmap[_model.plant.u] == Continuous() + @test varmap[_model.plant.x] == Continuous() + @test varmap[_model.plant.y] == Continuous() + @test varmap[_model.plant.output.u] == Continuous() + @test varmap[_model.holder.output.u] == Continuous() + @test varmap[_model.sampler.input.u] == Continuous() + @test varmap[_model.controller.u] == d + @test varmap[_model.holder.input.u] == d + @test varmap[_model.controller.output.u] == d + @test varmap[_model.controller.y] == d + @test varmap[_model.feedback.input1.u] == d + @test varmap[_model.ref.output.u] == d + @test varmap[_model.controller.input.u] == d + @test varmap[_model.controller.x] == d + @test varmap[_model.sampler.output.u] == d + @test varmap[_model.feedback.output.u] == d + @test varmap[_model.feedback.input2.u] == d + + ssys = structural_simplify(model) + + Tf = 0.2 + timevec = 0:(d.dt):Tf + + import ControlSystemsBase as CS + import ControlSystemsBase: c2d, tf, feedback, lsim + # z = tf('z', d.dt) + # P = c2d(tf(0.3, [1, 1]), d.dt) + P = c2d(CS.ss([-1], [0.3], [1], 0), d.dt) + C = CS.ss([1], [2], [1], [2], d.dt) + + # Test the output of the continuous partition + G = feedback(P * C) res = lsim(G, (x, t) -> [0.5], timevec) y = res.y[:] - @test_broken sol(timevec .+ 1e-10, idxs = model.controller.output.u)≈y rtol=1e-8 # Broken due to discrete observed - # plot([y sol(timevec .+ 1e-12, idxs=model.controller.output.u)], lab=["CS" "MTK"]) - - # TODO: test the same system, but with the PI controller implemented as - # x(k) ~ x(k-1) + ki * u - # y ~ x(k-1) + kp * u - # Instead. This should be equivalent to the above, but gve me an error when I tried -end - -## Test continuous clock - -c = ModelingToolkit.SolverStepClock(t) -k = ShiftIndex(c) - -@mtkmodel CounterSys begin - @variables begin - count(t) = 0 - u(t) = 0 - ud(t) = 0 - end - @equations begin - ud ~ Sample(c)(u) - count ~ ud(k - 1) + # plant = FirstOrder(k = 0.3, T = 1) + # controller = DiscretePI(kp = 2, ki = 2) + # ref = Constant(k = 0.5) + + # ; model.controller.x(k-1) => 0.0 + prob = ODEProblem(ssys, + [model.plant.x => 0.0; model.controller.kp => 2.0; model.controller.ki => 2.0], + (0.0, Tf)) + int = init(prob, Tsit5(); kwargshandle = KeywordArgSilent) + @test_broken int.ps[Hold(ssys.holder.input.u)] == 2 # constant output * kp issue https://github.com/SciML/ModelingToolkit.jl/issues/2356 + @test int.ps[ssys.controller.x] == 1 # c2d + @test int.ps[Sample(d)(ssys.sampler.input.u)] == 0 # disc state + sol = solve(prob, + Tsit5(), + kwargshandle = KeywordArgSilent, + abstol = 1e-8, + reltol = 1e-8) + @test_skip begin + # plot([y sol(timevec, idxs = model.plant.output.u)], m = :o, lab = ["CS" "MTK"]) + + ## + + @test sol(timevec, idxs = model.plant.output.u)≈y rtol=1e-8 # The output of the continuous partition is delayed exactly one sample + + # Test the output of the discrete partition + G = feedback(C, P) + res = lsim(G, (x, t) -> [0.5], timevec) + y = res.y[:] + + @test_broken sol(timevec .+ 1e-10, idxs = model.controller.output.u)≈y rtol=1e-8 # Broken due to discrete observed + # plot([y sol(timevec .+ 1e-12, idxs=model.controller.output.u)], lab=["CS" "MTK"]) + + # TODO: test the same system, but with the PI controller implemented as + # x(k) ~ x(k-1) + ki * u + # y ~ x(k-1) + kp * u + # Instead. This should be equivalent to the above, but gve me an error when I tried end -end -@mtkmodel FirstOrderSys begin - @variables begin - x(t) = 0 - end - @equations begin - D(x) ~ -x + sin(t) + ## Test continuous clock + + c = ModelingToolkit.SolverStepClock(t) + k = ShiftIndex(c) + + @mtkmodel CounterSys begin + @variables begin + count(t) = 0 + u(t) = 0 + ud(t) = 0 + end + @equations begin + ud ~ Sample(c)(u) + count ~ ud(k - 1) + end end -end -@mtkmodel FirstOrderWithStepCounter begin - @components begin - counter = CounterSys() - fo = FirstOrderSys() + @mtkmodel FirstOrderSys begin + @variables begin + x(t) = 0 + end + @equations begin + D(x) ~ -x + sin(t) + end end - @equations begin - counter.u ~ fo.x + + @mtkmodel FirstOrderWithStepCounter begin + @components begin + counter = CounterSys() + fo = FirstOrderSys() + end + @equations begin + counter.u ~ fo.x + end end -end -@mtkbuild model = FirstOrderWithStepCounter() -prob = ODEProblem(model, [], (0.0, 10.0)) -sol = solve(prob, Tsit5(), kwargshandle = KeywordArgSilent) - -@test sol.prob.kwargs[:disc_saved_values][1].t == sol.t[1:2:end] # Test that the discrete-tiem system executed at every step of the continuous solver. The solver saves each time step twice, one state value before discrete affect and one after. -@test_nowarn ModelingToolkit.build_explicit_observed_function( - model, model.counter.ud)(sol.u[1], prob.p..., sol.t[1]) - -@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) -prob = ODEProblem(sys, [], (0.0, 10.0)) -int = init(prob, Tsit5(); kwargshandle = KeywordArgSilent) -@test int.ps[x] == 2.0 -@test int.ps[x(k - 1)] == 1.0 - -@test_throws ErrorException ODEProblem(sys, [], (0.0, 10.0), [x => 2.0]) -prob = ODEProblem(sys, [], (0.0, 10.0), [x(k - 1) => 2.0]) -int = init(prob, Tsit5(); kwargshandle = KeywordArgSilent) -@test int.ps[x] == 3.0 -@test int.ps[x(k - 1)] == 2.0 + @mtkbuild model = FirstOrderWithStepCounter() + prob = ODEProblem(model, [], (0.0, 10.0)) + sol = solve(prob, Tsit5(), kwargshandle = KeywordArgSilent) + + @test sol.prob.kwargs[:disc_saved_values][1].t == sol.t[1:2:end] # Test that the discrete-tiem system executed at every step of the continuous solver. The solver saves each time step twice, one state value before discrete affect and one after. + @test_nowarn ModelingToolkit.build_explicit_observed_function( + model, model.counter.ud)(sol.u[1], prob.p..., sol.t[1]) + + @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) + prob = ODEProblem(sys, [], (0.0, 10.0)) + int = init(prob, Tsit5(); kwargshandle = KeywordArgSilent) + @test int.ps[x] == 2.0 + @test int.ps[x(k - 1)] == 1.0 + + @test_throws ErrorException ODEProblem(sys, [], (0.0, 10.0), [x => 2.0]) + prob = ODEProblem(sys, [], (0.0, 10.0), [x(k - 1) => 2.0]) + int = init(prob, Tsit5(); kwargshandle = KeywordArgSilent) + @test int.ps[x] == 3.0 + @test int.ps[x(k - 1)] == 2.0 +end diff --git a/test/runtests.jl b/test/runtests.jl index 39cfcdb71c..cec0a4bc0b 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -26,7 +26,7 @@ end @safetestset "Direct Usage Test" include("direct.jl") @safetestset "System Linearity Test" include("linearity.jl") @safetestset "Input Output Test" include("input_output_handling.jl") - # @safetestset "Clock Test" include("clock.jl") + @safetestset "Clock Test" include("clock.jl") @safetestset "ODESystem Test" include("odesystem.jl") @safetestset "Dynamic Quantities Test" include("dq_units.jl") @safetestset "Unitful Quantities Test" include("units.jl") From 7f29017e3aa1ef23c61b435a9aab587178047bf7 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Wed, 5 Jun 2024 17:28:21 +0530 Subject: [PATCH 121/316] fix: error when simplified discrete system contains algebraic equations --- src/systems/systems.jl | 7 +++++++ test/discrete_system.jl | 6 ++++++ 2 files changed, 13 insertions(+) diff --git a/src/systems/systems.jl b/src/systems/systems.jl index 96e2043625..124ffdfa8b 100644 --- a/src/systems/systems.jl +++ b/src/systems/systems.jl @@ -26,6 +26,13 @@ function structural_simplify( else newsys = newsys′ end + if newsys isa DiscreteSystem && + any(eq -> symbolic_type(eq.lhs) == NotSymbolic(), equations(newsys)) + error(""" + Encountered algebraic equations when simplifying discrete system. This is \ + not yet supported. + """) + end if newsys isa ODESystem || has_parent(newsys) @set! newsys.parent = complete(sys; split) end diff --git a/test/discrete_system.jl b/test/discrete_system.jl index ebbb80770e..f8ed0a911f 100644 --- a/test/discrete_system.jl +++ b/test/discrete_system.jl @@ -265,3 +265,9 @@ function System(; name, buffer) end @test_nowarn @mtkbuild sys = System(; buffer = ones(10)) + +# 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) +@test_throws ["algebraic equations", "not yet supported"] structural_simplify(sys) From ea4d2fcc8f8cba9378a97ffaea57ae705ef3280f Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Thu, 18 Apr 2024 13:58:14 +0530 Subject: [PATCH 122/316] feat: add parameter type and size validation in remake_buffer and setp --- src/systems/index_cache.jl | 57 ++++++++++---------- src/systems/parameter_buffer.jl | 95 +++++++++++++++++++++++++++++++-- test/index_cache.jl | 45 ++++++++++++++++ test/mtkparameters.jl | 42 +++++++++++++++ test/runtests.jl | 1 + 5 files changed, 206 insertions(+), 34 deletions(-) create mode 100644 test/index_cache.jl diff --git a/src/systems/index_cache.jl b/src/systems/index_cache.jl index 07bef4b450..7b94aed0d3 100644 --- a/src/systems/index_cache.jl +++ b/src/systems/index_cache.jl @@ -1,5 +1,5 @@ struct BufferTemplate - type::DataType + type::Union{DataType, UnionAll} length::Int end @@ -16,8 +16,11 @@ const NONNUMERIC_PORTION = Nonnumeric() struct ParameterIndex{P, I} portion::P idx::I + validate_size::Bool end +ParameterIndex(portion, idx) = ParameterIndex(portion, idx, false) + const ParamIndexMap = Dict{Union{Symbol, BasicSymbolic}, Tuple{Int, Int}} const UnknownIndexMap = Dict{ Union{Symbol, BasicSymbolic}, Union{Int, UnitRange{Int}, AbstractArray{Int}}} @@ -34,11 +37,14 @@ struct IndexCache constant_buffer_sizes::Vector{BufferTemplate} dependent_buffer_sizes::Vector{BufferTemplate} nonnumeric_buffer_sizes::Vector{BufferTemplate} + symbol_to_variable::Dict{Symbol, BasicSymbolic} end function IndexCache(sys::AbstractSystem) unks = solved_unknowns(sys) unk_idxs = UnknownIndexMap() + symbol_to_variable = Dict{Symbol, BasicSymbolic}() + let idx = 1 for sym in unks usym = unwrap(sym) @@ -50,7 +56,9 @@ function IndexCache(sys::AbstractSystem) unk_idxs[usym] = sym_idx if hasname(sym) && (!istree(sym) || operation(sym) !== getindex) - unk_idxs[getname(usym)] = sym_idx + name = getname(usym) + unk_idxs[name] = sym_idx + symbol_to_variable[name] = sym end idx += length(sym) end @@ -66,7 +74,9 @@ function IndexCache(sys::AbstractSystem) end unk_idxs[arrsym] = idxs if hasname(arrsym) - unk_idxs[getname(arrsym)] = idxs + name = getname(arrsym) + unk_idxs[name] = idxs + symbol_to_variable[name] = arrsym end end end @@ -144,14 +154,15 @@ function IndexCache(sys::AbstractSystem) idxs[default_toterm(p)] = (i, j) if hasname(p) && (!istree(p) || operation(p) !== getindex) idxs[getname(p)] = (i, j) + symbol_to_variable[getname(p)] = p idxs[getname(default_toterm(p))] = (i, j) + symbol_to_variable[getname(default_toterm(p))] = p end end push!(buffer_sizes, BufferTemplate(T, length(buf))) end return idxs, buffer_sizes end - disc_idxs, discrete_buffer_sizes = get_buffer_sizes_and_idxs(disc_buffers) tunable_idxs, tunable_buffer_sizes = get_buffer_sizes_and_idxs(tunable_buffers) const_idxs, const_buffer_sizes = get_buffer_sizes_and_idxs(constant_buffers) @@ -169,7 +180,8 @@ function IndexCache(sys::AbstractSystem) tunable_buffer_sizes, const_buffer_sizes, dependent_buffer_sizes, - nonnumeric_buffer_sizes + nonnumeric_buffer_sizes, + symbol_to_variable ) end @@ -190,16 +202,21 @@ function SymbolicIndexingInterface.is_parameter(ic::IndexCache, sym) end function SymbolicIndexingInterface.parameter_index(ic::IndexCache, sym) + if sym isa Symbol + sym = ic.symbol_to_variable[sym] + end + validate_size = Symbolics.isarraysymbolic(sym) && + Symbolics.shape(sym) !== Symbolics.Unknown() return if (idx = check_index_map(ic.tunable_idx, sym)) !== nothing - ParameterIndex(SciMLStructures.Tunable(), idx) + ParameterIndex(SciMLStructures.Tunable(), idx, validate_size) elseif (idx = check_index_map(ic.discrete_idx, sym)) !== nothing - ParameterIndex(SciMLStructures.Discrete(), idx) + ParameterIndex(SciMLStructures.Discrete(), idx, validate_size) elseif (idx = check_index_map(ic.constant_idx, sym)) !== nothing - ParameterIndex(SciMLStructures.Constants(), idx) + ParameterIndex(SciMLStructures.Constants(), idx, validate_size) elseif (idx = check_index_map(ic.nonnumeric_idx, sym)) !== nothing - ParameterIndex(NONNUMERIC_PORTION, idx) + ParameterIndex(NONNUMERIC_PORTION, idx, validate_size) elseif (idx = check_index_map(ic.dependent_idx, sym)) !== nothing - ParameterIndex(DEPENDENT_PORTION, idx) + ParameterIndex(DEPENDENT_PORTION, idx, validate_size) else nothing end @@ -224,26 +241,6 @@ function check_index_map(idxmap, sym) end end -function ParameterIndex(ic::IndexCache, p, sub_idx = ()) - p = unwrap(p) - return if haskey(ic.tunable_idx, p) - ParameterIndex(SciMLStructures.Tunable(), (ic.tunable_idx[p]..., sub_idx...)) - elseif haskey(ic.discrete_idx, p) - ParameterIndex(SciMLStructures.Discrete(), (ic.discrete_idx[p]..., sub_idx...)) - elseif haskey(ic.constant_idx, p) - ParameterIndex(SciMLStructures.Constants(), (ic.constant_idx[p]..., sub_idx...)) - elseif haskey(ic.dependent_idx, p) - ParameterIndex(DEPENDENT_PORTION, (ic.dependent_idx[p]..., sub_idx...)) - elseif haskey(ic.nonnumeric_idx, p) - ParameterIndex(NONNUMERIC_PORTION, (ic.nonnumeric_idx[p]..., sub_idx...)) - elseif istree(p) && operation(p) === getindex - _p, sub_idx... = arguments(p) - ParameterIndex(ic, _p, sub_idx) - else - nothing - end -end - function discrete_linear_index(ic::IndexCache, idx::ParameterIndex) idx.portion isa SciMLStructures.Discrete || error("Discrete variable index expected") ind = sum(temp.length for temp in ic.tunable_buffer_sizes; init = 0) diff --git a/src/systems/parameter_buffer.jl b/src/systems/parameter_buffer.jl index 52f5271d96..fed1bbb549 100644 --- a/src/systems/parameter_buffer.jl +++ b/src/systems/parameter_buffer.jl @@ -132,7 +132,8 @@ function MTKParameters( tunable_buffer = narrow_buffer_type.(tunable_buffer) disc_buffer = narrow_buffer_type.(disc_buffer) const_buffer = narrow_buffer_type.(const_buffer) - nonnumeric_buffer = narrow_buffer_type.(nonnumeric_buffer) + # Don't narrow nonnumeric types + nonnumeric_buffer = nonnumeric_buffer if has_parameter_dependencies(sys) && (pdeps = get_parameter_dependencies(sys)) !== nothing @@ -308,22 +309,31 @@ end function SymbolicIndexingInterface.set_parameter!( p::MTKParameters, val, idx::ParameterIndex) - @unpack portion, idx = idx + @unpack portion, idx, validate_size = idx i, j, k... = idx if portion isa SciMLStructures.Tunable if isempty(k) + if validate_size && size(val) !== size(p.tunable[i][j]) + throw(InvalidParameterSizeException(size(p.tunable[i][j]), size(val))) + end p.tunable[i][j] = val else p.tunable[i][j][k...] = val end elseif portion isa SciMLStructures.Discrete if isempty(k) + if validate_size && size(val) !== size(p.discrete[i][j]) + throw(InvalidParameterSizeException(size(p.discrete[i][j]), size(val))) + end p.discrete[i][j] = val else p.discrete[i][j][k...] = val end elseif portion isa SciMLStructures.Constants if isempty(k) + if validate_size && size(val) !== size(p.constant[i][j]) + throw(InvalidParameterSizeException(size(p.constant[i][j]), size(val))) + end p.constant[i][j] = val else p.constant[i][j][k...] = val @@ -392,6 +402,9 @@ function narrow_buffer_type_and_fallback_undefs(oldbuf::Vector, newbuf::Vector) isassigned(newbuf, i) || continue type = promote_type(type, typeof(newbuf[i])) end + if type == Union{} + type = eltype(oldbuf) + end for i in eachindex(newbuf) isassigned(newbuf, i) && continue newbuf[i] = convert(type, oldbuf[i]) @@ -399,7 +412,63 @@ function narrow_buffer_type_and_fallback_undefs(oldbuf::Vector, newbuf::Vector) return convert(Vector{type}, newbuf) end -function SymbolicIndexingInterface.remake_buffer(sys, oldbuf::MTKParameters, vals::Dict) +function validate_parameter_type(ic::IndexCache, p, index, val) + p = unwrap(p) + if p isa Symbol + p = get(ic.symbol_to_variable, p, nothing) + if p === nothing + @warn "No matching variable found for `Symbol` $p, skipping type validation." + return nothing + end + end + (; portion) = index + # Nonnumeric parameters have to match the type + if portion === NONNUMERIC_PORTION + stype = symtype(p) + val isa stype && return nothing + throw(ParameterTypeException(:validate_parameter_type, p, stype, val)) + end + stype = symtype(p) + # Array parameters need array values... + if stype <: AbstractArray && !isa(val, AbstractArray) + throw(ParameterTypeException(:validate_parameter_type, p, stype, val)) + end + # ... and must match sizes + if stype <: AbstractArray && Symbolics.shape(p) !== Symbolics.Unknown() && + size(val) != size(p) + throw(InvalidParameterSizeException(p, val)) + end + # Early exit + val isa stype && return nothing + if stype <: AbstractArray + # Arrays need handling when eltype is `Real` (accept any real array) + etype = eltype(stype) + if etype <: Real + etype = Real + end + # This is for duals and other complicated number types + etype = SciMLBase.parameterless_type(etype) + eltype(val) <: etype || throw(ParameterTypeException( + :validate_parameter_type, p, AbstractArray{etype}, val)) + else + # Real check + if stype <: Real + stype = Real + end + stype = SciMLBase.parameterless_type(stype) + val isa stype || + throw(ParameterTypeException(:validate_parameter_type, p, stype, val)) + end +end + +function indp_to_system(indp) + while hasmethod(symbolic_container, Tuple{typeof(indp)}) + indp = symbolic_container(indp) + end + return indp +end + +function SymbolicIndexingInterface.remake_buffer(indp, oldbuf::MTKParameters, vals::Dict) newbuf = @set oldbuf.tunable = Tuple(Vector{Any}(undef, length(buf)) for buf in oldbuf.tunable) @set! newbuf.discrete = Tuple(Vector{Any}(undef, length(buf)) @@ -409,9 +478,15 @@ function SymbolicIndexingInterface.remake_buffer(sys, oldbuf::MTKParameters, val @set! newbuf.nonnumeric = Tuple(Vector{Any}(undef, length(buf)) for buf in newbuf.nonnumeric) + # If the parameter buffer is an `MTKParameters` object, `indp` must eventually drill + # down to an `AbstractSystem` using `symbolic_container`. We leverage this to get + # the index cache. + ic = get_index_cache(indp_to_system(indp)) for (p, val) in vals + idx = parameter_index(indp, p) + validate_parameter_type(ic, p, idx, val) _set_parameter_unchecked!( - newbuf, val, parameter_index(sys, p); update_dependent = false) + newbuf, val, idx; update_dependent = false) end @set! newbuf.tunable = narrow_buffer_type_and_fallback_undefs.( @@ -588,3 +663,15 @@ function Base.showerror(io::IO, e::MissingParametersError) println(io, MISSING_PARAMETERS_MESSAGE) println(io, e.vars) end + +function InvalidParameterSizeException(param, val) + DimensionMismatch("InvalidParameterSizeException: For parameter $(param) expected value of size $(size(param)). Received value $(val) of size $(size(val)).") +end + +function InvalidParameterSizeException(param::Tuple, val::Tuple) + DimensionMismatch("InvalidParameterSizeException: Expected value of size $(param). Received value of size $(val).") +end + +function ParameterTypeException(func, param, expected, val) + TypeError(func, "Parameter $param", expected, val) +end diff --git a/test/index_cache.jl b/test/index_cache.jl new file mode 100644 index 0000000000..3bee1db381 --- /dev/null +++ b/test/index_cache.jl @@ -0,0 +1,45 @@ +using ModelingToolkit, SymbolicIndexingInterface +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], []) +sys1 = complete(sys) +@named sys = ODESystem(Equation[], t, [x...], []) +sys2 = complete(sys) +for sys in [sys1, sys2] + for (sym, idx) in [(x, 1:2), (x[1], 1), (x[2], 2)] + @test is_variable(sys, sym) + @test variable_index(sys, sym) == idx + end +end + +@variables x(t)[1:2, 1:2] +@named sys = ODESystem(Equation[], t, [x], []) +sys1 = complete(sys) +@named sys = ODESystem(Equation[], t, [x...], []) +sys2 = complete(sys) +for sys in [sys1, sys2] + @test is_variable(sys, x) + @test variable_index(sys, x) == [1 3; 2 4] + for i in eachindex(x) + @test is_variable(sys, x[i]) + @test variable_index(sys, x[i]) == variable_index(sys, x)[i] + end +end + +# Ensure Symbol to symbolic map is correct +@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]) +sys = complete(sys) + +ic = ModelingToolkit.get_index_cache(sys) + +@test isequal(ic.symbol_to_variable[:p1], p1) +@test isequal(ic.symbol_to_variable[:p2], p2) +@test isequal(ic.symbol_to_variable[:p3], p3) +@test isequal(ic.symbol_to_variable[:x], x) +@test isequal(ic.symbol_to_variable[:y], y) +@test isequal(ic.symbol_to_variable[:z], z) diff --git a/test/mtkparameters.jl b/test/mtkparameters.jl index d3707f3db9..aac71dd43e 100644 --- a/test/mtkparameters.jl +++ b/test/mtkparameters.jl @@ -241,3 +241,45 @@ newps = remake_buffer( VDual = Vector{<:ForwardDiff.Dual} VVDual = Vector{<:Vector{<:ForwardDiff.Dual}} @test newps.dependent isa Union{Tuple{VDual, VVDual}, Tuple{VVDual, VDual}} + +@testset "Parameter type validation" begin + struct Foo{T} + x::T + 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]) + sys = complete(sys) + ps = MTKParameters(sys, + Dict(a => 1.0, b => 2, c => 3ones(2), + d => 3ones(Int, 2, 2), e => Foo(1), f => Foo("a"))) + @test_nowarn setp(sys, c)(ps, ones(4)) # so this is fixed when SII is fixed + @test_throws DimensionMismatch set_parameter!( + ps, 4ones(Int, 3, 2), parameter_index(sys, d)) + @test_throws DimensionMismatch set_parameter!( + ps, 4ones(Int, 4), parameter_index(sys, d)) # size has to match, not just length + @test_nowarn setp(sys, f)(ps, Foo(:a)) # can change non-concrete type + + # Same flexibility is afforded to `b::Int` to allow for ForwardDiff + for sym in [a, b] + @test_nowarn remake_buffer(sys, ps, Dict(sym => 1)) + newps = @test_nowarn remake_buffer(sys, ps, Dict(sym => 1.0f0)) # Can change type if it's numeric + @test getp(sys, sym)(newps) isa Float32 + newps = @test_nowarn remake_buffer(sys, ps, Dict(sym => ForwardDiff.Dual(1.0))) + @test getp(sys, sym)(newps) isa ForwardDiff.Dual + @test_throws TypeError remake_buffer(sys, ps, Dict(sym => :a)) # still has to be numeric + end + + newps = @test_nowarn remake_buffer(sys, ps, Dict(c => view(1.0:4.0, 2:4))) # can change type of array + @test getp(sys, c)(newps) == 2.0:4.0 + @test parameter_values(newps, parameter_index(sys, c)) ≈ [2.0, 3.0, 4.0] + @test_throws TypeError remake_buffer(sys, ps, Dict(c => [:a, :b, :c])) # can't arbitrarily change eltype + @test_throws TypeError remake_buffer(sys, ps, Dict(c => :a)) # can't arbitrarily change type + + newps = @test_nowarn remake_buffer(sys, ps, Dict(d => ForwardDiff.Dual.(ones(2, 2)))) # can change eltype + @test_throws TypeError remake_buffer(sys, ps, Dict(d => [:a :b; :c :d])) # eltype still has to be numeric + @test getp(sys, d)(newps) isa Matrix{<:ForwardDiff.Dual} + + @test_throws TypeError remake_buffer(sys, ps, Dict(e => Foo(2.0))) # need exact same type for nonnumeric + @test_nowarn remake_buffer(sys, ps, Dict(f => Foo(:a))) +end diff --git a/test/runtests.jl b/test/runtests.jl index cec0a4bc0b..1395b249f7 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -24,6 +24,7 @@ end @safetestset "Parsing Test" include("variable_parsing.jl") @safetestset "Simplify Test" include("simplify.jl") @safetestset "Direct Usage Test" include("direct.jl") + @safetestset "IndexCache Test" include("index_cache.jl") @safetestset "System Linearity Test" include("linearity.jl") @safetestset "Input Output Test" include("input_output_handling.jl") @safetestset "Clock Test" include("clock.jl") From 234d20c97b5d460903ae4623fa897234be0c1498 Mon Sep 17 00:00:00 2001 From: Fredrik Bagge Carlson Date: Wed, 5 Jun 2024 15:04:13 +0200 Subject: [PATCH 123/316] deactivate more tests --- test/parameter_dependencies.jl | 50 +++++++++++++++++++--------------- 1 file changed, 28 insertions(+), 22 deletions(-) diff --git a/test/parameter_dependencies.jl b/test/parameter_dependencies.jl index caaae3544f..5d1a5b9d7e 100644 --- a/test/parameter_dependencies.jl +++ b/test/parameter_dependencies.jl @@ -81,28 +81,34 @@ end D(x) ~ -x + u y ~ x z(k) ~ z(k - 2) + yd(k - 2)] - @mtkbuild sys = ODESystem(eqs, t; parameter_dependencies = [kq => 2kp]) - - Tf = 1.0 - 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]) - @test_nowarn solve(prob, Tsit5(); kwargshandle = KeywordArgSilent) - - @mtkbuild sys = ODESystem(eqs, t; parameter_dependencies = [kq => 2kp], - discrete_events = [[0.5] => [kp ~ 2.0]]) - 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]) - @test prob.ps[kp] == 1.0 - @test prob.ps[kq] == 2.0 - @test_nowarn solve(prob, Tsit5(), kwargshandle = KeywordArgSilent) - 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]) - integ = init(prob, Tsit5(), kwargshandle = KeywordArgSilent) - @test integ.ps[kp] == 1.0 - @test integ.ps[kq] == 2.0 - step!(integ, 0.6) - @test integ.ps[kp] == 2.0 - @test integ.ps[kq] == 4.0 + @test_throws ModelingToolkit.HybridSystemNotSupportedExcpetion @mtkbuild sys = ODESystem( + eqs, t; parameter_dependencies = [kq => 2kp]) + + @test_skip begin + Tf = 1.0 + 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]) + @test_nowarn solve(prob, Tsit5(); kwargshandle = KeywordArgSilent) + + @mtkbuild sys = ODESystem(eqs, t; parameter_dependencies = [kq => 2kp], + discrete_events = [[0.5] => [kp ~ 2.0]]) + 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]) + @test prob.ps[kp] == 1.0 + @test prob.ps[kq] == 2.0 + @test_nowarn solve(prob, Tsit5(), kwargshandle = KeywordArgSilent) + 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]) + integ = init(prob, Tsit5(), kwargshandle = KeywordArgSilent) + @test integ.ps[kp] == 1.0 + @test integ.ps[kq] == 2.0 + step!(integ, 0.6) + @test integ.ps[kp] == 2.0 + @test integ.ps[kq] == 4.0 + end end @testset "SDESystem" begin From d6240ce15684977b3756f43330c69a57b605af02 Mon Sep 17 00:00:00 2001 From: Christopher Rackauckas Date: Wed, 5 Jun 2024 09:19:35 -0400 Subject: [PATCH 124/316] Update Project.toml --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index f5db4005f1..fef0a00e2c 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.15.0" +version = "9.16.0" [deps] AbstractTrees = "1520ce14-60c1-5f80-bbc7-55ef81b5835c" From 2d98d614ef95263d664593bdf771931dffbbb2b4 Mon Sep 17 00:00:00 2001 From: Christopher Rackauckas Date: Wed, 5 Jun 2024 19:12:00 -0400 Subject: [PATCH 125/316] Update Project.toml --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 508a53af1c..2655d74636 100644 --- a/Project.toml +++ b/Project.toml @@ -108,7 +108,7 @@ SpecialFunctions = "0.7, 0.8, 0.9, 0.10, 1.0, 2" StaticArrays = "0.10, 0.11, 0.12, 1.0" SymbolicIndexingInterface = "0.3.12" SymbolicUtils = "2" -Symbolics = "5.29" +Symbolics = "5.30.1" URIs = "1" UnPack = "0.1, 1.0" Unitful = "1.1" From 4762af97f5071953f0326c2c82f75e34ecda5362 Mon Sep 17 00:00:00 2001 From: Christopher Rackauckas Date: Wed, 5 Jun 2024 19:14:20 -0400 Subject: [PATCH 126/316] Update index_cache.jl --- src/systems/index_cache.jl | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/src/systems/index_cache.jl b/src/systems/index_cache.jl index fcc5100661..48342f5fff 100644 --- a/src/systems/index_cache.jl +++ b/src/systems/index_cache.jl @@ -239,26 +239,6 @@ function check_index_map(idxmap, sym) nothing end end - -function ParameterIndex(ic::IndexCache, p, sub_idx = ()) - p = unwrap(p) - return if haskey(ic.tunable_idx, p) - ParameterIndex(SciMLStructures.Tunable(), (ic.tunable_idx[p]..., sub_idx...)) - elseif haskey(ic.discrete_idx, p) - ParameterIndex(SciMLStructures.Discrete(), (ic.discrete_idx[p]..., sub_idx...)) - elseif haskey(ic.constant_idx, p) - ParameterIndex(SciMLStructures.Constants(), (ic.constant_idx[p]..., sub_idx...)) - elseif haskey(ic.dependent_idx, p) - ParameterIndex(DEPENDENT_PORTION, (ic.dependent_idx[p]..., sub_idx...)) - elseif haskey(ic.nonnumeric_idx, p) - ParameterIndex(NONNUMERIC_PORTION, (ic.nonnumeric_idx[p]..., sub_idx...)) - elseif iscall(p) && operation(p) === getindex - _p, sub_idx... = arguments(p) - ParameterIndex(ic, _p, sub_idx) - else - nothing - end -end function discrete_linear_index(ic::IndexCache, idx::ParameterIndex) idx.portion isa SciMLStructures.Discrete || error("Discrete variable index expected") From 5db199c74de0471cf52dcfc040a7ac2c612de313 Mon Sep 17 00:00:00 2001 From: Christopher Rackauckas Date: Wed, 5 Jun 2024 19:17:19 -0400 Subject: [PATCH 127/316] fix formatting --- src/systems/index_cache.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/systems/index_cache.jl b/src/systems/index_cache.jl index 48342f5fff..09565ca7fe 100644 --- a/src/systems/index_cache.jl +++ b/src/systems/index_cache.jl @@ -239,7 +239,7 @@ function check_index_map(idxmap, sym) nothing end end - + function discrete_linear_index(ic::IndexCache, idx::ParameterIndex) idx.portion isa SciMLStructures.Discrete || error("Discrete variable index expected") ind = sum(temp.length for temp in ic.tunable_buffer_sizes; init = 0) From e6dd32d1c1ee1328522499d642824aeed60f9bbe Mon Sep 17 00:00:00 2001 From: Christopher Rackauckas Date: Wed, 5 Jun 2024 19:48:49 -0400 Subject: [PATCH 128/316] Update abstractsystem.jl --- 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 d16506a8d9..8600eb681f 100644 --- a/src/systems/abstractsystem.jl +++ b/src/systems/abstractsystem.jl @@ -2565,8 +2565,8 @@ is_diff_equation(eq2) # false """ function is_diff_equation(eq) (eq isa Equation) || (return false) - isdefined(eq, :lhs) && occursin(is_derivative, wrap(eq.lhs)) && (return true) - isdefined(eq, :rhs) && occursin(is_derivative, wrap(eq.rhs)) && (return true) + isdefined(eq, :lhs) && hasnode(is_derivative, wrap(eq.lhs)) && (return true) + isdefined(eq, :rhs) && hasnode(is_derivative, wrap(eq.rhs)) && (return true) return false end From 392229639dc2c7f1a6db6a686e9f9a0e172ab4b2 Mon Sep 17 00:00:00 2001 From: Christopher Rackauckas Date: Wed, 5 Jun 2024 21:49:19 -0400 Subject: [PATCH 129/316] Update ModelingToolkit.jl --- src/ModelingToolkit.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ModelingToolkit.jl b/src/ModelingToolkit.jl index c8945cf56a..62e1f4b6e9 100644 --- a/src/ModelingToolkit.jl +++ b/src/ModelingToolkit.jl @@ -10,7 +10,7 @@ end import SymbolicUtils import SymbolicUtils: iscall, arguments, operation, maketerm, promote_symtype, - Symbolic, isadd, ismul, ispow, issym, FnType, + hasnode, Symbolic, isadd, ismul, ispow, issym, FnType, @rule, Rewriters, substitute, metadata, BasicSymbolic, Sym, Term using SymbolicUtils.Code From a75c5686b16b8a72cfb7d7a3e16614fd7bd2221e Mon Sep 17 00:00:00 2001 From: Christopher Rackauckas Date: Wed, 5 Jun 2024 22:29:44 -0400 Subject: [PATCH 130/316] Update src/systems/abstractsystem.jl --- 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 8600eb681f..22c17f0634 100644 --- a/src/systems/abstractsystem.jl +++ b/src/systems/abstractsystem.jl @@ -2565,8 +2565,8 @@ is_diff_equation(eq2) # false """ function is_diff_equation(eq) (eq isa Equation) || (return false) - isdefined(eq, :lhs) && hasnode(is_derivative, wrap(eq.lhs)) && (return true) - isdefined(eq, :rhs) && hasnode(is_derivative, wrap(eq.rhs)) && (return true) + isdefined(eq, :lhs) && SymbolicUtils.hasnode(is_derivative, wrap(eq.lhs)) && (return true) + isdefined(eq, :rhs) && SymbolicUtils.hasnode(is_derivative, wrap(eq.rhs)) && (return true) return false end From 2b1b3067e7070a6e3662051cec70d9395ac51a91 Mon Sep 17 00:00:00 2001 From: Christopher Rackauckas Date: Wed, 5 Jun 2024 22:32:24 -0400 Subject: [PATCH 131/316] format --- src/systems/abstractsystem.jl | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/systems/abstractsystem.jl b/src/systems/abstractsystem.jl index 22c17f0634..16fa62d751 100644 --- a/src/systems/abstractsystem.jl +++ b/src/systems/abstractsystem.jl @@ -2565,8 +2565,10 @@ is_diff_equation(eq2) # false """ function is_diff_equation(eq) (eq isa Equation) || (return false) - isdefined(eq, :lhs) && SymbolicUtils.hasnode(is_derivative, wrap(eq.lhs)) && (return true) - isdefined(eq, :rhs) && SymbolicUtils.hasnode(is_derivative, wrap(eq.rhs)) && (return true) + isdefined(eq, :lhs) && SymbolicUtils.hasnode(is_derivative, wrap(eq.lhs)) && + (return true) + isdefined(eq, :rhs) && SymbolicUtils.hasnode(is_derivative, wrap(eq.rhs)) && + (return true) return false end From 35c5673f67cc03d7db8de9e6374281c0a38b9d92 Mon Sep 17 00:00:00 2001 From: "Bowen S. Zhu" Date: Wed, 5 Jun 2024 19:51:36 -0700 Subject: [PATCH 132/316] Remove unnecessary line in `calculate_massmatrix` function --- src/systems/diffeqs/abstractodesystem.jl | 1 - 1 file changed, 1 deletion(-) diff --git a/src/systems/diffeqs/abstractodesystem.jl b/src/systems/diffeqs/abstractodesystem.jl index b976945f79..d68112bdcd 100644 --- a/src/systems/diffeqs/abstractodesystem.jl +++ b/src/systems/diffeqs/abstractodesystem.jl @@ -241,7 +241,6 @@ end function calculate_massmatrix(sys::AbstractODESystem; simplify = false) eqs = [eq for eq in equations(sys)] - dvs = unknowns(sys) M = zeros(length(eqs), length(eqs)) for (i, eq) in enumerate(eqs) if istree(eq.lhs) && operation(eq.lhs) isa Differential From 9b7a41b0a7c63524b841c9ff50687f1b4388a270 Mon Sep 17 00:00:00 2001 From: Christopher Rackauckas Date: Wed, 5 Jun 2024 23:53:53 -0400 Subject: [PATCH 133/316] Update ModelingToolkit.jl --- src/ModelingToolkit.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ModelingToolkit.jl b/src/ModelingToolkit.jl index 62e1f4b6e9..9ced3053e1 100644 --- a/src/ModelingToolkit.jl +++ b/src/ModelingToolkit.jl @@ -10,7 +10,7 @@ end import SymbolicUtils import SymbolicUtils: iscall, arguments, operation, maketerm, promote_symtype, - hasnode, Symbolic, isadd, ismul, ispow, issym, FnType, + Symbolic, isadd, ismul, ispow, issym, FnType, @rule, Rewriters, substitute, metadata, BasicSymbolic, Sym, Term using SymbolicUtils.Code @@ -59,7 +59,7 @@ using Symbolics: _parse_vars, value, @derivatives, get_variables, exprs_occur_in, solve_for, build_expr, unwrap, wrap, VariableSource, getname, variable, Connection, connect, NAMESPACE_SEPARATOR, set_scalar_metadata, setdefaultval, - initial_state, transition, activeState, entry, + initial_state, transition, activeState, entry, hasnode, ticksInState, timeInState, fixpoint_sub, fast_substitute import Symbolics: rename, get_variables!, _solve, hessian_sparsity, jacobian_sparsity, isaffine, islinear, _iszero, _isone, From 1f41c611d8ffc0f53a360aeae4b434c098acd36f Mon Sep 17 00:00:00 2001 From: Christopher Rackauckas Date: Wed, 5 Jun 2024 23:54:22 -0400 Subject: [PATCH 134/316] Update src/systems/abstractsystem.jl --- 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 16fa62d751..a74614f94c 100644 --- a/src/systems/abstractsystem.jl +++ b/src/systems/abstractsystem.jl @@ -2565,9 +2565,9 @@ is_diff_equation(eq2) # false """ function is_diff_equation(eq) (eq isa Equation) || (return false) - isdefined(eq, :lhs) && SymbolicUtils.hasnode(is_derivative, wrap(eq.lhs)) && + isdefined(eq, :lhs) && hasnode(is_derivative, wrap(eq.lhs)) && (return true) - isdefined(eq, :rhs) && SymbolicUtils.hasnode(is_derivative, wrap(eq.rhs)) && + isdefined(eq, :rhs) && hasnode(is_derivative, wrap(eq.rhs)) && (return true) return false end From 8ea234249fd9bd4e3217beccd0a99323df8e70fb Mon Sep 17 00:00:00 2001 From: Christopher Rackauckas Date: Thu, 6 Jun 2024 01:14:44 -0400 Subject: [PATCH 135/316] Update Project.toml --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 9e7eaef115..3b035e48aa 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.16.0" +version = "9.17.0" [deps] AbstractTrees = "1520ce14-60c1-5f80-bbc7-55ef81b5835c" From 47f154724302f9ca1385f703b78eec2956e081e3 Mon Sep 17 00:00:00 2001 From: Christopher Rackauckas Date: Thu, 6 Jun 2024 01:15:29 -0400 Subject: [PATCH 136/316] Update Project.toml --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 3b035e48aa..9e7eaef115 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.17.0" +version = "9.16.0" [deps] AbstractTrees = "1520ce14-60c1-5f80-bbc7-55ef81b5835c" From 4eb79254758052ada92474f47f52ff8c7af9a668 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Thu, 6 Jun 2024 12:59:52 +0530 Subject: [PATCH 137/316] fix: fix `build_explicit_observed_function` for array parameter expressions --- src/systems/diffeqs/odesystem.jl | 5 ++++- test/symbolic_indexing_interface.jl | 16 ++++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/src/systems/diffeqs/odesystem.jl b/src/systems/diffeqs/odesystem.jl index 4b22602c8a..d20629df79 100644 --- a/src/systems/diffeqs/odesystem.jl +++ b/src/systems/diffeqs/odesystem.jl @@ -420,7 +420,10 @@ function build_explicit_observed_function(sys, ts; subs = Dict() maxidx = 0 for s in dep_vars - if s in param_set || s in param_set_ns + if s in param_set || s in param_set_ns || + iscall(s) && + operation(s) === getindex && + (arguments(s)[1] in param_set || arguments(s)[1] in param_set_ns) continue end idx = get(observed_idx, s, nothing) diff --git a/test/symbolic_indexing_interface.jl b/test/symbolic_indexing_interface.jl index 6ae3430c3e..12d9c68c73 100644 --- a/test/symbolic_indexing_interface.jl +++ b/test/symbolic_indexing_interface.jl @@ -85,3 +85,19 @@ analytic_function = (ps, t, x) -> -ps[1] * x * (x - 1) * sin(x) * exp(-2 * ps[1] @test isequal(pdesys.ps, [h]) @test isequal(parameter_symbols(pdesys), [h]) @test isequal(parameters(pdesys), [h]) + +# 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 = ODESystem( + [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] From 2adc33a55457c58699edf2d128a3024787fcfb61 Mon Sep 17 00:00:00 2001 From: Christopher Rackauckas Date: Thu, 6 Jun 2024 06:05:33 -0400 Subject: [PATCH 138/316] Update Project.toml --- docs/Project.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/Project.toml b/docs/Project.toml index 2cb3964373..9fecafecd6 100644 --- a/docs/Project.toml +++ b/docs/Project.toml @@ -28,7 +28,7 @@ Distributions = "0.25" Documenter = "1" DynamicQuantities = "^0.11.2, 0.12" ModelingToolkit = "8.33, 9" -NonlinearSolve = "0.3, 1, 2, 3" +NonlinearSolve = "3" Optim = "1.7" Optimization = "3.9" OptimizationOptimJL = "0.1" @@ -37,6 +37,6 @@ Plots = "1.36" SciMLStructures = "1.1" StochasticDiffEq = "6" SymbolicIndexingInterface = "0.3.1" -SymbolicUtils = "1" +SymbolicUtils = "2" Symbolics = "5" Unitful = "1.12" From 5e6556a15dfd406c67397d47e5212afbea528480 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Miclu=C8=9Ba-C=C3=A2mpeanu?= Date: Wed, 24 Apr 2024 17:45:42 +0300 Subject: [PATCH 139/316] fix: use `full_parameters` in `build_explicit_observed_function` This makes dependent parameters available in the observed functions. --- src/systems/diffeqs/odesystem.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/systems/diffeqs/odesystem.jl b/src/systems/diffeqs/odesystem.jl index d20629df79..fe8b5e32f6 100644 --- a/src/systems/diffeqs/odesystem.jl +++ b/src/systems/diffeqs/odesystem.jl @@ -405,10 +405,10 @@ function build_explicit_observed_function(sys, ts; Set(arguments(st)[1] for st in sts if iscall(st) && operation(st) === getindex)) observed_idx = Dict(x.lhs => i for (i, x) in enumerate(obs)) - param_set = Set(parameters(sys)) + param_set = Set(full_parameters(sys)) param_set = union(param_set, Set(arguments(p)[1] for p in param_set if iscall(p) && operation(p) === getindex)) - param_set_ns = Set(unknowns(sys, p) for p in parameters(sys)) + param_set_ns = Set(unknowns(sys, p) for p in full_parameters(sys)) param_set_ns = union(param_set_ns, Set(arguments(p)[1] for p in param_set_ns if iscall(p) && operation(p) === getindex)) From 441565bf14fe628aba43165aad877c8fdc4395d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Miclu=C8=9Ba-C=C3=A2mpeanu?= Date: Wed, 24 Apr 2024 17:46:02 +0300 Subject: [PATCH 140/316] test: add test for getu with parameter deps --- test/parameter_dependencies.jl | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/test/parameter_dependencies.jl b/test/parameter_dependencies.jl index caaae3544f..6970d5d23a 100644 --- a/test/parameter_dependencies.jl +++ b/test/parameter_dependencies.jl @@ -67,6 +67,20 @@ end @test Set(full_parameters(sys)) == Set([p1, p2]) end +@testset "getu with parameter deps" begin + @parameters p1=1.0 p2=1.0 + @variables x(t)=0 + + @named sys = ODESystem( + [D(x) ~ p1 * t + p2], + t; + parameter_dependencies = [p2 => 2p1] + ) + prob = ODEProblem(complete(sys)) + get_dep = getu(prob, 2p2) + @test get_dep(prob) == 4 +end + @testset "Clock system" begin dt = 0.1 @variables x(t) y(t) u(t) yd(t) ud(t) r(t) z(t) From c54903e2c8babb9f31f01c29e24a2a44e7f4c414 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Miclu=C8=9Ba-C=C3=A2mpeanu?= Date: Thu, 25 Apr 2024 03:31:23 +0300 Subject: [PATCH 141/316] style: fix formating --- test/parameter_dependencies.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/parameter_dependencies.jl b/test/parameter_dependencies.jl index 6970d5d23a..4469bde53b 100644 --- a/test/parameter_dependencies.jl +++ b/test/parameter_dependencies.jl @@ -69,7 +69,7 @@ end @testset "getu with parameter deps" begin @parameters p1=1.0 p2=1.0 - @variables x(t)=0 + @variables x(t) = 0 @named sys = ODESystem( [D(x) ~ p1 * t + p2], From 45d9925ecb6b3a79c26ffe161ac64e3ed9db727c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Miclu=C8=9Ba-C=C3=A2mpeanu?= Date: Fri, 26 Apr 2024 03:32:24 +0300 Subject: [PATCH 142/316] fix: do not forget about parameter dependencies when flattening the system this fixes structural simplification dropping parameter dependencies --- src/systems/diffeqs/odesystem.jl | 1 + 1 file changed, 1 insertion(+) diff --git a/src/systems/diffeqs/odesystem.jl b/src/systems/diffeqs/odesystem.jl index fe8b5e32f6..ab0e98492c 100644 --- a/src/systems/diffeqs/odesystem.jl +++ b/src/systems/diffeqs/odesystem.jl @@ -355,6 +355,7 @@ function flatten(sys::ODESystem, noeqs = false) get_iv(sys), unknowns(sys), parameters(sys), + parameter_dependencies = get_parameter_dependencies(sys), guesses = guesses(sys), observed = observed(sys), continuous_events = continuous_events(sys), From 09bc118bf6f5ae7fa32463737dd89ebfd8fd1cff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Miclu=C8=9Ba-C=C3=A2mpeanu?= Date: Fri, 26 Apr 2024 03:32:52 +0300 Subject: [PATCH 143/316] fix: do not forget about parameter dependencies in initialization --- 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 2421f20bf2..9a52739562 100644 --- a/src/systems/nonlinear/initializesystem.jl +++ b/src/systems/nonlinear/initializesystem.jl @@ -102,6 +102,7 @@ function generate_initializesystem(sys::ODESystem; full_states, pars; defaults = merge(ModelingToolkit.defaults(sys), todict(u0), dd_guess), + parameter_dependencies = get_parameter_dependencies(sys), name, kwargs...) From adf3b44276b4a2b6b4924b30f63a58a2db4ceed0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Miclu=C8=9Ba-C=C3=A2mpeanu?= Date: Tue, 21 May 2024 16:12:16 +0300 Subject: [PATCH 144/316] refactor: renamespace parameter dependencies --- src/systems/abstractsystem.jl | 27 +++++++++++++++++++++++++-- src/systems/diffeqs/odesystem.jl | 2 +- src/systems/index_cache.jl | 4 ++-- 3 files changed, 28 insertions(+), 5 deletions(-) diff --git a/src/systems/abstractsystem.jl b/src/systems/abstractsystem.jl index a74614f94c..39b1d84b58 100644 --- a/src/systems/abstractsystem.jl +++ b/src/systems/abstractsystem.jl @@ -878,6 +878,11 @@ function namespace_guesses(sys) Dict(unknowns(sys, k) => namespace_expr(v, sys) for (k, v) in guess) end +function namespace_parameter_dependencies(sys) + pdeps = get_parameter_dependencies(sys) + Dict(dependent_parameters(sys, k) => namespace_expr(v, sys) for (k, v) in pdeps) +end + function namespace_equations(sys::AbstractSystem, ivs = independent_variables(sys)) eqs = equations(sys) isempty(eqs) && return Equation[] @@ -976,13 +981,29 @@ end function dependent_parameters(sys::AbstractSystem) if has_parameter_dependencies(sys) && - (pdeps = get_parameter_dependencies(sys)) !== nothing - collect(keys(pdeps)) + !isempty(parameter_dependencies(sys)) + collect(keys(parameter_dependencies(sys))) else [] end end +function parameter_dependencies(sys::AbstractSystem) + pdeps = get_parameter_dependencies(sys) + if isnothing(pdeps) + pdeps = Dict() + end + systems = get_systems(sys) + isempty(systems) && return pdeps + for subsys in systems + isnothing(get_parameter_dependencies(subsys)) && continue + + pdeps = merge(pdeps, namespace_parameter_dependencies(subsys)) + end + # @info pdeps + return pdeps +end + function full_parameters(sys::AbstractSystem) vcat(parameters(sys), dependent_parameters(sys)) end @@ -1045,6 +1066,8 @@ for f in [:unknowns, :parameters] end end +dependent_parameters(sys::Union{AbstractSystem, Nothing}, v) = renamespace(sys, v) + flatten(sys::AbstractSystem, args...) = sys function equations(sys::AbstractSystem) diff --git a/src/systems/diffeqs/odesystem.jl b/src/systems/diffeqs/odesystem.jl index ab0e98492c..bac83f3784 100644 --- a/src/systems/diffeqs/odesystem.jl +++ b/src/systems/diffeqs/odesystem.jl @@ -355,7 +355,7 @@ function flatten(sys::ODESystem, noeqs = false) get_iv(sys), unknowns(sys), parameters(sys), - parameter_dependencies = get_parameter_dependencies(sys), + parameter_dependencies = parameter_dependencies(sys), guesses = guesses(sys), observed = observed(sys), continuous_events = continuous_events(sys), diff --git a/src/systems/index_cache.jl b/src/systems/index_cache.jl index 09565ca7fe..2a56c5eb3e 100644 --- a/src/systems/index_cache.jl +++ b/src/systems/index_cache.jl @@ -115,8 +115,8 @@ function IndexCache(sys::AbstractSystem) end end - if has_parameter_dependencies(sys) && - (pdeps = get_parameter_dependencies(sys)) !== nothing + if has_parameter_dependencies(sys) + pdeps = parameter_dependencies(sys) for (sym, value) in pdeps sym = unwrap(sym) insert_by_type!(dependent_buffers, sym) From 87d1dd594dd29a089cfc79a428cdc75ead44e92b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Miclu=C8=9Ba-C=C3=A2mpeanu?= Date: Tue, 21 May 2024 16:12:44 +0300 Subject: [PATCH 145/316] test: add test for composing systems with dependencies --- test/parameter_dependencies.jl | 35 +++++++++++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/test/parameter_dependencies.jl b/test/parameter_dependencies.jl index 4469bde53b..28c73809f1 100644 --- a/test/parameter_dependencies.jl +++ b/test/parameter_dependencies.jl @@ -51,7 +51,7 @@ end @testset "extend" begin @parameters p1=1.0 p2=1.0 - @variables x(t) + @variables x(t) = 0 @mtkbuild sys1 = ODESystem( [D(x) ~ p1 * t + p2], @@ -65,6 +65,9 @@ end sys = extend(sys2, sys1) @test isequal(only(parameters(sys)), p1) @test Set(full_parameters(sys)) == Set([p1, p2]) + prob = ODEProblem(complete(sys)) + get_dep = getu(prob, 2p2) + @test get_dep(prob) == 4 end @testset "getu with parameter deps" begin @@ -81,6 +84,36 @@ end @test get_dep(prob) == 4 end +@testset "composing systems with parameter deps" begin + @parameters p1=1.0 p2=2.0 + @variables x(t) = 0 + + @mtkbuild sys1 = ODESystem( + [D(x) ~ p1 * t + p2], + t + ) + @named sys2 = ODESystem( + [D(x) ~ p1 * t - p2], + t; + parameter_dependencies = [p2 => 2p1] + ) + sys = complete(ODESystem([], t, systems = [sys1, sys2], name = :sys)) + + prob = ODEProblem(sys) + v1 = sys.sys2.p2 + v2 = 2 * v1 + @test is_parameter(prob, v1) + @test is_observed(prob, v2) + get_v1 = getu(prob, v1) + get_v2 = getu(prob, v2) + @test get_v1(prob) == 2 + @test get_v2(prob) == 4 + + new_prob = remake(prob, p = [sys2.p1 => 1.5]) + @test new_prob.ps[sys2.p1] == 1.5 + @test new_prob.ps[sys2.p2] == 3.0 +end + @testset "Clock system" begin dt = 0.1 @variables x(t) y(t) u(t) yd(t) ud(t) r(t) z(t) From 99f088be4bb0bc6d687449033f3733b1ffb50ff9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Miclu=C8=9Ba-C=C3=A2mpeanu?= <31181429+SebastianM-C@users.noreply.github.com> Date: Mon, 27 May 2024 14:28:21 +0300 Subject: [PATCH 146/316] refactor: no need for `dependent_parameters` Co-authored-by: Aayush Sabharwal --- src/systems/abstractsystem.jl | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/systems/abstractsystem.jl b/src/systems/abstractsystem.jl index 39b1d84b58..a770fcbc3d 100644 --- a/src/systems/abstractsystem.jl +++ b/src/systems/abstractsystem.jl @@ -880,7 +880,7 @@ end function namespace_parameter_dependencies(sys) pdeps = get_parameter_dependencies(sys) - Dict(dependent_parameters(sys, k) => namespace_expr(v, sys) for (k, v) in pdeps) + Dict(parameters(sys, k) => namespace_expr(v, sys) for (k, v) in pdeps) end function namespace_equations(sys::AbstractSystem, ivs = independent_variables(sys)) @@ -1066,8 +1066,6 @@ for f in [:unknowns, :parameters] end end -dependent_parameters(sys::Union{AbstractSystem, Nothing}, v) = renamespace(sys, v) - flatten(sys::AbstractSystem, args...) = sys function equations(sys::AbstractSystem) From a5c79d89481bf2e270d5622da8115f22b3e97812 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Miclu=C8=9Ba-C=C3=A2mpeanu?= <31181429+SebastianM-C@users.noreply.github.com> Date: Mon, 27 May 2024 14:30:25 +0300 Subject: [PATCH 147/316] refactor: use recursion for nested systems Co-authored-by: Aayush Sabharwal --- src/systems/abstractsystem.jl | 2 +- src/systems/nonlinear/initializesystem.jl | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/systems/abstractsystem.jl b/src/systems/abstractsystem.jl index a770fcbc3d..0f1178dd21 100644 --- a/src/systems/abstractsystem.jl +++ b/src/systems/abstractsystem.jl @@ -879,7 +879,7 @@ function namespace_guesses(sys) end function namespace_parameter_dependencies(sys) - pdeps = get_parameter_dependencies(sys) + pdeps = parameter_dependencies(sys) Dict(parameters(sys, k) => namespace_expr(v, sys) for (k, v) in pdeps) end diff --git a/src/systems/nonlinear/initializesystem.jl b/src/systems/nonlinear/initializesystem.jl index 9a52739562..95d38c2bbf 100644 --- a/src/systems/nonlinear/initializesystem.jl +++ b/src/systems/nonlinear/initializesystem.jl @@ -102,7 +102,7 @@ function generate_initializesystem(sys::ODESystem; full_states, pars; defaults = merge(ModelingToolkit.defaults(sys), todict(u0), dd_guess), - parameter_dependencies = get_parameter_dependencies(sys), + parameter_dependencies = parameter_dependencies(sys), name, kwargs...) From 250e730a202e96d74ef46d9c7d133eeea38d6da7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Miclu=C8=9Ba-C=C3=A2mpeanu?= <31181429+SebastianM-C@users.noreply.github.com> Date: Mon, 27 May 2024 14:33:54 +0300 Subject: [PATCH 148/316] refactor: parameter_dependencies is now called recursively `subsys` might not have parameter dependencies, but subsystems of `subsys` might Co-authored-by: Aayush Sabharwal --- src/systems/abstractsystem.jl | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/systems/abstractsystem.jl b/src/systems/abstractsystem.jl index 0f1178dd21..43ada40369 100644 --- a/src/systems/abstractsystem.jl +++ b/src/systems/abstractsystem.jl @@ -996,8 +996,6 @@ function parameter_dependencies(sys::AbstractSystem) systems = get_systems(sys) isempty(systems) && return pdeps for subsys in systems - isnothing(get_parameter_dependencies(subsys)) && continue - pdeps = merge(pdeps, namespace_parameter_dependencies(subsys)) end # @info pdeps From 5a2705e5dac9805233407871269ea432eb61bfee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Miclu=C8=9Ba-C=C3=A2mpeanu?= Date: Thu, 6 Jun 2024 15:45:01 +0300 Subject: [PATCH 149/316] refactor: recursively check for parameter dependencies everywhere Co-authored-by: Aayush Sabharwal --- src/systems/abstractsystem.jl | 6 +++--- src/systems/diffeqs/sdesystem.jl | 4 ++-- src/systems/parameter_buffer.jl | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/systems/abstractsystem.jl b/src/systems/abstractsystem.jl index 43ada40369..982ce805e9 100644 --- a/src/systems/abstractsystem.jl +++ b/src/systems/abstractsystem.jl @@ -970,7 +970,7 @@ function parameters(sys::AbstractSystem) result = unique(isempty(systems) ? ps : [ps; reduce(vcat, namespace_parameters.(systems))]) if has_parameter_dependencies(sys) && - (pdeps = get_parameter_dependencies(sys)) !== nothing + (pdeps = parameter_dependencies(sys)) !== nothing filter(result) do sym !haskey(pdeps, sym) end @@ -2391,8 +2391,8 @@ function extend(sys::AbstractSystem, basesys::AbstractSystem; name::Symbol = nam eqs = union(get_eqs(basesys), get_eqs(sys)) sts = union(get_unknowns(basesys), get_unknowns(sys)) ps = union(get_ps(basesys), get_ps(sys)) - base_deps = get_parameter_dependencies(basesys) - deps = get_parameter_dependencies(sys) + base_deps = parameter_dependencies(basesys) + deps = parameter_dependencies(sys) dep_ps = isnothing(base_deps) ? deps : isnothing(deps) ? base_deps : union(base_deps, deps) obs = union(get_observed(basesys), get_observed(sys)) diff --git a/src/systems/diffeqs/sdesystem.jl b/src/systems/diffeqs/sdesystem.jl index b021a201fe..05d85ecd6b 100644 --- a/src/systems/diffeqs/sdesystem.jl +++ b/src/systems/diffeqs/sdesystem.jl @@ -291,7 +291,7 @@ function stochastic_integral_transform(sys::SDESystem, correction_factor) end SDESystem(deqs, get_noiseeqs(sys), get_iv(sys), unknowns(sys), parameters(sys), - name = name, parameter_dependencies = get_parameter_dependencies(sys), checks = false) + name = name, parameter_dependencies = parameter_dependencies(sys), checks = false) end """ @@ -399,7 +399,7 @@ function Girsanov_transform(sys::SDESystem, u; θ0 = 1.0) # return modified SDE System SDESystem(deqs, noiseeqs, get_iv(sys), unknown_vars, parameters(sys); defaults = Dict(θ => θ0), observed = [weight ~ θ / θ0], - name = name, parameter_dependencies = get_parameter_dependencies(sys), + name = name, parameter_dependencies = parameter_dependencies(sys), checks = false) end diff --git a/src/systems/parameter_buffer.jl b/src/systems/parameter_buffer.jl index c3c740bc0b..65f24be30c 100644 --- a/src/systems/parameter_buffer.jl +++ b/src/systems/parameter_buffer.jl @@ -136,7 +136,7 @@ function MTKParameters( nonnumeric_buffer = nonnumeric_buffer if has_parameter_dependencies(sys) && - (pdeps = get_parameter_dependencies(sys)) !== nothing + (pdeps = parameter_dependencies(sys)) !== nothing pdeps = Dict(k => fixpoint_sub(v, pdeps) for (k, v) in pdeps) dep_exprs = ArrayPartition((Any[missing for _ in 1:length(v)] for v in dep_buffer)...) for (sym, val) in pdeps From 55a9dc85419941bdec6d2764ee8f79973a62311a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Miclu=C8=9Ba-C=C3=A2mpeanu?= Date: Thu, 6 Jun 2024 15:45:30 +0300 Subject: [PATCH 150/316] test: add more parameter dependencies tests --- test/parameter_dependencies.jl | 39 ++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/test/parameter_dependencies.jl b/test/parameter_dependencies.jl index 28c73809f1..b07959f6c4 100644 --- a/test/parameter_dependencies.jl +++ b/test/parameter_dependencies.jl @@ -49,6 +49,25 @@ using NonlinearSolve @test integ.ps[p2] == 10.0 end +@testset "vector parameter deps" begin + @parameters p1[1:2]=[1.0, 2.0] p2[1:2]=[0.0, 0.0] + @variables x(t) = 0 + + @named sys = ODESystem( + [D(x) ~ sum(p1) * t + sum(p2)], + t; + parameter_dependencies = [p2 => 2p1] + ) + prob = ODEProblem(complete(sys)) + setp1! = setp(prob, p1) + get_p1 = getp(prob, p1) + get_p2 = getp(prob, p2) + setp1!(prob, [1.5, 2.5]) + + @test get_p1(prob) == [1.5, 2.5] + @test get_p2(prob) == [3.0, 5.0] +end + @testset "extend" begin @parameters p1=1.0 p2=1.0 @variables x(t) = 0 @@ -84,6 +103,20 @@ end @test get_dep(prob) == 4 end +@testset "getu with vector parameter deps" begin + @parameters p1[1:2]=[1.0, 2.0] p2[1:2]=[0.0, 0.0] + @variables x(t) = 0 + + @named sys = ODESystem( + [D(x) ~ sum(p1) * t + sum(p2)], + t; + parameter_dependencies = [p2 => 2p1] + ) + prob = ODEProblem(complete(sys)) + get_dep = getu(prob, 2p1) + @test get_dep(prob) == [2.0, 4.0] +end + @testset "composing systems with parameter deps" begin @parameters p1=1.0 p2=2.0 @variables x(t) = 0 @@ -109,7 +142,13 @@ end @test get_v1(prob) == 2 @test get_v2(prob) == 4 + setp1! = setp(prob, sys2.p1) + setp1!(prob, 2.5) + @test prob.ps[sys2.p2] == 5.0 + new_prob = remake(prob, p = [sys2.p1 => 1.5]) + + @test !isempty(ModelingToolkit.parameter_dependencies(sys2)) @test new_prob.ps[sys2.p1] == 1.5 @test new_prob.ps[sys2.p2] == 3.0 end From 96bab6f06afeeb7d4060d6429e307555b52bb679 Mon Sep 17 00:00:00 2001 From: Christopher Rackauckas Date: Sun, 31 Mar 2024 14:28:39 -0400 Subject: [PATCH 151/316] WIP: Add initialization_equations flattening constructor --- src/systems/abstractsystem.jl | 20 ++++++++++++++++++++ src/systems/diffeqs/abstractodesystem.jl | 6 +++--- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/src/systems/abstractsystem.jl b/src/systems/abstractsystem.jl index 982ce805e9..24fae70614 100644 --- a/src/systems/abstractsystem.jl +++ b/src/systems/abstractsystem.jl @@ -898,6 +898,12 @@ function namespace_equation(eq::Equation, _lhs ~ _rhs end +function namespace_initialization_equations(sys::AbstractSystem, ivs = independent_variables(sys)) + eqs = initialization_equations(sys) + isempty(eqs) && return Equation[] + map(eq -> namespace_equation(eq, sys; ivs), eqs) +end + function namespace_assignment(eq::Assignment, sys) _lhs = namespace_expr(eq.lhs, sys) _rhs = namespace_expr(eq.rhs, sys) @@ -1080,6 +1086,20 @@ function equations(sys::AbstractSystem) end end +function initialization_equations(sys::AbstractSystem) + eqs = get_initialization_eqs(sys) + systems = get_systems(sys) + if isempty(systems) + return eqs + else + eqs = Equation[eqs; + reduce(vcat, + namespace_equations.(get_systems(sys)); + init = Equation[])] + return eqs + end +end + function preface(sys::AbstractSystem) has_preface(sys) || return nothing pre = get_preface(sys) diff --git a/src/systems/diffeqs/abstractodesystem.jl b/src/systems/diffeqs/abstractodesystem.jl index 847ef274af..a377b462a8 100644 --- a/src/systems/diffeqs/abstractodesystem.jl +++ b/src/systems/diffeqs/abstractodesystem.jl @@ -908,10 +908,10 @@ function process_DEProblem(constructor, sys::AbstractODESystem, u0map, parammap; # TODO: make it work with clocks # ModelingToolkit.get_tearing_state(sys) !== nothing => Requires structural_simplify first if sys isa ODESystem && build_initializeprob && - (implicit_dae || !isempty(missingvars)) && + ((implicit_dae || !isempty(missingvars)) && all(isequal(Continuous()), ci.var_domain) && - ModelingToolkit.get_tearing_state(sys) !== nothing && - t !== nothing + ModelingToolkit.get_tearing_state(sys) !== nothing) || + !isempty(initialization_equations(sys)) && t !== nothing if eltype(u0map) <: Number u0map = unknowns(sys) .=> u0map end From 452f8fe9fcaacdc5318c1e82e6513ff650398a61 Mon Sep 17 00:00:00 2001 From: Yingbo Ma Date: Fri, 5 Apr 2024 15:47:59 -0400 Subject: [PATCH 152/316] Use `initialization_equations` in `flatten` --- src/systems/abstractsystem.jl | 3 ++- src/systems/diffeqs/abstractodesystem.jl | 4 ++-- src/systems/diffeqs/odesystem.jl | 1 + 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/systems/abstractsystem.jl b/src/systems/abstractsystem.jl index 24fae70614..3a1927a976 100644 --- a/src/systems/abstractsystem.jl +++ b/src/systems/abstractsystem.jl @@ -898,7 +898,8 @@ function namespace_equation(eq::Equation, _lhs ~ _rhs end -function namespace_initialization_equations(sys::AbstractSystem, ivs = independent_variables(sys)) +function namespace_initialization_equations( + sys::AbstractSystem, ivs = independent_variables(sys)) eqs = initialization_equations(sys) isempty(eqs) && return Equation[] map(eq -> namespace_equation(eq, sys; ivs), eqs) diff --git a/src/systems/diffeqs/abstractodesystem.jl b/src/systems/diffeqs/abstractodesystem.jl index a377b462a8..e9996d7716 100644 --- a/src/systems/diffeqs/abstractodesystem.jl +++ b/src/systems/diffeqs/abstractodesystem.jl @@ -909,8 +909,8 @@ function process_DEProblem(constructor, sys::AbstractODESystem, u0map, parammap; # ModelingToolkit.get_tearing_state(sys) !== nothing => Requires structural_simplify first if sys isa ODESystem && build_initializeprob && ((implicit_dae || !isempty(missingvars)) && - all(isequal(Continuous()), ci.var_domain) && - ModelingToolkit.get_tearing_state(sys) !== nothing) || + all(isequal(Continuous()), ci.var_domain) && + ModelingToolkit.get_tearing_state(sys) !== nothing) || !isempty(initialization_equations(sys)) && t !== nothing if eltype(u0map) <: Number u0map = unknowns(sys) .=> u0map diff --git a/src/systems/diffeqs/odesystem.jl b/src/systems/diffeqs/odesystem.jl index bac83f3784..9c11f65cfd 100644 --- a/src/systems/diffeqs/odesystem.jl +++ b/src/systems/diffeqs/odesystem.jl @@ -362,6 +362,7 @@ function flatten(sys::ODESystem, noeqs = false) discrete_events = discrete_events(sys), defaults = defaults(sys), name = nameof(sys), + initialization_eqs = initialization_equations(sys), checks = false) end end From 29c2b54f75f80b87b91ce5180a0108e48d90208d Mon Sep 17 00:00:00 2001 From: Christopher Rackauckas Date: Thu, 18 Apr 2024 12:43:55 -0400 Subject: [PATCH 153/316] Complete hierarchical --- src/systems/abstractsystem.jl | 8 +- src/systems/nonlinear/initializesystem.jl | 4 +- test/hierarchical_initialization_eqs.jl | 157 ++++++++++++++++++++++ test/runtests.jl | 1 + 4 files changed, 167 insertions(+), 3 deletions(-) create mode 100644 test/hierarchical_initialization_eqs.jl diff --git a/src/systems/abstractsystem.jl b/src/systems/abstractsystem.jl index 3a1927a976..64168e4239 100644 --- a/src/systems/abstractsystem.jl +++ b/src/systems/abstractsystem.jl @@ -889,6 +889,12 @@ function namespace_equations(sys::AbstractSystem, ivs = independent_variables(sy map(eq -> namespace_equation(eq, sys; ivs), eqs) end +function namespace_initialization_equations(sys::AbstractSystem, ivs = independent_variables(sys)) + eqs = initialization_equations(sys) + isempty(eqs) && return Equation[] + map(eq -> namespace_equation(eq, sys; ivs), eqs) +end + function namespace_equation(eq::Equation, sys, n = nameof(sys); @@ -1095,7 +1101,7 @@ function initialization_equations(sys::AbstractSystem) else eqs = Equation[eqs; reduce(vcat, - namespace_equations.(get_systems(sys)); + namespace_initialization_equations.(get_systems(sys)); init = Equation[])] return eqs end diff --git a/src/systems/nonlinear/initializesystem.jl b/src/systems/nonlinear/initializesystem.jl index 95d38c2bbf..ba916f420d 100644 --- a/src/systems/nonlinear/initializesystem.jl +++ b/src/systems/nonlinear/initializesystem.jl @@ -23,6 +23,7 @@ function generate_initializesystem(sys::ODESystem; diffmap = Dict(getfield.(eqs_diff, :lhs) .=> getfield.(eqs_diff, :rhs)) observed_diffmap = Dict(Differential(get_iv(sys)).(getfield.((observed(sys)), :lhs)) .=> Differential(get_iv(sys)).(getfield.((observed(sys)), :rhs))) + full_diffmap = merge(diffmap, observed_diffmap) full_states = unique([sts; getfield.((observed(sys)), :lhs)]) set_full_states = Set(full_states) @@ -39,8 +40,7 @@ function generate_initializesystem(sys::ODESystem; filtered_u0 = Pair[] for x in u0map y = get(schedule.dummy_sub, x[1], x[1]) - y = ModelingToolkit.fixpoint_sub(y, observed_diffmap) - y = get(diffmap, y, y) + y = ModelingToolkit.fixpoint_sub(y, full_diffmap) if y isa Symbolics.Arr _y = collect(y) diff --git a/test/hierarchical_initialization_eqs.jl b/test/hierarchical_initialization_eqs.jl new file mode 100644 index 0000000000..5212fd71b2 --- /dev/null +++ b/test/hierarchical_initialization_eqs.jl @@ -0,0 +1,157 @@ +using ModelingToolkit, OrdinaryDiffEq + +t = only(@variables(t)) +D = Differential(t) +""" +A simple linear resistor model + +![Resistor](https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcTpJkiEyqh-BRx27pvVH0GLZ4MP_D1oriBwJhnZdgIq7m17z9VKUWaW9MeNQAz1rTML2ho&usqp=CAU) +""" +@component function Resistor(; name, R=1.0) + systems = @named begin + p = Pin() + n = Pin() + end + vars = @variables begin + v(t), [guess=0.0] + i(t), [guess=0.0] + end + params = @parameters begin + R = R, [description = "Resistance of this Resistor"] + end + eqs = [ + v ~ p.v - n.v + i ~ p.i + p.i + n.i ~ 0 + # Ohm's Law + v ~ i * R + ] + return ODESystem(eqs, t, vars, params; systems, name) +end +@connector Pin begin + v(t) + i(t), [connect = Flow] +end +@component function ConstantVoltage(; name, V=1.0) + systems = @named begin + p = Pin() + n = Pin() + end + vars = @variables begin + v(t), [guess=0.0] + i(t), [guess=0.0] + end + params = @parameters begin + V = 10 + end + eqs = [ + v ~ p.v - n.v + i ~ p.i + p.i + n.i ~ 0 + v ~ V + ] + return ODESystem(eqs, t, vars, params; systems, name) +end + +@component function Capacitor(; name, C=1.0) + systems = @named begin + p = Pin() + n = Pin() + end + vars = @variables begin + v(t), [guess=0.0] + i(t), [guess=0.0] + end + params = @parameters begin + C = C + end + initialization_eqs = [ + v ~ 0 + ] + eqs = [ + v ~ p.v - n.v + i ~ p.i + p.i + n.i ~ 0 + C * D(v) ~ i + ] + return ODESystem(eqs, t, vars, params; systems, name, initialization_eqs) + end + + @component function Ground(; name) + systems = @named begin + g = Pin() + end + eqs = [ + g.v ~ 0 + ] + return ODESystem(eqs, t, [], []; systems, name) + end + + @component function Inductor(; name, L=1.0) + systems = @named begin + p = Pin() + n = Pin() + end + vars = @variables begin + v(t), [guess = 0.0] + i(t), [guess = 0.0] + end + params = @parameters begin + (L = L) + end + eqs = [ + v ~ p.v - n.v + i ~ p.i + p.i + n.i ~ 0 + L * D(i) ~ v + ] + return ODESystem(eqs, t, vars, params; systems, name) + end + +""" +This is an RLC model. This should support markdown. That includes +HTML as well. +""" +@component function RLCModel(; name) + systems = @named begin + resistor = Resistor(R=100) + capacitor = Capacitor(C=0.001) + inductor = Inductor(L=1) + source = ConstantVoltage(V=30) + ground = Ground() + end + initialization_eqs = [ + inductor.i ~ 0 + ] + 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) +end +"""Run model RLCModel from 0 to 10""" +function simple() + @mtkbuild model = RLCModel() + u0 = [] + prob = ODEProblem(model, u0, (0.0, 10.0)) + sol = solve(prob) +end +@test SciMLBase.successful_retcode(simple()) + +@named model = RLCModel() +@test length(ModelingToolkit.get_initialization_eqs(model)) == 1 +syslist = ModelingToolkit.get_systems(model) +@test length(ModelingToolkit.get_initialization_eqs(syslist[1])) == 0 +@test length(ModelingToolkit.get_initialization_eqs(syslist[2])) == 1 +@test length(ModelingToolkit.get_initialization_eqs(syslist[3])) == 0 +@test length(ModelingToolkit.get_initialization_eqs(syslist[4])) == 0 +@test length(ModelingToolkit.get_initialization_eqs(syslist[5])) == 0 +@test length(ModelingToolkit.initialization_equations(model)) == 2 + +u0 = [] +prob = ODEProblem(structural_simplify(model), u0, (0.0, 10.0)) +sol = solve(prob, Rodas5P()) +@test length(sol[end]) == 2 +@test length(equations(prob.f.initializeprob.f.sys)) == 0 +@test length(unknowns(prob.f.initializeprob.f.sys)) == 0 diff --git a/test/runtests.jl b/test/runtests.jl index 1395b249f7..7315fee7c7 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -38,6 +38,7 @@ end @safetestset "DDESystem Test" include("dde.jl") @safetestset "NonlinearSystem Test" include("nonlinearsystem.jl") @safetestset "InitializationSystem Test" include("initializationsystem.jl") + @safetestset "Hierarchical Initialization Equations" include("hierarchical_initialization_eqs.jl") @safetestset "PDE Construction Test" include("pde.jl") @safetestset "JumpSystem Test" include("jumpsystem.jl") @safetestset "Constraints Test" include("constraints.jl") From 133b843e6cb5e712e9436a35b1084c685fd004cf Mon Sep 17 00:00:00 2001 From: Christopher Rackauckas Date: Thu, 18 Apr 2024 13:04:03 -0400 Subject: [PATCH 154/316] format --- src/systems/abstractsystem.jl | 3 +- test/hierarchical_initialization_eqs.jl | 174 +++++++++++------------- 2 files changed, 84 insertions(+), 93 deletions(-) diff --git a/src/systems/abstractsystem.jl b/src/systems/abstractsystem.jl index 64168e4239..cf65d31067 100644 --- a/src/systems/abstractsystem.jl +++ b/src/systems/abstractsystem.jl @@ -889,7 +889,8 @@ function namespace_equations(sys::AbstractSystem, ivs = independent_variables(sy map(eq -> namespace_equation(eq, sys; ivs), eqs) end -function namespace_initialization_equations(sys::AbstractSystem, ivs = independent_variables(sys)) +function namespace_initialization_equations( + sys::AbstractSystem, ivs = independent_variables(sys)) eqs = initialization_equations(sys) isempty(eqs) && return Equation[] map(eq -> namespace_equation(eq, sys; ivs), eqs) diff --git a/test/hierarchical_initialization_eqs.jl b/test/hierarchical_initialization_eqs.jl index 5212fd71b2..077d834db2 100644 --- a/test/hierarchical_initialization_eqs.jl +++ b/test/hierarchical_initialization_eqs.jl @@ -7,135 +7,125 @@ A simple linear resistor model ![Resistor](https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcTpJkiEyqh-BRx27pvVH0GLZ4MP_D1oriBwJhnZdgIq7m17z9VKUWaW9MeNQAz1rTML2ho&usqp=CAU) """ -@component function Resistor(; name, R=1.0) - systems = @named begin - p = Pin() - n = Pin() - end - vars = @variables begin - v(t), [guess=0.0] - i(t), [guess=0.0] - end - params = @parameters begin - R = R, [description = "Resistance of this Resistor"] - end - eqs = [ - v ~ p.v - n.v - i ~ p.i - p.i + n.i ~ 0 - # Ohm's Law - v ~ i * R - ] - return ODESystem(eqs, t, vars, params; systems, name) +@component function Resistor(; name, R = 1.0) + systems = @named begin + p = Pin() + n = Pin() + end + vars = @variables begin + v(t), [guess = 0.0] + i(t), [guess = 0.0] + end + params = @parameters begin + R = R, [description = "Resistance of this Resistor"] + end + eqs = [v ~ p.v - n.v + i ~ p.i + p.i + n.i ~ 0 + # Ohm's Law + v ~ i * R] + return ODESystem(eqs, t, vars, params; systems, name) end @connector Pin begin - v(t) - i(t), [connect = Flow] + v(t) + i(t), [connect = Flow] end -@component function ConstantVoltage(; name, V=1.0) - systems = @named begin - p = Pin() - n = Pin() - end - vars = @variables begin - v(t), [guess=0.0] - i(t), [guess=0.0] - end - params = @parameters begin - V = 10 - end - eqs = [ - v ~ p.v - n.v - i ~ p.i - p.i + n.i ~ 0 - v ~ V - ] - return ODESystem(eqs, t, vars, params; systems, name) +@component function ConstantVoltage(; name, V = 1.0) + systems = @named begin + p = Pin() + n = Pin() + end + vars = @variables begin + v(t), [guess = 0.0] + i(t), [guess = 0.0] + end + params = @parameters begin + V = 10 + end + eqs = [v ~ p.v - n.v + i ~ p.i + p.i + n.i ~ 0 + v ~ V] + return ODESystem(eqs, t, vars, params; systems, name) end -@component function Capacitor(; name, C=1.0) +@component function Capacitor(; name, C = 1.0) systems = @named begin - p = Pin() - n = Pin() + p = Pin() + n = Pin() end vars = @variables begin - v(t), [guess=0.0] - i(t), [guess=0.0] + v(t), [guess = 0.0] + i(t), [guess = 0.0] end params = @parameters begin - C = C + C = C end initialization_eqs = [ - v ~ 0 - ] - eqs = [ - v ~ p.v - n.v - i ~ p.i - p.i + n.i ~ 0 - C * D(v) ~ i + v ~ 0 ] + eqs = [v ~ p.v - n.v + i ~ p.i + p.i + n.i ~ 0 + C * D(v) ~ i] return ODESystem(eqs, t, vars, params; systems, name, initialization_eqs) - end +end - @component function Ground(; name) +@component function Ground(; name) systems = @named begin - g = Pin() + g = Pin() end eqs = [ - g.v ~ 0 + g.v ~ 0 ] return ODESystem(eqs, t, [], []; systems, name) - end +end - @component function Inductor(; name, L=1.0) +@component function Inductor(; name, L = 1.0) systems = @named begin - p = Pin() - n = Pin() + p = Pin() + n = Pin() end vars = @variables begin - v(t), [guess = 0.0] - i(t), [guess = 0.0] + v(t), [guess = 0.0] + i(t), [guess = 0.0] end params = @parameters begin - (L = L) + (L = L) end - eqs = [ - v ~ p.v - n.v - i ~ p.i - p.i + n.i ~ 0 - L * D(i) ~ v - ] + eqs = [v ~ p.v - n.v + i ~ p.i + p.i + n.i ~ 0 + L * D(i) ~ v] return ODESystem(eqs, t, vars, params; systems, name) - end +end """ This is an RLC model. This should support markdown. That includes HTML as well. """ @component function RLCModel(; name) - systems = @named begin - resistor = Resistor(R=100) - capacitor = Capacitor(C=0.001) - inductor = Inductor(L=1) - source = ConstantVoltage(V=30) - ground = Ground() - end - initialization_eqs = [ - inductor.i ~ 0 - ] - 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) + systems = @named begin + resistor = Resistor(R = 100) + capacitor = Capacitor(C = 0.001) + inductor = Inductor(L = 1) + source = ConstantVoltage(V = 30) + ground = Ground() + end + initialization_eqs = [ + inductor.i ~ 0 + ] + 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) end """Run model RLCModel from 0 to 10""" function simple() - @mtkbuild model = RLCModel() - u0 = [] - prob = ODEProblem(model, u0, (0.0, 10.0)) - sol = solve(prob) + @mtkbuild model = RLCModel() + u0 = [] + prob = ODEProblem(model, u0, (0.0, 10.0)) + sol = solve(prob) end @test SciMLBase.successful_retcode(simple()) From 19cfdad84df705caa0ef075e7408eb17f5f9f1aa Mon Sep 17 00:00:00 2001 From: Christopher Rackauckas Date: Thu, 18 Apr 2024 14:08:41 -0400 Subject: [PATCH 155/316] fix parens --- src/systems/diffeqs/abstractodesystem.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/systems/diffeqs/abstractodesystem.jl b/src/systems/diffeqs/abstractodesystem.jl index e9996d7716..baa2e3d82c 100644 --- a/src/systems/diffeqs/abstractodesystem.jl +++ b/src/systems/diffeqs/abstractodesystem.jl @@ -908,10 +908,10 @@ function process_DEProblem(constructor, sys::AbstractODESystem, u0map, parammap; # TODO: make it work with clocks # ModelingToolkit.get_tearing_state(sys) !== nothing => Requires structural_simplify first if sys isa ODESystem && build_initializeprob && - ((implicit_dae || !isempty(missingvars)) && + (((implicit_dae || !isempty(missingvars)) && all(isequal(Continuous()), ci.var_domain) && ModelingToolkit.get_tearing_state(sys) !== nothing) || - !isempty(initialization_equations(sys)) && t !== nothing + !isempty(initialization_equations(sys))) && t !== nothing if eltype(u0map) <: Number u0map = unknowns(sys) .=> u0map end From f9ee383c120016c18d420b5f68e5936410edd58f Mon Sep 17 00:00:00 2001 From: Christopher Rackauckas Date: Thu, 18 Apr 2024 14:26:32 -0400 Subject: [PATCH 156/316] format --- src/systems/diffeqs/abstractodesystem.jl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/systems/diffeqs/abstractodesystem.jl b/src/systems/diffeqs/abstractodesystem.jl index baa2e3d82c..d48b2615d0 100644 --- a/src/systems/diffeqs/abstractodesystem.jl +++ b/src/systems/diffeqs/abstractodesystem.jl @@ -909,9 +909,9 @@ function process_DEProblem(constructor, sys::AbstractODESystem, u0map, parammap; # ModelingToolkit.get_tearing_state(sys) !== nothing => Requires structural_simplify first if sys isa ODESystem && build_initializeprob && (((implicit_dae || !isempty(missingvars)) && - all(isequal(Continuous()), ci.var_domain) && - ModelingToolkit.get_tearing_state(sys) !== nothing) || - !isempty(initialization_equations(sys))) && t !== nothing + all(isequal(Continuous()), ci.var_domain) && + ModelingToolkit.get_tearing_state(sys) !== nothing) || + !isempty(initialization_equations(sys))) && t !== nothing if eltype(u0map) <: Number u0map = unknowns(sys) .=> u0map end From a8bae3c2bbab597b3b81ca04081f1f71f4624a0f Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Wed, 5 Jun 2024 19:39:59 +0530 Subject: [PATCH 157/316] feat: allow specifying variable names in `modelingtoolkitize` --- src/systems/diffeqs/modelingtoolkitize.jl | 125 ++++++++++++++---- src/systems/nonlinear/modelingtoolkitize.jl | 40 +++++- .../optimization/modelingtoolkitize.jl | 89 +++++++++++-- 3 files changed, 209 insertions(+), 45 deletions(-) diff --git a/src/systems/diffeqs/modelingtoolkitize.jl b/src/systems/diffeqs/modelingtoolkitize.jl index d398778b33..34cae293b2 100644 --- a/src/systems/diffeqs/modelingtoolkitize.jl +++ b/src/systems/diffeqs/modelingtoolkitize.jl @@ -3,21 +3,36 @@ $(TYPEDSIGNATURES) Generate `ODESystem`, dependent variables, and parameters from an `ODEProblem`. """ -function modelingtoolkitize(prob::DiffEqBase.ODEProblem; kwargs...) +function modelingtoolkitize( + prob::DiffEqBase.ODEProblem; u_names = nothing, p_names = nothing, kwargs...) prob.f isa DiffEqBase.AbstractParameterizedFunction && return prob.f.sys - @parameters t - + t = t_nounits p = prob.p has_p = !(p isa Union{DiffEqBase.NullParameters, Nothing}) - _vars = define_vars(prob.u0, t) + 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 - _params = define_params(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 ? _params : + (p isa Tuple || p isa NamedTuple || p isa AbstractDict || p isa MTKParameters ? + _params : ArrayInterface.restructure(p, _params)) else [] @@ -25,7 +40,7 @@ function modelingtoolkitize(prob::DiffEqBase.ODEProblem; kwargs...) var_set = Set(vars) - D = Differential(t) + D = D_nounits mm = prob.f.mass_matrix if mm === I @@ -70,6 +85,8 @@ function modelingtoolkitize(prob::DiffEqBase.ODEProblem; kwargs...) 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 @@ -125,44 +142,96 @@ function Base.showerror(io::IO, e::ModelingtoolkitizeParametersNotSupportedError println(io, e.type) end -function define_params(p) +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) - [toparam(variable(:α, i)) for i in eachindex(p)] +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) - [toparam(variable(:α))] +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) - OrderedDict(k => toparam(variable(:α, i)) for (i, k) in zip(1:length(p), keys(p))) +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::Union{SLArray, LArray}) - [toparam(variable(x)) for x in LabelledArrays.symnames(typeof(p))] +function define_params(p::Union{SLArray, LArray}, names = nothing) + if names === nothing + [toparam(variable(x)) for x in LabelledArrays.symnames(typeof(p))] + else + varnames_length_check(p, names) + [toparam(variable(names[i])) for i in eachindex(p)] + end end -function define_params(p::Tuple) - tuple((toparam(variable(:α, i)) for i in eachindex(p))...) +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) - NamedTuple(x => toparam(variable(x)) for x in keys(p)) +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) - bufs = (p...,) - i = 1 - ps = [] - for buf in bufs - for _ in buf - push!(ps, toparam(variable(:α, i))) +function define_params(p::MTKParameters, names = nothing) + if names === nothing + bufs = (p...,) + i = 1 + ps = [] + for buf in bufs + for _ in buf + push!( + ps, + if names === nothing + toparam(variable(:α, i)) + else + toparam(variable(names[i])) + end + ) + end end + return identity.(ps) + else + return collect(values(names)) end - return identity.(ps) end """ diff --git a/src/systems/nonlinear/modelingtoolkitize.jl b/src/systems/nonlinear/modelingtoolkitize.jl index 843e260406..6212b5fe73 100644 --- a/src/systems/nonlinear/modelingtoolkitize.jl +++ b/src/systems/nonlinear/modelingtoolkitize.jl @@ -3,17 +3,34 @@ $(TYPEDSIGNATURES) Generate `NonlinearSystem`, dependent variables, and parameters from an `NonlinearProblem`. """ -function modelingtoolkitize(prob::NonlinearProblem; kwargs...) +function modelingtoolkitize( + prob::NonlinearProblem; u_names = nothing, p_names = nothing, kwargs...) p = prob.p has_p = !(p isa Union{DiffEqBase.NullParameters, Nothing}) - _vars = reshape([variable(:x, i) for i in eachindex(prob.u0)], size(prob.u0)) + 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 - _params = define_params(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 ? _params : + (p isa Tuple || p isa NamedTuple || p isa AbstractDict || p isa MTKParameters ? + _params : ArrayInterface.restructure(p, _params)) else [] @@ -29,14 +46,25 @@ function modelingtoolkitize(prob::NonlinearProblem; kwargs...) eqs = vcat([0.0 ~ rhs[i] for i in 1:length(out_def)]...) 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 = has_p ? Dict(params .=> vec(collect(prob.p))) : Dict() + 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 = NonlinearSystem(eqs, sts, params, defaults = merge(default_u0, default_p); diff --git a/src/systems/optimization/modelingtoolkitize.jl b/src/systems/optimization/modelingtoolkitize.jl index 9c419e0b24..5e419093ad 100644 --- a/src/systems/optimization/modelingtoolkitize.jl +++ b/src/systems/optimization/modelingtoolkitize.jl @@ -3,25 +3,70 @@ $(TYPEDSIGNATURES) Generate `OptimizationSystem`, dependent variables, and parameters from an `OptimizationProblem`. """ -function modelingtoolkitize(prob::DiffEqBase.OptimizationProblem; kwargs...) +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 - - vars = ArrayInterface.restructure(prob.u0, - [variable(:x, i) for i in eachindex(prob.u0)]) - params = if p isa DiffEqBase.NullParameters - [] - elseif p isa MTKParameters - [variable(:α, i) for i in eachindex(vcat(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] + 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) + 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, full_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 - ArrayInterface.restructure(p, [variable(:α, i) for i in eachindex(p)]) + [] end - eqs = prob.f(vars, params) + 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) @@ -58,10 +103,32 @@ function modelingtoolkitize(prob::DiffEqBase.OptimizationProblem; kwargs...) 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 - de = OptimizationSystem(eqs, vec(vars), vec(toparam.(params)); + 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 9ed1b7371c09018337452afa24a05cfd492ac1cd Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Fri, 7 Jun 2024 11:26:06 +0530 Subject: [PATCH 158/316] test: add extensive modelingtoolkitize tests --- test/modelingtoolkitize.jl | 172 ++++++++++++++++++++++++++++++++++++- 1 file changed, 169 insertions(+), 3 deletions(-) diff --git a/test/modelingtoolkitize.jl b/test/modelingtoolkitize.jl index ec582e4145..996d333f2f 100644 --- a/test/modelingtoolkitize.jl +++ b/test/modelingtoolkitize.jl @@ -1,5 +1,8 @@ using OrdinaryDiffEq, ModelingToolkit, DataStructures, Test using Optimization, RecursiveArrayTools, OptimizationOptimJL +using LabelledArrays, SymbolicIndexingInterface +using ModelingToolkit: t_nounits as t, D_nounits as D +using SciMLBase: parameterless_type N = 32 const xyd_brusselator = range(0, stop = 1, length = N) @@ -252,7 +255,6 @@ u0 = @LArray [9998.0, 1.0, 1.0, 1.0] (:S, :I, :R, :C) problem = ODEProblem(SIR!, u0, tspan, p) sys = complete(modelingtoolkitize(problem)) -@parameters t @test all(isequal.(parameters(sys), getproperty.(@variables(β, η, ω, φ, σ, μ), :val))) @test all(isequal.(Symbol.(unknowns(sys)), Symbol.(@variables(S(t), I(t), R(t), C(t))))) @@ -291,9 +293,8 @@ sys = modelingtoolkitize(prob) @test [ModelingToolkit.defaults(sys)[s] for s in parameters(sys)] == [10, 20] @test ModelingToolkit.has_tspan(sys) -@parameters t sig=10 rho=28.0 beta=8 / 3 +@parameters sig=10 rho=28.0 beta=8 / 3 @variables x(t)=100 y(t)=1.0 z(t)=1 -D = Differential(t) eqs = [D(x) ~ sig * (y - x), D(y) ~ x * (rho - z) - y, @@ -307,3 +308,168 @@ noiseeqs = [0.1 * x, prob = SDEProblem(complete(sys)) sys = modelingtoolkitize(prob) @test ModelingToolkit.has_tspan(sys) + +@testset "Explicit variable names" begin + function fn(du, u, p::NamedTuple, t) + du[1] = u[1] + p.a * u[2] + du[2] = u[2] + p.b * u[1] + end + function fn(du, u, p::AbstractDict, t) + du[1] = u[1] + p[:a] * u[2] + du[2] = u[2] + p[:b] * u[1] + end + function fn(du, u, p, t) + du[1] = u[1] + p[1] * u[2] + du[2] = u[2] + p[2] * u[1] + end + function fn(du, u, p::Real, t) + du[1] = u[1] + p * u[2] + du[2] = u[2] + p * u[1] + end + function nl_fn(u, p::NamedTuple) + [u[1] + p.a * u[2], u[2] + p.b * u[1]] + end + function nl_fn(u, p::AbstractDict) + [u[1] + p[:a] * u[2], u[2] + p[:b] * u[1]] + end + function nl_fn(u, p) + [u[1] + p[1] * u[2], u[2] + p[2] * u[1]] + end + function nl_fn(u, p::Real) + [u[1] + p * u[2], u[2] + p * u[1]] + end + params = (a = 1, b = 1) + odeprob = ODEProblem(fn, [1 1], (0, 1), params) + nlprob = NonlinearProblem(nl_fn, [1, 1], params) + optprob = OptimizationProblem(nl_fn, [1, 1], params) + + @testset "$(parameterless_type(prob))" for prob in [optprob] + sys = modelingtoolkitize(prob, u_names = [:a, :b]) + @test is_variable(sys, sys.a) + @test is_variable(sys, sys.b) + @test is_variable(sys, :a) + @test is_variable(sys, :b) + + @test_throws ["unknowns", "2", "does not match", "names", "3"] modelingtoolkitize( + prob, u_names = [:a, :b, :c]) + for (pvals, pnames) in [ + ([1, 2], [:p, :q]), + ((1, 2), [:p, :q]), + ([1, 2], Dict(1 => :p, 2 => :q)), + ((1, 2), Dict(1 => :p, 2 => :q)), + (1.0, :p), + (1.0, [:p]), + (1.0, Dict(1 => :p)), + (Dict(:a => 2, :b => 4), Dict(:a => :p, :b => :q)), + (LVector(a = 1, b = 2), [:p, :q]), + (SLVector(a = 1, b = 2), [:p, :q]), + (LVector(a = 1, b = 2), Dict(1 => :p, 2 => :q)), + (SLVector(a = 1, b = 2), Dict(1 => :p, 2 => :q)), + ((a = 1, b = 2), (a = :p, b = :q)), + ((a = 1, b = 2), Dict(:a => :p, :b => :q)) + ] + if pvals isa NamedTuple && prob isa OptimizationProblem + continue + end + sys = modelingtoolkitize( + remake(prob, p = pvals, interpret_symbolicmap = false), p_names = pnames) + if pnames isa Symbol + @test is_parameter(sys, pnames) + continue + end + for p in values(pnames) + @test is_parameter(sys, p) + end + end + + for (pvals, pnames) in [ + ([1, 2], [:p, :q, :r]), + ((1, 2), [:p, :q, :r]), + ([1, 2], Dict(1 => :p, 2 => :q, 3 => :r)), + ((1, 2), Dict(1 => :p, 2 => :q, 3 => :r)), + (1.0, [:p, :q]), + (1.0, Dict(1 => :p, 2 => :q)), + (Dict(:a => 2, :b => 4), Dict(:a => :p, :b => :q, :c => :r)), + (LVector(a = 1, b = 2), [:p, :q, :r]), + (SLVector(a = 1, b = 2), [:p, :q, :r]), + (LVector(a = 1, b = 2), Dict(1 => :p, 2 => :q, 3 => :r)), + (SLVector(a = 1, b = 2), Dict(1 => :p, 2 => :q, 3 => :r)), + ((a = 1, b = 2), (a = :p, b = :q, c = :r)), + ((a = 1, b = 2), Dict(:a => :p, :b => :q, :c => :r)) + ] + newprob = remake(prob, p = pvals, interpret_symbolicmap = false) + @test_throws [ + "parameters", "$(length(pvals))", "does not match", "$(length(pnames))"] modelingtoolkitize( + newprob, p_names = pnames) + end + + sc = SymbolCache([:a, :b], [:p, :q]) + sci_f = parameterless_type(prob.f)(prob.f.f, sys = sc) + newprob = remake(prob, f = sci_f, p = [1, 2]) + sys = modelingtoolkitize(newprob) + @test is_variable(sys, sys.a) + @test is_variable(sys, sys.b) + @test is_variable(sys, :a) + @test is_variable(sys, :b) + @test is_parameter(sys, sys.p) + @test is_parameter(sys, sys.q) + @test is_parameter(sys, :p) + @test is_parameter(sys, :q) + end + + @testset "From MTK model" begin + @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) + prob1 = ODEProblem(sys, [], (0.0, 5.0)) + newsys = complete(modelingtoolkitize(prob1)) + @test is_variable(newsys, newsys.x) + @test is_variable(newsys, newsys.y) + @test is_parameter(newsys, newsys.p) + @test is_parameter(newsys, newsys.q) + prob2 = ODEProblem(newsys, [], (0.0, 5.0)) + + sol1 = solve(prob1, Tsit5()) + sol2 = solve(prob2, Tsit5()) + @test sol1 ≈ sol2 + end + @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]) + prob1 = NonlinearProblem(nlsys, []) + newsys = complete(modelingtoolkitize(prob1)) + @test is_variable(newsys, newsys.x) + @test is_variable(newsys, newsys.y) + @test is_parameter(newsys, newsys.p) + @test is_parameter(newsys, newsys.q) + prob2 = NonlinearProblem(newsys, []) + + sol1 = solve(prob1) + sol2 = solve(prob2) + @test sol1 ≈ sol2 + end + @testset "Optimization" begin + @variables begin + x = 1.0, [bounds = (-2.0, 10.0)] + y = 2.0, [bounds = (-1.0, 10.0)] + end + @parameters p=3.0 q=4.0 + loss = (p - x)^2 + q * (y - x^2)^2 + @mtkbuild optsys = OptimizationSystem(loss, [x, y], [p, q]) + prob1 = OptimizationProblem(optsys, [], grad = true, hess = true) + newsys = complete(modelingtoolkitize(prob1)) + @test is_variable(newsys, newsys.x) + @test is_variable(newsys, newsys.y) + @test is_parameter(newsys, newsys.p) + @test is_parameter(newsys, newsys.q) + prob2 = OptimizationProblem(newsys, [], grad = true, hess = true) + + sol1 = solve(prob1, GradientDescent()) + sol2 = solve(prob2, GradientDescent()) + + @test sol1 ≈ sol2 + end + end +end From f2bbd06c4d7ecbb3bb09bc44b601980b5bfc246a Mon Sep 17 00:00:00 2001 From: Christopher Rackauckas Date: Sat, 8 Jun 2024 06:46:48 -0400 Subject: [PATCH 159/316] last few fixes --- Project.toml | 2 +- src/systems/abstractsystem.jl | 7 ------- 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/Project.toml b/Project.toml index 9e7eaef115..4477027f53 100644 --- a/Project.toml +++ b/Project.toml @@ -93,7 +93,7 @@ LinearAlgebra = "1" MLStyle = "0.4.17" NaNMath = "0.3, 1" OrderedCollections = "1" -OrdinaryDiffEq = "6.73.0" +OrdinaryDiffEq = "6.82.0" PrecompileTools = "1" RecursiveArrayTools = "2.3, 3" Reexport = "0.2, 1" diff --git a/src/systems/abstractsystem.jl b/src/systems/abstractsystem.jl index cf65d31067..e4eb67bc50 100644 --- a/src/systems/abstractsystem.jl +++ b/src/systems/abstractsystem.jl @@ -905,13 +905,6 @@ function namespace_equation(eq::Equation, _lhs ~ _rhs end -function namespace_initialization_equations( - sys::AbstractSystem, ivs = independent_variables(sys)) - eqs = initialization_equations(sys) - isempty(eqs) && return Equation[] - map(eq -> namespace_equation(eq, sys; ivs), eqs) -end - function namespace_assignment(eq::Assignment, sys) _lhs = namespace_expr(eq.lhs, sys) _rhs = namespace_expr(eq.rhs, sys) From a5bff261dcfcb27385ef575c17b5a306074be75b Mon Sep 17 00:00:00 2001 From: Christopher Rackauckas Date: Sat, 8 Jun 2024 09:06:52 -0400 Subject: [PATCH 160/316] Fix typos --- docs/src/examples/remake.md | 2 +- docs/src/tutorials/discrete_system.md | 2 +- src/clock.jl | 2 +- src/systems/abstractsystem.jl | 6 +++--- src/systems/clock_inference.jl | 10 +++++----- src/systems/systemstructure.jl | 2 +- test/clock.jl | 8 ++++---- test/parameter_dependencies.jl | 2 +- 8 files changed, 17 insertions(+), 17 deletions(-) diff --git a/docs/src/examples/remake.md b/docs/src/examples/remake.md index de6aa77d74..f4396ef7a7 100644 --- a/docs/src/examples/remake.md +++ b/docs/src/examples/remake.md @@ -133,7 +133,7 @@ over the various methods, listing their use cases. This method is the most generic. It can handle symbolic maps, initializations of parameters/states dependent on each other and partial updates. However, this comes at the -cost of performance. `remake` is also not always inferrable. +cost of performance. `remake` is also not always inferable. ## `remake` and `setp`/`setu` diff --git a/docs/src/tutorials/discrete_system.md b/docs/src/tutorials/discrete_system.md index 666125e20e..c45c7e112e 100644 --- a/docs/src/tutorials/discrete_system.md +++ b/docs/src/tutorials/discrete_system.md @@ -43,7 +43,7 @@ the Fibonacci series: ``` The "default value" here should be interpreted as the value of `x` at all past timesteps. -For example, here `x(k-1)` and `x(k-2)` will be `1.0`, and the inital value of `x(k)` will +For example, here `x(k-1)` and `x(k-2)` will be `1.0`, and the initialvalue of `x(k)` will thus be `2.0`. During problem construction, the _past_ value of a variable should be provided. For example, providing `[x => 1.0]` while constructing this problem will error. Provide `[x(k-1) => 1.0]` instead. Note that values provided during problem construction diff --git a/src/clock.jl b/src/clock.jl index 4cf5b9170b..5df6cfb022 100644 --- a/src/clock.jl +++ b/src/clock.jl @@ -130,7 +130,7 @@ is_concrete_time_domain(x) = x isa Union{AbstractClock, Continuous} SolverStepClock() SolverStepClock(t) -A clock that ticks at each solver step (sometimes referred to as "continuous sample time"). This clock **does generally not have equidistant tick intervals**, instead, the tick interval depends on the adaptive step-size slection of the continuous solver, as well as any continuous event handling. If adaptivity of the solver is turned off and there are no continuous events, the tick interval will be given by the fixed solver time step `dt`. +A clock that ticks at each solver step (sometimes referred to as "continuous sample time"). This clock **does generally not have equidistant tick intervals**, instead, the tick interval depends on the adaptive step-size selection of the continuous solver, as well as any continuous event handling. If adaptivity of the solver is turned off and there are no continuous events, the tick interval will be given by the fixed solver time step `dt`. Due to possibly non-equidistant tick intervals, this clock should typically not be used with discrete-time systems that assume a fixed sample time, such as PID controllers and digital filters. """ diff --git a/src/systems/abstractsystem.jl b/src/systems/abstractsystem.jl index 6b4c1a487c..4df1be2589 100644 --- a/src/systems/abstractsystem.jl +++ b/src/systems/abstractsystem.jl @@ -2341,11 +2341,11 @@ function Base.showerror(io::IO, e::ExtraEquationsSystemException) print(io, "ExtraEquationsSystemException: ", e.msg) end -struct HybridSystemNotSupportedExcpetion <: Exception +struct HybridSystemNotSupportedException <: Exception msg::String end -function Base.showerror(io::IO, e::HybridSystemNotSupportedExcpetion) - print(io, "HybridSystemNotSupportedExcpetion: ", e.msg) +function Base.showerror(io::IO, e::HybridSystemNotSupportedException) + print(io, "HybridSystemNotSupportedException: ", e.msg) end function AbstractTrees.children(sys::ModelingToolkit.AbstractSystem) diff --git a/src/systems/clock_inference.jl b/src/systems/clock_inference.jl index 120daccb70..d7a47f1f1f 100644 --- a/src/systems/clock_inference.jl +++ b/src/systems/clock_inference.jl @@ -21,9 +21,9 @@ function ClockInference(ts::TransformationState) ClockInference(ts, eq_domain, var_domain, inferred) end -struct NotInferedTimeDomain end +struct NotInferredTimeDomain end function error_sample_time(eq) - error("$eq\ncontains `SampleTime` but it is not an infered discrete equation.") + error("$eq\ncontains `SampleTime` but it is not an Inferred discrete equation.") end function substitute_sample_time(ci::ClockInference, ts::TearingState) @unpack eq_domain = ci @@ -34,7 +34,7 @@ function substitute_sample_time(ci::ClockInference, ts::TearingState) domain = eq_domain[i] dt = sampletime(domain) neweq = substitute_sample_time(eq, dt) - if neweq isa NotInferedTimeDomain + if neweq isa NotInferredTimeDomain error_sample_time(eq) end eqs[i] = neweq @@ -52,13 +52,13 @@ function substitute_sample_time(ex, dt) op = operation(ex) args = arguments(ex) if op == SampleTime - dt === nothing && return NotInferedTimeDomain() + dt === nothing && return NotInferredTimeDomain() return dt else new_args = similar(args) for (i, arg) in enumerate(args) ex_arg = substitute_sample_time(arg, dt) - if ex_arg isa NotInferedTimeDomain + if ex_arg isa NotInferredTimeDomain return ex_arg end new_args[i] = ex_arg diff --git a/src/systems/systemstructure.jl b/src/systems/systemstructure.jl index 78b1a1bb50..befc0ac0bd 100644 --- a/src/systems/systemstructure.jl +++ b/src/systems/systemstructure.jl @@ -636,7 +636,7 @@ function structural_simplify!(state::TearingState, io = nothing; simplify = fals kwargs...) if length(tss) > 1 if continuous_id > 0 - throw(HybridSystemNotSupportedExcpetion("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/")) + 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)) diff --git a/test/clock.jl b/test/clock.jl index 9429da1951..86967365ad 100644 --- a/test/clock.jl +++ b/test/clock.jl @@ -105,7 +105,7 @@ eqs = [yd ~ Sample(t, dt)(y) D(x) ~ -x + u y ~ x] @named sys = ODESystem(eqs, t) -@test_throws ModelingToolkit.HybridSystemNotSupportedExcpetion ss=structural_simplify(sys); +@test_throws ModelingToolkit.HybridSystemNotSupportedException ss=structural_simplify(sys); @test_skip begin Tf = 1.0 @@ -500,10 +500,10 @@ eqs = [yd ~ Sample(t, dt)(y) @mtkmodel FirstOrderWithStepCounter begin @components begin counter = CounterSys() - fo = FirstOrderSys() + firstorder = FirstOrderSys() end @equations begin - counter.u ~ fo.x + counter.u ~ firstorder.x end end @@ -511,7 +511,7 @@ eqs = [yd ~ Sample(t, dt)(y) prob = ODEProblem(model, [], (0.0, 10.0)) sol = solve(prob, Tsit5(), kwargshandle = KeywordArgSilent) - @test sol.prob.kwargs[:disc_saved_values][1].t == sol.t[1:2:end] # Test that the discrete-tiem system executed at every step of the continuous solver. The solver saves each time step twice, one state value before discrete affect and one after. + @test sol.prob.kwargs[:disc_saved_values][1].t == sol.t[1:2:end] # Test that the discrete-time system executed at every step of the continuous solver. The solver saves each time step twice, one state value before discrete affect and one after. @test_nowarn ModelingToolkit.build_explicit_observed_function( model, model.counter.ud)(sol.u[1], prob.p..., sol.t[1]) diff --git a/test/parameter_dependencies.jl b/test/parameter_dependencies.jl index 904b62ca3f..ef446e9630 100644 --- a/test/parameter_dependencies.jl +++ b/test/parameter_dependencies.jl @@ -167,7 +167,7 @@ end D(x) ~ -x + u y ~ x z(k) ~ z(k - 2) + yd(k - 2)] - @test_throws ModelingToolkit.HybridSystemNotSupportedExcpetion @mtkbuild sys = ODESystem( + @test_throws ModelingToolkit.HybridSystemNotSupportedException @mtkbuild sys = ODESystem( eqs, t; parameter_dependencies = [kq => 2kp]) @test_skip begin From fa02ee783fcffab887d7f5f10c1837738bb2e4d1 Mon Sep 17 00:00:00 2001 From: Christopher Rackauckas Date: Sat, 8 Jun 2024 09:08:04 -0400 Subject: [PATCH 161/316] Update docs/src/tutorials/discrete_system.md --- docs/src/tutorials/discrete_system.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/tutorials/discrete_system.md b/docs/src/tutorials/discrete_system.md index c45c7e112e..8f6828fde7 100644 --- a/docs/src/tutorials/discrete_system.md +++ b/docs/src/tutorials/discrete_system.md @@ -43,7 +43,7 @@ the Fibonacci series: ``` The "default value" here should be interpreted as the value of `x` at all past timesteps. -For example, here `x(k-1)` and `x(k-2)` will be `1.0`, and the initialvalue of `x(k)` will +For example, here `x(k-1)` and `x(k-2)` will be `1.0`, and the initial value of `x(k)` will thus be `2.0`. During problem construction, the _past_ value of a variable should be provided. For example, providing `[x => 1.0]` while constructing this problem will error. Provide `[x(k-1) => 1.0]` instead. Note that values provided during problem construction From 39885d841dbeec8d3711e93b7a6dadc06af29927 Mon Sep 17 00:00:00 2001 From: Christopher Rackauckas Date: Sat, 8 Jun 2024 12:02:02 -0400 Subject: [PATCH 162/316] Add propagation of guesses from parameters and observed Fixes https://github.com/SciML/ModelingToolkit.jl/issues/2716 --- src/systems/diffeqs/abstractodesystem.jl | 7 ++- src/systems/nonlinear/initializesystem.jl | 2 +- src/utils.jl | 12 ++++ test/guess_propagation.jl | 75 +++++++++++++++++++++++ test/runtests.jl | 1 + 5 files changed, 94 insertions(+), 3 deletions(-) create mode 100644 test/guess_propagation.jl diff --git a/src/systems/diffeqs/abstractodesystem.jl b/src/systems/diffeqs/abstractodesystem.jl index d48b2615d0..8ba14a1874 100644 --- a/src/systems/diffeqs/abstractodesystem.jl +++ b/src/systems/diffeqs/abstractodesystem.jl @@ -790,7 +790,8 @@ function get_u0_p(sys, @warn "Observed variables cannot be assigned initial values. Initial values for $u0s_in_obs will be ignored." end end - defs = mergedefaults(defs, u0map, dvs) + observedmap = todict(map(x->x.rhs => x.lhs,observed(sys))) + defs = mergedefaults(defs, observedmap, u0map, dvs) for (k, v) in defs if Symbolics.isarraysymbolic(k) ks = scalarize(k) @@ -821,7 +822,9 @@ function get_u0( if parammap !== nothing defs = mergedefaults(defs, parammap, ps) end - defs = mergedefaults(defs, u0map, dvs) + obs = map(x->x.rhs => x.lhs, observed(sys)) + observedmap = isempty(obs) ? Dict() : todict(obs) + defs = mergedefaults(defs, observedmap, u0map, dvs) if symbolic_u0 u0 = varmap_to_vars( u0map, dvs; defaults = defs, tofloat = false, use_union = false, toterm) diff --git a/src/systems/nonlinear/initializesystem.jl b/src/systems/nonlinear/initializesystem.jl index ba916f420d..96f1d1d670 100644 --- a/src/systems/nonlinear/initializesystem.jl +++ b/src/systems/nonlinear/initializesystem.jl @@ -31,7 +31,7 @@ function generate_initializesystem(sys::ODESystem; schedule = getfield(sys, :schedule) if schedule !== nothing - guessmap = [x[2] => get(guesses, x[1], default_dd_value) + guessmap = [x[1] => get(guesses, x[1], default_dd_value) for x in schedule.dummy_sub] dd_guess = Dict(filter(x -> !isnothing(x[1]), guessmap)) if u0map === nothing || isempty(u0map) diff --git a/src/utils.jl b/src/utils.jl index 9476cded7d..53f9130ea1 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -617,6 +617,18 @@ function mergedefaults(defaults, varmap, vars) end end +function mergedefaults(defaults, observedmap, varmap, vars) + defs = if varmap isa Dict + merge(observedmap, defaults, varmap) + elseif eltype(varmap) <: Pair + merge(observedmap, defaults, Dict(varmap)) + elseif eltype(varmap) <: Number + merge(observedmap, defaults, Dict(zip(vars, varmap))) + else + merge(observedmap, defaults) + end +end + @noinline function throw_missingvars_in_sys(vars) throw(ArgumentError("$vars are either missing from the variable map or missing from the system's unknowns/parameters list.")) end diff --git a/test/guess_propagation.jl b/test/guess_propagation.jl new file mode 100644 index 0000000000..fc7618ee2b --- /dev/null +++ b/test/guess_propagation.jl @@ -0,0 +1,75 @@ +using ModelingToolkit, OrdinaryDiffEq +using ModelingToolkit: D, t_nounits as t +using Test + +# Standard case + +@variables x(t) [guess = 2] +@variables y(t) +eqs = [D(x) ~ 1 + x ~ y] +initialization_eqs = [1 ~ exp(1 + x)] + +@named sys = ODESystem(eqs, t; initialization_eqs) +sys = complete(structural_simplify(sys)) +tspan = (0.0, 0.2) +prob = ODEProblem(sys, [], tspan, []) + +@test prob.f.initializeprob[y] == 2.0 +@test prob.f.initializeprob[x] == 2.0 +sol = solve(prob.f.initializeprob; show_trace=Val(true)) + +# Guess via observed + +@variables x(t) +@variables y(t) [guess = 2] +eqs = [D(x) ~ 1 + x ~ y] +initialization_eqs = [1 ~ exp(1 + x)] + +@named sys = ODESystem(eqs, t; initialization_eqs) +sys = complete(structural_simplify(sys)) +tspan = (0.0, 0.2) +prob = ODEProblem(sys, [], tspan, []) + +@test prob.f.initializeprob[x] == 2.0 +@test prob.f.initializeprob[y] == 2.0 +sol = solve(prob.f.initializeprob; show_trace=Val(true)) + +# Guess via parameter + +@parameters a = -1.0 +@variables x(t) [guess = a] + +eqs = [D(x) ~ a] + +initialization_eqs = [1 ~ exp(1 + x)] + +@named sys = ODESystem(eqs, t; initialization_eqs) +sys = complete(structural_simplify(sys)) + +tspan = (0.0, 0.2) +prob = ODEProblem(sys, [], tspan, []) + +@test prob.f.initializeprob[x] == -1.0 +sol = solve(prob.f.initializeprob; show_trace=Val(true)) + +# Guess via observed parameter + +@parameters a = -1.0 +@variables x(t) +@variables y(t) [guess = a] + +eqs = [D(x) ~ a, + y ~ x] + +initialization_eqs = [1 ~ exp(1 + x)] + +@named sys = ODESystem(eqs, t; initialization_eqs) +sys = complete(structural_simplify(sys)) + +tspan = (0.0, 0.2) +prob = ODEProblem(sys, [], tspan, []) + +@test prob.f.initializeprob[x] == -1.0 +sol = solve(prob.f.initializeprob; show_trace=Val(true)) diff --git a/test/runtests.jl b/test/runtests.jl index 7315fee7c7..263b91f1e9 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -38,6 +38,7 @@ end @safetestset "DDESystem Test" include("dde.jl") @safetestset "NonlinearSystem Test" include("nonlinearsystem.jl") @safetestset "InitializationSystem Test" include("initializationsystem.jl") + @safetestset "Guess Propagation" include("guess_propagation.jl") @safetestset "Hierarchical Initialization Equations" include("hierarchical_initialization_eqs.jl") @safetestset "PDE Construction Test" include("pde.jl") @safetestset "JumpSystem Test" include("jumpsystem.jl") From cdb38ffe959c3edcda5f0a29a01cceed7781aa62 Mon Sep 17 00:00:00 2001 From: Christopher Rackauckas Date: Sat, 8 Jun 2024 12:13:15 -0400 Subject: [PATCH 163/316] format --- src/systems/diffeqs/abstractodesystem.jl | 4 ++-- test/guess_propagation.jl | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/systems/diffeqs/abstractodesystem.jl b/src/systems/diffeqs/abstractodesystem.jl index 8ba14a1874..e85d77ebd7 100644 --- a/src/systems/diffeqs/abstractodesystem.jl +++ b/src/systems/diffeqs/abstractodesystem.jl @@ -790,7 +790,7 @@ function get_u0_p(sys, @warn "Observed variables cannot be assigned initial values. Initial values for $u0s_in_obs will be ignored." end end - observedmap = todict(map(x->x.rhs => x.lhs,observed(sys))) + observedmap = todict(map(x -> x.rhs => x.lhs, observed(sys))) defs = mergedefaults(defs, observedmap, u0map, dvs) for (k, v) in defs if Symbolics.isarraysymbolic(k) @@ -822,7 +822,7 @@ function get_u0( if parammap !== nothing defs = mergedefaults(defs, parammap, ps) end - obs = map(x->x.rhs => x.lhs, observed(sys)) + obs = map(x -> x.rhs => x.lhs, observed(sys)) observedmap = isempty(obs) ? Dict() : todict(obs) defs = mergedefaults(defs, observedmap, u0map, dvs) if symbolic_u0 diff --git a/test/guess_propagation.jl b/test/guess_propagation.jl index fc7618ee2b..709fe9aa54 100644 --- a/test/guess_propagation.jl +++ b/test/guess_propagation.jl @@ -17,7 +17,7 @@ prob = ODEProblem(sys, [], tspan, []) @test prob.f.initializeprob[y] == 2.0 @test prob.f.initializeprob[x] == 2.0 -sol = solve(prob.f.initializeprob; show_trace=Val(true)) +sol = solve(prob.f.initializeprob; show_trace = Val(true)) # Guess via observed @@ -34,7 +34,7 @@ prob = ODEProblem(sys, [], tspan, []) @test prob.f.initializeprob[x] == 2.0 @test prob.f.initializeprob[y] == 2.0 -sol = solve(prob.f.initializeprob; show_trace=Val(true)) +sol = solve(prob.f.initializeprob; show_trace = Val(true)) # Guess via parameter @@ -52,7 +52,7 @@ tspan = (0.0, 0.2) prob = ODEProblem(sys, [], tspan, []) @test prob.f.initializeprob[x] == -1.0 -sol = solve(prob.f.initializeprob; show_trace=Val(true)) +sol = solve(prob.f.initializeprob; show_trace = Val(true)) # Guess via observed parameter @@ -61,7 +61,7 @@ sol = solve(prob.f.initializeprob; show_trace=Val(true)) @variables y(t) [guess = a] eqs = [D(x) ~ a, - y ~ x] + y ~ x] initialization_eqs = [1 ~ exp(1 + x)] @@ -72,4 +72,4 @@ tspan = (0.0, 0.2) prob = ODEProblem(sys, [], tspan, []) @test prob.f.initializeprob[x] == -1.0 -sol = solve(prob.f.initializeprob; show_trace=Val(true)) +sol = solve(prob.f.initializeprob; show_trace = Val(true)) From 653b1c0732e75ccaf4c2eefaff49c4e4034f6a05 Mon Sep 17 00:00:00 2001 From: Torkel Date: Sat, 8 Jun 2024 16:01:20 -0400 Subject: [PATCH 164/316] init --- ext/MTKBifurcationKitExt.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ext/MTKBifurcationKitExt.jl b/ext/MTKBifurcationKitExt.jl index e687cb9adf..c0a812fe8a 100644 --- a/ext/MTKBifurcationKitExt.jl +++ b/ext/MTKBifurcationKitExt.jl @@ -103,8 +103,8 @@ function BifurcationKit.BifurcationProblem(nsys::NonlinearSystem, # Converts the input state guess. u0_bif_vals = ModelingToolkit.varmap_to_vars(u0_bif, unknowns(nsys); - defaults = nsys.defaults) - p_vals = ModelingToolkit.varmap_to_vars(ps, parameters(nsys); defaults = nsys.defaults) + defaults = get_defaults(nsys)) + p_vals = ModelingToolkit.varmap_to_vars(ps, parameters(nsys); defaults = get_defaults(nsys)) # Computes bifurcation parameter and the plotting function. bif_idx = findfirst(isequal(bif_par), parameters(nsys)) From 59f62f29cb4dce3a749bfafb6982a7b5e8dbdd76 Mon Sep 17 00:00:00 2001 From: Torkel Date: Sat, 8 Jun 2024 16:42:36 -0400 Subject: [PATCH 165/316] format --- ext/MTKBifurcationKitExt.jl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ext/MTKBifurcationKitExt.jl b/ext/MTKBifurcationKitExt.jl index c0a812fe8a..d2a4e16105 100644 --- a/ext/MTKBifurcationKitExt.jl +++ b/ext/MTKBifurcationKitExt.jl @@ -104,7 +104,8 @@ function BifurcationKit.BifurcationProblem(nsys::NonlinearSystem, u0_bif_vals = ModelingToolkit.varmap_to_vars(u0_bif, unknowns(nsys); defaults = get_defaults(nsys)) - p_vals = ModelingToolkit.varmap_to_vars(ps, parameters(nsys); defaults = get_defaults(nsys)) + p_vals = ModelingToolkit.varmap_to_vars( + ps, parameters(nsys); defaults = get_defaults(nsys)) # Computes bifurcation parameter and the plotting function. bif_idx = findfirst(isequal(bif_par), parameters(nsys)) From 9aa83032ba998b855761e09c1cdd1c31ce3cb1a1 Mon Sep 17 00:00:00 2001 From: Christopher Rackauckas Date: Sat, 8 Jun 2024 16:53:10 -0400 Subject: [PATCH 166/316] Stop infinite loop from numbers --- src/systems/diffeqs/abstractodesystem.jl | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/systems/diffeqs/abstractodesystem.jl b/src/systems/diffeqs/abstractodesystem.jl index e85d77ebd7..2f6639b167 100644 --- a/src/systems/diffeqs/abstractodesystem.jl +++ b/src/systems/diffeqs/abstractodesystem.jl @@ -790,7 +790,8 @@ function get_u0_p(sys, @warn "Observed variables cannot be assigned initial values. Initial values for $u0s_in_obs will be ignored." end end - observedmap = todict(map(x -> x.rhs => x.lhs, observed(sys))) + obs = filter!(x->!(x[1] isa Number), map(x -> x.rhs => x.lhs, observed(sys))) + observedmap = isempty(obs) ? Dict() : todict(obs) defs = mergedefaults(defs, observedmap, u0map, dvs) for (k, v) in defs if Symbolics.isarraysymbolic(k) @@ -822,7 +823,7 @@ function get_u0( if parammap !== nothing defs = mergedefaults(defs, parammap, ps) end - obs = map(x -> x.rhs => x.lhs, observed(sys)) + obs = filter!(x->!(x[1] isa Number), map(x -> x.rhs => x.lhs, observed(sys))) observedmap = isempty(obs) ? Dict() : todict(obs) defs = mergedefaults(defs, observedmap, u0map, dvs) if symbolic_u0 @@ -1640,6 +1641,7 @@ function InitializationProblem{iip, specialize}(sys::AbstractODESystem, if isempty(guesses) guesses = Dict() end + u0map = merge(todict(guesses), todict(u0map)) if neqs == nunknown NonlinearProblem(isys, u0map, parammap) From 514dfd92fa076e2ade25aad7a22a8adb5c293e85 Mon Sep 17 00:00:00 2001 From: Torkel Date: Sat, 8 Jun 2024 17:02:54 -0400 Subject: [PATCH 167/316] init --- ext/MTKBifurcationKitExt.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/MTKBifurcationKitExt.jl b/ext/MTKBifurcationKitExt.jl index d2a4e16105..9190627096 100644 --- a/ext/MTKBifurcationKitExt.jl +++ b/ext/MTKBifurcationKitExt.jl @@ -27,7 +27,7 @@ struct ObservableRecordFromSolution{S, T} plot_var, bif_idx, u0_vals, - p_vals) where {S, T} + p_vals) obs_eqs = observed(nsys) target_obs_idx = findfirst(isequal(plot_var, eq.lhs) for eq in observed(nsys)) state_end_idxs = length(unknowns(nsys)) From aa2f7f899582ed32c0a6812a7c8c5319fcdf95db Mon Sep 17 00:00:00 2001 From: Christopher Rackauckas Date: Sat, 8 Jun 2024 19:15:39 -0400 Subject: [PATCH 168/316] format --- src/systems/diffeqs/abstractodesystem.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/systems/diffeqs/abstractodesystem.jl b/src/systems/diffeqs/abstractodesystem.jl index 2f6639b167..11e9c04536 100644 --- a/src/systems/diffeqs/abstractodesystem.jl +++ b/src/systems/diffeqs/abstractodesystem.jl @@ -790,7 +790,7 @@ function get_u0_p(sys, @warn "Observed variables cannot be assigned initial values. Initial values for $u0s_in_obs will be ignored." end end - obs = filter!(x->!(x[1] isa Number), map(x -> x.rhs => x.lhs, observed(sys))) + obs = filter!(x -> !(x[1] isa Number), map(x -> x.rhs => x.lhs, observed(sys))) observedmap = isempty(obs) ? Dict() : todict(obs) defs = mergedefaults(defs, observedmap, u0map, dvs) for (k, v) in defs @@ -823,7 +823,7 @@ function get_u0( if parammap !== nothing defs = mergedefaults(defs, parammap, ps) end - obs = filter!(x->!(x[1] isa Number), map(x -> x.rhs => x.lhs, observed(sys))) + obs = filter!(x -> !(x[1] isa Number), map(x -> x.rhs => x.lhs, observed(sys))) observedmap = isempty(obs) ? Dict() : todict(obs) defs = mergedefaults(defs, observedmap, u0map, dvs) if symbolic_u0 From 7170f644b3f1b0473fb31283bd13e55a6b1ff68e Mon Sep 17 00:00:00 2001 From: Christopher Rackauckas Date: Sat, 8 Jun 2024 20:21:33 -0400 Subject: [PATCH 169/316] Update MTKBifurcationKitExt.jl --- ext/MTKBifurcationKitExt.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ext/MTKBifurcationKitExt.jl b/ext/MTKBifurcationKitExt.jl index 9190627096..b709b2eec0 100644 --- a/ext/MTKBifurcationKitExt.jl +++ b/ext/MTKBifurcationKitExt.jl @@ -103,9 +103,9 @@ function BifurcationKit.BifurcationProblem(nsys::NonlinearSystem, # Converts the input state guess. u0_bif_vals = ModelingToolkit.varmap_to_vars(u0_bif, unknowns(nsys); - defaults = get_defaults(nsys)) + defaults = ModelingToolkit.get_defaults(nsys)) p_vals = ModelingToolkit.varmap_to_vars( - ps, parameters(nsys); defaults = get_defaults(nsys)) + ps, parameters(nsys); defaults = ModelingToolkit.get_defaults(nsys)) # Computes bifurcation parameter and the plotting function. bif_idx = findfirst(isequal(bif_par), parameters(nsys)) From 9d8b81627b9ef83baf6ad6e28c7916f70a47f846 Mon Sep 17 00:00:00 2001 From: Christopher Rackauckas Date: Sun, 9 Jun 2024 07:59:29 -0400 Subject: [PATCH 170/316] Test parameters + defaults in initialization propagation Fixes https://github.com/SciML/ModelingToolkit.jl/issues/2774 --- src/systems/diffeqs/abstractodesystem.jl | 4 ++- test/guess_propagation.jl | 35 ++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/src/systems/diffeqs/abstractodesystem.jl b/src/systems/diffeqs/abstractodesystem.jl index 11e9c04536..1c23a45015 100644 --- a/src/systems/diffeqs/abstractodesystem.jl +++ b/src/systems/diffeqs/abstractodesystem.jl @@ -823,7 +823,9 @@ function get_u0( if parammap !== nothing defs = mergedefaults(defs, parammap, ps) end - obs = filter!(x -> !(x[1] isa Number), map(x -> x.rhs => x.lhs, observed(sys))) + + obs = filter!(x -> !(x[1] isa Number), + map(x -> isparameter(x.rhs) ? x.lhs => x.rhs : x.rhs => x.lhs, observed(sys))) observedmap = isempty(obs) ? Dict() : todict(obs) defs = mergedefaults(defs, observedmap, u0map, dvs) if symbolic_u0 diff --git a/test/guess_propagation.jl b/test/guess_propagation.jl index 709fe9aa54..6d71ce6ca1 100644 --- a/test/guess_propagation.jl +++ b/test/guess_propagation.jl @@ -73,3 +73,38 @@ prob = ODEProblem(sys, [], tspan, []) @test prob.f.initializeprob[x] == -1.0 sol = solve(prob.f.initializeprob; show_trace = Val(true)) + +# Test parameters + defaults +# https://github.com/SciML/ModelingToolkit.jl/issues/2774 + +@parameters x0 +@variables x(t) +@variables y(t) = x +@mtkbuild sys = ODESystem([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 + +@parameters x0 +@variables x(t) +@variables y(t) = x0 +@mtkbuild sys = ODESystem([x ~ x0, D(y) ~ x], t) +prob = ODEProblem(sys, [], (0.0, 1.0), [x0 => 1.0]) +prob[x] == 1.0 +prob[y] == 1.0 + +@parameters x0 +@variables x(t) +@variables y(t) = x0 +@mtkbuild sys = ODESystem([x ~ y, D(y) ~ x], t) +prob = ODEProblem(sys, [], (0.0, 1.0), [x0 => 1.0]) +prob[x] == 1.0 +prob[y] == 1.0 + +@parameters x0 +@variables x(t) = x0 +@variables y(t) = x +@mtkbuild sys = ODESystem([x ~ y, D(y) ~ x], t) +prob = ODEProblem(sys, [], (0.0, 1.0), [x0 => 1.0]) +prob[x] == 1.0 +prob[y] == 1.0 From 4c6e061c1c4f6b866bb7913eb25769fe6feb6cd6 Mon Sep 17 00:00:00 2001 From: Christopher Rackauckas Date: Sun, 9 Jun 2024 17:19:22 -0400 Subject: [PATCH 171/316] Only use the observed for simplification in MTKParameters construction You don't need it in the p itself, just wanted to use it for simplification, so don't loop over the whole thing. This fixes https://github.com/SciML/MethodOfLines.jl/pull/397 --- src/systems/parameter_buffer.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/systems/parameter_buffer.jl b/src/systems/parameter_buffer.jl index 65f24be30c..1ad33cc891 100644 --- a/src/systems/parameter_buffer.jl +++ b/src/systems/parameter_buffer.jl @@ -43,10 +43,10 @@ function MTKParameters( end defs = merge(defs, u0) defs = merge(Dict(eq.lhs => eq.rhs for eq in observed(sys)), defs) - p = merge(defs, p) + bigdefs = merge(defs, p) p = merge(Dict(unwrap(k) => v for (k, v) in p), Dict(default_toterm(unwrap(k)) => v for (k, v) in p)) - p = Dict(unwrap(k) => fixpoint_sub(v, p) for (k, v) in p) + p = Dict(unwrap(k) => fixpoint_sub(v, bigdefs) for (k, v) in p) for (sym, _) in p if iscall(sym) && operation(sym) === getindex && first(arguments(sym)) in all_ps From 730ad202db822336e85190211a467badfe14d621 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Mon, 10 Jun 2024 12:53:46 +0530 Subject: [PATCH 172/316] fix: avoid infinite loops in `MTKParameters` initialization --- src/systems/parameter_buffer.jl | 56 +++++++++++++++++++++------------ 1 file changed, 36 insertions(+), 20 deletions(-) diff --git a/src/systems/parameter_buffer.jl b/src/systems/parameter_buffer.jl index 1ad33cc891..6468753300 100644 --- a/src/systems/parameter_buffer.jl +++ b/src/systems/parameter_buffer.jl @@ -44,8 +44,42 @@ function MTKParameters( defs = merge(defs, u0) defs = merge(Dict(eq.lhs => eq.rhs for eq in observed(sys)), defs) bigdefs = merge(defs, p) - p = merge(Dict(unwrap(k) => v for (k, v) in p), - Dict(default_toterm(unwrap(k)) => v for (k, v) in p)) + p = Dict() + missing_params = Set() + + for sym in all_ps + ttsym = default_toterm(sym) + isarr = iscall(sym) && operation(sym) === getindex + arrparent = isarr ? arguments(sym)[1] : nothing + ttarrparent = isarr ? default_toterm(arrparent) : nothing + pname = hasname(sym) ? getname(sym) : nothing + ttpname = hasname(ttsym) ? getname(ttsym) : nothing + p[sym] = p[ttsym] = if haskey(bigdefs, sym) + bigdefs[sym] + elseif haskey(bigdefs, ttsym) + bigdefs[ttsym] + elseif haskey(bigdefs, pname) + isarr ? bigdefs[pname][arguments(sym)[2:end]...] : bigdefs[pname] + elseif haskey(bigdefs, ttpname) + isarr ? bigdefs[ttpname][arguments(sym)[2:end]...] : bigdefs[pname] + elseif isarr && haskey(bigdefs, arrparent) + bigdefs[arrparent][arguments(sym)[2:end]...] + elseif isarr && haskey(bigdefs, ttarrparent) + bigdefs[ttarrparent][arguments(sym)[2:end]...] + end + if get(p, sym, nothing) === nothing + push!(missing_params, sym) + continue + end + # We may encounter the `ttsym` version first, add it to `missing_params` + # then encounter the "normal" version of a parameter or vice versa + # Remove the old one in `missing_params` just in case + delete!(missing_params, sym) + delete!(missing_params, ttsym) + end + + isempty(missing_params) || throw(MissingParametersError(collect(missing_params))) + p = Dict(unwrap(k) => fixpoint_sub(v, bigdefs) for (k, v) in p) for (sym, _) in p if iscall(sym) && operation(sym) === getindex && @@ -54,24 +88,6 @@ function MTKParameters( end end - missing_params = Set() - for idxmap in (ic.tunable_idx, ic.discrete_idx, ic.constant_idx, ic.nonnumeric_idx) - for sym in keys(idxmap) - sym isa Symbol && continue - haskey(p, sym) && continue - hasname(sym) && haskey(p, getname(sym)) && continue - ttsym = default_toterm(sym) - haskey(p, ttsym) && continue - hasname(ttsym) && haskey(p, getname(ttsym)) && continue - - iscall(sym) && operation(sym) === getindex && haskey(p, arguments(sym)[1]) && - continue - push!(missing_params, sym) - end - end - - isempty(missing_params) || throw(MissingParametersError(collect(missing_params))) - tunable_buffer = Tuple(Vector{temp.type}(undef, temp.length) for temp in ic.tunable_buffer_sizes) disc_buffer = Tuple(Vector{temp.type}(undef, temp.length) From 9b4dd00ea7b0953e1504e44dd49c6e4d3b112880 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Mon, 10 Jun 2024 15:11:05 +0530 Subject: [PATCH 173/316] fix: correctly handle parameter dependencies --- src/systems/parameter_buffer.jl | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/systems/parameter_buffer.jl b/src/systems/parameter_buffer.jl index 6468753300..86891bab52 100644 --- a/src/systems/parameter_buffer.jl +++ b/src/systems/parameter_buffer.jl @@ -46,6 +46,7 @@ function MTKParameters( bigdefs = merge(defs, p) p = Dict() missing_params = Set() + pdeps = has_parameter_dependencies(sys) ? parameter_dependencies(sys) : nothing for sym in all_ps ttsym = default_toterm(sym) @@ -78,6 +79,16 @@ function MTKParameters( delete!(missing_params, ttsym) end + if pdeps !== nothing + for (sym, expr) in pdeps + sym = unwrap(sym) + ttsym = default_toterm(sym) + delete!(missing_params, sym) + delete!(missing_params, ttsym) + p[sym] = p[ttsym] = expr + end + end + isempty(missing_params) || throw(MissingParametersError(collect(missing_params))) p = Dict(unwrap(k) => fixpoint_sub(v, bigdefs) for (k, v) in p) @@ -151,8 +162,7 @@ function MTKParameters( # Don't narrow nonnumeric types nonnumeric_buffer = nonnumeric_buffer - if has_parameter_dependencies(sys) && - (pdeps = parameter_dependencies(sys)) !== nothing + if pdeps !== nothing pdeps = Dict(k => fixpoint_sub(v, pdeps) for (k, v) in pdeps) dep_exprs = ArrayPartition((Any[missing for _ in 1:length(v)] for v in dep_buffer)...) for (sym, val) in pdeps From 5b4468dacccbdc3476601adbeaf61ea119490ccc Mon Sep 17 00:00:00 2001 From: Christopher Rackauckas Date: Mon, 10 Jun 2024 07:43:47 -0400 Subject: [PATCH 174/316] Update Project.toml --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 4477027f53..da315c5295 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.16.0" +version = "9.17.0" [deps] AbstractTrees = "1520ce14-60c1-5f80-bbc7-55ef81b5835c" From dfb5e7369114366a538f47da8f520f09c74bcb53 Mon Sep 17 00:00:00 2001 From: Christopher Rackauckas Date: Mon, 10 Jun 2024 12:15:14 -0400 Subject: [PATCH 175/316] allow for late binding of initialization equations --- src/systems/diffeqs/abstractodesystem.jl | 11 +++++++---- src/systems/nonlinear/initializesystem.jl | 3 ++- src/systems/nonlinear/nonlinearsystem.jl | 1 + test/initializationsystem.jl | 12 ++++++++++++ 4 files changed, 22 insertions(+), 5 deletions(-) diff --git a/src/systems/diffeqs/abstractodesystem.jl b/src/systems/diffeqs/abstractodesystem.jl index 1c23a45015..df7f8a5e2b 100644 --- a/src/systems/diffeqs/abstractodesystem.jl +++ b/src/systems/diffeqs/abstractodesystem.jl @@ -853,6 +853,7 @@ function process_DEProblem(constructor, sys::AbstractODESystem, u0map, parammap; t = nothing, warn_initialize_determined = true, build_initializeprob = true, + initialization_eqs = [], kwargs...) eqs = equations(sys) dvs = unknowns(sys) @@ -925,7 +926,7 @@ function process_DEProblem(constructor, sys::AbstractODESystem, u0map, parammap; u0map = Dict() end initializeprob = ModelingToolkit.InitializationProblem( - sys, t, u0map, parammap; guesses, warn_initialize_determined) + sys, t, u0map, parammap; guesses, warn_initialize_determined, initialization_eqs) initializeprobmap = getu(initializeprob, unknowns(sys)) zerovars = Dict(setdiff(unknowns(sys), keys(defaults(sys))) .=> 0.0) @@ -1555,6 +1556,7 @@ InitializationProblem{iip}(sys::AbstractODESystem, u0map, tspan, checkbounds = false, sparse = false, simplify = false, linenumbers = true, parallel = SerialForm(), + initialization_eqs = [], kwargs...) where {iip} ``` @@ -1603,17 +1605,18 @@ function InitializationProblem{iip, specialize}(sys::AbstractODESystem, guesses = [], check_length = true, warn_initialize_determined = true, + initialization_eqs = [], 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) + isys = get_initializesystem(sys; initialization_eqs) elseif isempty(u0map) && get_initializesystem(sys) === nothing - isys = structural_simplify(generate_initializesystem(sys); fully_determined = false) + isys = structural_simplify(generate_initializesystem(sys; initialization_eqs); fully_determined = false) else isys = structural_simplify( - generate_initializesystem(sys; u0map); fully_determined = false) + generate_initializesystem(sys; u0map, initialization_eqs); fully_determined = false) end uninit = setdiff(unknowns(sys), [unknowns(isys); getfield.(observed(isys), :lhs)]) diff --git a/src/systems/nonlinear/initializesystem.jl b/src/systems/nonlinear/initializesystem.jl index 96f1d1d670..a079548025 100644 --- a/src/systems/nonlinear/initializesystem.jl +++ b/src/systems/nonlinear/initializesystem.jl @@ -9,6 +9,7 @@ function generate_initializesystem(sys::ODESystem; guesses = Dict(), check_defguess = false, default_dd_value = 0.0, algebraic_only = false, + initialization_eqs = [], kwargs...) sts, eqs = unknowns(sys), equations(sys) idxs_diff = isdiffeq.(eqs) @@ -95,7 +96,7 @@ function generate_initializesystem(sys::ODESystem; nleqs = if algebraic_only [eqs_ics; observed(sys)] else - [eqs_ics; get_initialization_eqs(sys); observed(sys)] + [eqs_ics; get_initialization_eqs(sys); initialization_eqs; observed(sys)] end sys_nl = NonlinearSystem(nleqs, diff --git a/src/systems/nonlinear/nonlinearsystem.jl b/src/systems/nonlinear/nonlinearsystem.jl index dd6243ef00..70bffabec2 100644 --- a/src/systems/nonlinear/nonlinearsystem.jl +++ b/src/systems/nonlinear/nonlinearsystem.jl @@ -394,6 +394,7 @@ function process_NonlinearProblem(constructor, sys::NonlinearSystem, u0map, para eqs = equations(sys) dvs = unknowns(sys) ps = full_parameters(sys) + Main.isys[] = sys if has_index_cache(sys) && get_index_cache(sys) !== nothing u0, defs = get_u0(sys, u0map, parammap) check_eqs_u0(eqs, dvs, u0; kwargs...) diff --git a/test/initializationsystem.jl b/test/initializationsystem.jl index 44a9423f24..f6ddccd89a 100644 --- a/test/initializationsystem.jl +++ b/test/initializationsystem.jl @@ -430,3 +430,15 @@ sol = solve(prob, Tsit5()) # This should warn, but logging tests can't be marked as broken @test_logs prob = ODEProblem(simpsys, [], tspan, guesses = [x => 2.0]) + +# Late Binding initialization_eqs +# https://github.com/SciML/ModelingToolkit.jl/issues/2787 + +@parameters g +@variables x(t) y(t) [state_priority = 10] λ(t) +eqs = [D(D(x)) ~ λ * x + D(D(y)) ~ λ * y - g + x^2 + y^2 ~ 1] +@mtkbuild pend = ODESystem(eqs, t) + +prob = ODEProblem(pend, [x => 1], (0.0, 1.5), [g => 1], guesses = [λ => 0, y => 1], initialization_eqs = [y ~ 1]) From 6ec7def7afe33f87503fcb3b9057aa0a4a29b71f Mon Sep 17 00:00:00 2001 From: Christopher Rackauckas Date: Mon, 10 Jun 2024 12:35:14 -0400 Subject: [PATCH 176/316] format --- src/systems/diffeqs/abstractodesystem.jl | 3 ++- test/initializationsystem.jl | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/systems/diffeqs/abstractodesystem.jl b/src/systems/diffeqs/abstractodesystem.jl index df7f8a5e2b..da701560b9 100644 --- a/src/systems/diffeqs/abstractodesystem.jl +++ b/src/systems/diffeqs/abstractodesystem.jl @@ -1613,7 +1613,8 @@ function InitializationProblem{iip, specialize}(sys::AbstractODESystem, if isempty(u0map) && get_initializesystem(sys) !== nothing isys = get_initializesystem(sys; initialization_eqs) elseif isempty(u0map) && get_initializesystem(sys) === nothing - isys = structural_simplify(generate_initializesystem(sys; initialization_eqs); fully_determined = false) + isys = structural_simplify( + generate_initializesystem(sys; initialization_eqs); fully_determined = false) else isys = structural_simplify( generate_initializesystem(sys; u0map, initialization_eqs); fully_determined = false) diff --git a/test/initializationsystem.jl b/test/initializationsystem.jl index f6ddccd89a..384ec2c560 100644 --- a/test/initializationsystem.jl +++ b/test/initializationsystem.jl @@ -441,4 +441,5 @@ eqs = [D(D(x)) ~ λ * x x^2 + y^2 ~ 1] @mtkbuild pend = ODESystem(eqs, t) -prob = ODEProblem(pend, [x => 1], (0.0, 1.5), [g => 1], guesses = [λ => 0, y => 1], initialization_eqs = [y ~ 1]) +prob = ODEProblem(pend, [x => 1], (0.0, 1.5), [g => 1], + guesses = [λ => 0, y => 1], initialization_eqs = [y ~ 1]) From 516d5ca56d6530d159ddc7beee2ea208a764ec85 Mon Sep 17 00:00:00 2001 From: Christopher Rackauckas Date: Mon, 10 Jun 2024 12:48:31 -0400 Subject: [PATCH 177/316] Update src/systems/nonlinear/nonlinearsystem.jl --- src/systems/nonlinear/nonlinearsystem.jl | 1 - 1 file changed, 1 deletion(-) diff --git a/src/systems/nonlinear/nonlinearsystem.jl b/src/systems/nonlinear/nonlinearsystem.jl index 70bffabec2..dd6243ef00 100644 --- a/src/systems/nonlinear/nonlinearsystem.jl +++ b/src/systems/nonlinear/nonlinearsystem.jl @@ -394,7 +394,6 @@ function process_NonlinearProblem(constructor, sys::NonlinearSystem, u0map, para eqs = equations(sys) dvs = unknowns(sys) ps = full_parameters(sys) - Main.isys[] = sys if has_index_cache(sys) && get_index_cache(sys) !== nothing u0, defs = get_u0(sys, u0map, parammap) check_eqs_u0(eqs, dvs, u0; kwargs...) From edf6fcbd819e3fbc63419ae72c3dce8cbf24972b Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Tue, 11 Jun 2024 17:14:47 +0530 Subject: [PATCH 178/316] feat: store observed equation lhs in symbol_to_variable mapping --- src/systems/index_cache.jl | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/systems/index_cache.jl b/src/systems/index_cache.jl index 2a56c5eb3e..75c8a7e235 100644 --- a/src/systems/index_cache.jl +++ b/src/systems/index_cache.jl @@ -80,6 +80,12 @@ function IndexCache(sys::AbstractSystem) end end + for eq in observed(sys) + if symbolic_type(eq.lhs) != NotSymbolic() && hasname(eq.lhs) + symbol_to_variable[getname(eq.lhs)] = eq.lhs + end + end + disc_buffers = Dict{Any, Set{BasicSymbolic}}() tunable_buffers = Dict{Any, Set{BasicSymbolic}}() constant_buffers = Dict{Any, Set{BasicSymbolic}}() From 3e7a7217558cc753c6327ecf2efba21e1399522e Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Tue, 11 Jun 2024 17:15:09 +0530 Subject: [PATCH 179/316] feat: support generating observed for `Symbol` variables --- src/systems/abstractsystem.jl | 38 ++++++++++++++++++++++++++--- test/symbolic_indexing_interface.jl | 13 ++++++++++ 2 files changed, 47 insertions(+), 4 deletions(-) diff --git a/src/systems/abstractsystem.jl b/src/systems/abstractsystem.jl index 4df1be2589..66d4f6801c 100644 --- a/src/systems/abstractsystem.jl +++ b/src/systems/abstractsystem.jl @@ -495,10 +495,40 @@ function SymbolicIndexingInterface.is_observed(sys::AbstractSystem, sym) end function SymbolicIndexingInterface.observed(sys::AbstractSystem, sym) - return let _fn = build_explicit_observed_function(sys, sym) - fn(u, p, t) = _fn(u, p, t) - fn(u, p::MTKParameters, t) = _fn(u, p..., t) - fn + if has_index_cache(sys) && (ic = get_index_cache(sys)) !== nothing + if sym isa Symbol + _sym = get(ic.symbol_to_variable, sym, nothing) + if _sym === nothing + throw(ArgumentError("Symbol $sym does not exist in the system")) + end + sym = _sym + elseif sym isa AbstractArray && symbolic_type(sym) isa NotSymbolic && + any(x -> x isa Symbol, sym) + sym = map(sym) do s + if s isa Symbol + _s = get(ic.symbol_to_variable, s, nothing) + if _s === nothing + throw(ArgumentError("Symbol $s does not exist in the system")) + end + return _s + end + return unwrap(s) + end + end + end + _fn = build_explicit_observed_function(sys, sym) + if is_time_dependent(sys) + return let _fn = _fn + fn1(u, p, t) = _fn(u, p, t) + fn1(u, p::MTKParameters, t) = _fn(u, p..., t) + fn1 + end + else + return let _fn = _fn + fn2(u, p) = _fn(u, p) + fn2(u, p::MTKParameters) = _fn(u, p...) + fn2 + end end end diff --git a/test/symbolic_indexing_interface.jl b/test/symbolic_indexing_interface.jl index 12d9c68c73..7fd57c0474 100644 --- a/test/symbolic_indexing_interface.jl +++ b/test/symbolic_indexing_interface.jl @@ -101,3 +101,16 @@ using SymbolicIndexingInterface prob = ODEProblem(complete(sys)) get_dep = @test_nowarn getu(prob, 2p1) @test get_dep(prob) == [2.0, 4.0] + +@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) + 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) + @test getu(prob, [x, y])(prob) == getu(prob, [:x, :y])(prob) + @test getu(prob, z)(prob) == getu(prob, :z)(prob) + @test getu(prob, p1)(prob) == getu(prob, :p1)(prob) + @test getu(prob, p2)(prob) == getu(prob, :p2)(prob) +end From 0d450defbb1fb45c33e7c4966f0e3f8d4a340b8d Mon Sep 17 00:00:00 2001 From: Christopher Rackauckas Date: Tue, 11 Jun 2024 15:23:31 -0400 Subject: [PATCH 180/316] Update Project.toml --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index da315c5295..f3def3db48 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.17.0" +version = "9.17.1" [deps] AbstractTrees = "1520ce14-60c1-5f80-bbc7-55ef81b5835c" From d7597adbd42fe832f911074acd4f6ffeac4293cb Mon Sep 17 00:00:00 2001 From: Yingbo Ma Date: Tue, 11 Jun 2024 21:19:08 -0400 Subject: [PATCH 181/316] Better over-determined tearing --- .../bipartite_tearing/modia_tearing.jl | 42 +++++++++++++------ 1 file changed, 30 insertions(+), 12 deletions(-) diff --git a/src/structural_transformation/bipartite_tearing/modia_tearing.jl b/src/structural_transformation/bipartite_tearing/modia_tearing.jl index 97d3b4588e..f9f9c2a1f6 100644 --- a/src/structural_transformation/bipartite_tearing/modia_tearing.jl +++ b/src/structural_transformation/bipartite_tearing/modia_tearing.jl @@ -89,27 +89,45 @@ function tear_graph_modia(structure::SystemStructure, isder::F = nothing, ieqs = Int[] filtered_vars = BitSet() + seen_eqs = falses(nsrcs(graph)) for vars in var_sccs for var in vars if varfilter(var) push!(filtered_vars, var) if var_eq_matching[var] !== unassigned - push!(ieqs, var_eq_matching[var]) + ieq = var_eq_matching[var] + seen_eqs[ieq] = true + push!(ieqs, ieq) end end var_eq_matching[var] = unassigned end - tear_graph_block_modia!(var_eq_matching, ict, solvable_graph, ieqs, - filtered_vars, - isder) - - # clear cache - vargraph.ne = 0 - for var in vars - vargraph.matching[var] = unassigned - end - empty!(ieqs) - empty!(filtered_vars) + tear_block!(vargraph, vars, + var_eq_matching, ict, solvable_graph, + ieqs, filtered_vars, isder) + end + free_eqs = findall(!, seen_eqs) + if !isempty(free_eqs) + free_vars = findall(x -> !(x isa Int), var_eq_matching) + tear_block!(vargraph, (), + var_eq_matching, ict, solvable_graph, + free_eqs, BitSet(free_vars), isder) end return var_eq_matching, full_var_eq_matching, var_sccs end + +function tear_block!(vargraph, vars, + var_eq_matching, ict, solvable_graph, ieqs, + filtered_vars, isder) + tear_graph_block_modia!(var_eq_matching, ict, solvable_graph, ieqs, + filtered_vars, + isder) + + # clear cache + vargraph.ne = 0 + for var in vars + vargraph.matching[var] = unassigned + end + empty!(ieqs) + empty!(filtered_vars) +end From 19b8934236ff51e0487e9fc7eb58f17b81c67f63 Mon Sep 17 00:00:00 2001 From: Yingbo Ma Date: Tue, 11 Jun 2024 21:21:49 -0400 Subject: [PATCH 182/316] Add tests --- test/initializationsystem.jl | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/initializationsystem.jl b/test/initializationsystem.jl index 384ec2c560..101bab5c0d 100644 --- a/test/initializationsystem.jl +++ b/test/initializationsystem.jl @@ -443,3 +443,7 @@ eqs = [D(D(x)) ~ λ * x prob = ODEProblem(pend, [x => 1], (0.0, 1.5), [g => 1], guesses = [λ => 0, y => 1], initialization_eqs = [y ~ 1]) + +unsimp = generate_initializesystem(pend; u0map = [x => 1], initialization_eqs = [y ~ 1]) +sys = structural_simplify(unsimp; fully_determined = false) +@test length(equations(sys)) == 3 From a42c5c66fa68a2d0546267a80d42c74952a7afbf Mon Sep 17 00:00:00 2001 From: Yingbo Ma Date: Tue, 11 Jun 2024 21:25:53 -0400 Subject: [PATCH 183/316] Fix matching length --- .../bipartite_tearing/modia_tearing.jl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/structural_transformation/bipartite_tearing/modia_tearing.jl b/src/structural_transformation/bipartite_tearing/modia_tearing.jl index f9f9c2a1f6..46c0d701c6 100644 --- a/src/structural_transformation/bipartite_tearing/modia_tearing.jl +++ b/src/structural_transformation/bipartite_tearing/modia_tearing.jl @@ -79,12 +79,12 @@ function tear_graph_modia(structure::SystemStructure, isder::F = nothing, @unpack graph, solvable_graph = structure var_eq_matching = maximal_matching(graph, eqfilter, varfilter, U) - var_eq_matching = complete(var_eq_matching, - max(length(var_eq_matching), - maximum(x -> x isa Int ? x : 0, var_eq_matching, init = 0))) + matching_len = max(length(var_eq_matching), + maximum(x -> x isa Int ? x : 0, var_eq_matching, init = 0)) + var_eq_matching = complete(var_eq_matching, matching_len) full_var_eq_matching = copy(var_eq_matching) var_sccs = find_var_sccs(graph, var_eq_matching) - vargraph = DiCMOBiGraph{true}(graph) + vargraph = DiCMOBiGraph{true}(graph, 0, Matching(matching_len)) ict = IncrementalCycleTracker(vargraph; dir = :in) ieqs = Int[] From b60976d6a2984d66d4122d77372b3fa52dc4b8bc Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Tue, 4 Jun 2024 11:58:37 +0530 Subject: [PATCH 184/316] refactor: directly solve initialization problem in `linearization_function` --- Project.toml | 6 ++++-- src/ModelingToolkit.jl | 2 +- src/systems/abstractsystem.jl | 15 ++++++++------- 3 files changed, 13 insertions(+), 10 deletions(-) diff --git a/Project.toml b/Project.toml index f3def3db48..4e226fb697 100644 --- a/Project.toml +++ b/Project.toml @@ -32,8 +32,8 @@ Libdl = "8f399da3-3557-5675-b5ff-fb832c97cbdb" LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" MLStyle = "d8e11817-5142-5d16-987a-aa16d5891078" NaNMath = "77ba4419-2d1f-58cd-9bb1-8ffee604a2e3" +NonlinearSolve = "8913a72c-1f9b-4ce2-8d82-65094dcecaec" OrderedCollections = "bac558e1-5e72-5ebc-8fee-abe8a469f55d" -OrdinaryDiffEq = "1dea7af3-3e70-54e6-95c3-0bf5283fa5ed" PrecompileTools = "aea7be01-6a6a-4083-8856-8a6e6704d82a" RecursiveArrayTools = "731186ca-8d62-57ce-b412-fbd966d074cd" Reexport = "189a3867-3050-52da-a836-e630ba90ab69" @@ -92,6 +92,7 @@ Libdl = "1" LinearAlgebra = "1" MLStyle = "0.4.17" NaNMath = "0.3, 1" +NonlinearSolve = "3.12" OrderedCollections = "1" OrdinaryDiffEq = "6.82.0" PrecompileTools = "1" @@ -129,6 +130,7 @@ NonlinearSolve = "8913a72c-1f9b-4ce2-8d82-65094dcecaec" Optimization = "7f7a1694-90dd-40f0-9382-eb1efda571ba" OptimizationMOI = "fd9f6733-72f4-499f-8506-86b2bdd0dea1" OptimizationOptimJL = "36348300-93cb-4f02-beb5-3c3902f8871e" +OrdinaryDiffEq = "1dea7af3-3e70-54e6-95c3-0bf5283fa5ed" Pkg = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f" Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" ReferenceTests = "324d217c-45ce-50fc-942e-d289b448e8cf" @@ -142,4 +144,4 @@ Sundials = "c3572dad-4567-51f8-b174-8c6c989267f4" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" [targets] -test = ["AmplNLWriter", "BenchmarkTools", "ControlSystemsBase", "DelayDiffEq", "NonlinearSolve", "ForwardDiff", "Ipopt", "Ipopt_jll", "ModelingToolkitStandardLibrary", "Optimization", "OptimizationOptimJL", "OptimizationMOI", "Random", "ReferenceTests", "SafeTestsets", "StableRNGs", "Statistics", "SteadyStateDiffEq", "Test", "StochasticDiffEq", "Sundials", "StochasticDelayDiffEq", "Pkg", "JET"] +test = ["AmplNLWriter", "BenchmarkTools", "ControlSystemsBase", "DelayDiffEq", "NonlinearSolve", "ForwardDiff", "Ipopt", "Ipopt_jll", "ModelingToolkitStandardLibrary", "Optimization", "OptimizationOptimJL", "OptimizationMOI", "OrdinaryDiffEq", "Random", "ReferenceTests", "SafeTestsets", "StableRNGs", "Statistics", "SteadyStateDiffEq", "Test", "StochasticDiffEq", "Sundials", "StochasticDelayDiffEq", "Pkg", "JET"] diff --git a/src/ModelingToolkit.jl b/src/ModelingToolkit.jl index 9ced3053e1..3b4435a19d 100644 --- a/src/ModelingToolkit.jl +++ b/src/ModelingToolkit.jl @@ -46,7 +46,7 @@ using SciMLBase: StandardODEProblem, StandardNonlinearProblem, handle_varmap using Distributed import JuliaFormatter using MLStyle -import OrdinaryDiffEq +using NonlinearSolve using Reexport using RecursiveArrayTools import Graphs: SimpleDiGraph, add_edge!, incidence_matrix diff --git a/src/systems/abstractsystem.jl b/src/systems/abstractsystem.jl index 66d4f6801c..ece9df8ef6 100644 --- a/src/systems/abstractsystem.jl +++ b/src/systems/abstractsystem.jl @@ -1843,8 +1843,10 @@ function linearization_function(sys::AbstractSystem, inputs, op = merge(defs, op) end sys = ssys - initsys = complete(generate_initializesystem( - sys, guesses = guesses(sys), algebraic_only = true)) + initsys = structural_simplify( + generate_initializesystem( + sys, guesses = guesses(sys), algebraic_only = true), + fully_determined = false) if p isa SciMLBase.NullParameters p = Dict() else @@ -1927,8 +1929,9 @@ function linearization_function(sys::AbstractSystem, inputs, sts = unknowns(sys), get_initprob_u_p = get_initprob_u_p, fun = ODEFunction{true, SciMLBase.FullSpecialize}( - sys, unknowns(sys), ps; initializeprobmap = initprobmap), + sys, unknowns(sys), ps), initfn = initfn, + initprobmap = initprobmap, h = build_explicit_observed_function(sys, outputs), chunk = ForwardDiff.Chunk(input_idxs), sys_ps = sys_ps, @@ -1953,10 +1956,8 @@ function linearization_function(sys::AbstractSystem, inputs, if norm(residual[alge_idxs]) > √(eps(eltype(residual))) initu0, initp = get_initprob_u_p(u, p, t) initprob = NonlinearLeastSquaresProblem(initfn, initu0, initp) - @set! fun.initializeprob = initprob - prob = ODEProblem(fun, u, (t, t + 1), p) - integ = init(prob, OrdinaryDiffEq.Rodas5P()) - u = integ.u + nlsol = solve(initprob) + u = initprobmap(nlsol) end end uf = SciMLBase.UJacobianWrapper(fun, t, p) From dc395f6eb7b195b00adcd5ed463e71314854110b Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Tue, 4 Jun 2024 14:14:50 +0530 Subject: [PATCH 185/316] feat: allow specifying initialization solver algorithm default to `TrustRegion` --- src/systems/abstractsystem.jl | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/systems/abstractsystem.jl b/src/systems/abstractsystem.jl index ece9df8ef6..57c47c2fc3 100644 --- a/src/systems/abstractsystem.jl +++ b/src/systems/abstractsystem.jl @@ -1795,7 +1795,7 @@ function io_preprocessing(sys::AbstractSystem, inputs, end """ - lin_fun, simplified_sys = linearization_function(sys::AbstractSystem, inputs, outputs; simplify = false, initialize = true, kwargs...) + lin_fun, simplified_sys = linearization_function(sys::AbstractSystem, inputs, outputs; simplify = false, initialize = true, initialization_solver_alg = TrustRegion(), kwargs...) Return a function that linearizes the system `sys`. The function [`linearize`](@ref) provides a higher-level and easier to use interface. @@ -1820,6 +1820,7 @@ The `simplified_sys` has undergone [`structural_simplify`](@ref) and had any occ - `outputs`: A vector of variables that indicate the outputs of the linearized input-output model. - `simplify`: Apply simplification in tearing. - `initialize`: If true, a check is performed to ensure that the operating point is consistent (satisfies algebraic equations). If the op is not consistent, initialization is performed. + - `initialization_solver_alg`: A NonlinearSolve algorithm to use for solving for a feasible set of state and algebraic variables that satisfies the specified operating point. - `kwargs`: Are passed on to `find_solvables!` See also [`linearize`](@ref) which provides a higher-level interface. @@ -1830,6 +1831,7 @@ function linearization_function(sys::AbstractSystem, inputs, op = Dict(), p = DiffEqBase.NullParameters(), zero_dummy_der = false, + initialization_solver_alg = TrustRegion(), kwargs...) inputs isa AbstractVector || (inputs = [inputs]) outputs isa AbstractVector || (outputs = [outputs]) @@ -1936,6 +1938,7 @@ function linearization_function(sys::AbstractSystem, inputs, chunk = ForwardDiff.Chunk(input_idxs), sys_ps = sys_ps, initialize = initialize, + initialization_solver_alg = initialization_solver_alg, sys = sys function (u, p, t) @@ -1956,7 +1959,7 @@ function linearization_function(sys::AbstractSystem, inputs, if norm(residual[alge_idxs]) > √(eps(eltype(residual))) initu0, initp = get_initprob_u_p(u, p, t) initprob = NonlinearLeastSquaresProblem(initfn, initu0, initp) - nlsol = solve(initprob) + nlsol = solve(initprob, initialization_solver_alg) u = initprobmap(nlsol) end end From 7359ddba3a7ea9a2699e5e4a85f76f444d4dcf59 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Mon, 10 Jun 2024 16:19:23 +0530 Subject: [PATCH 186/316] fix: respect operating point in `linearize` --- 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 57c47c2fc3..9d462e0225 100644 --- a/src/systems/abstractsystem.jl +++ b/src/systems/abstractsystem.jl @@ -2229,7 +2229,7 @@ function linearize(sys, lin_fun; t = 0.0, op = Dict(), allow_input_derivatives = u0, defs = get_u0(sys, x0, p) if has_index_cache(sys) && get_index_cache(sys) !== nothing if p isa SciMLBase.NullParameters - p = Dict() + p = op elseif p isa Dict p = merge(p, op) elseif p isa Vector && eltype(p) <: Pair From 568cd44765f06d21d139c657cc15f8771e8862ed Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Mon, 10 Jun 2024 23:41:49 +0530 Subject: [PATCH 187/316] test: mark inversemodel tests as broken --- test/downstream/inversemodel.jl | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/test/downstream/inversemodel.jl b/test/downstream/inversemodel.jl index c1eb85ca31..e37f07e809 100644 --- a/test/downstream/inversemodel.jl +++ b/test/downstream/inversemodel.jl @@ -148,19 +148,27 @@ sol = solve(prob, Rodas5P()) Sf, simplified_sys = Blocks.get_sensitivity_function(model, :y) # This should work without providing an operating opint containing a dummy derivative x, _ = ModelingToolkit.get_u0_p(simplified_sys, op) p = ModelingToolkit.MTKParameters(simplified_sys, op) -matrices1 = Sf(x, p, 0) -matrices2, _ = Blocks.get_sensitivity(model, :y; op) # Test that we get the same result when calling the higher-level API -@test_broken matrices1.f_x ≈ matrices2.A[1:7, 1:7] -nsys = get_named_sensitivity(model, :y; op) # Test that we get the same result when calling an even higher-level API -@test matrices2.A ≈ nsys.A +# If this somehow passes, mention it on +# https://github.com/SciML/ModelingToolkit.jl/issues/2786 +@test_broken begin + matrices1 = Sf(x, p, 0) + matrices2, _ = Blocks.get_sensitivity(model, :y; op) # Test that we get the same result when calling the higher-level API + @test matrices1.f_x ≈ matrices2.A[1:7, 1:7] + nsys = get_named_sensitivity(model, :y; op) # Test that we get the same result when calling an even higher-level API + @test matrices2.A ≈ nsys.A +end # Test the same thing for comp sensitivities Sf, simplified_sys = Blocks.get_comp_sensitivity_function(model, :y) # This should work without providing an operating opint containing a dummy derivative x, _ = ModelingToolkit.get_u0_p(simplified_sys, op) p = ModelingToolkit.MTKParameters(simplified_sys, op) -matrices1 = Sf(x, p, 0) -matrices2, _ = Blocks.get_comp_sensitivity(model, :y; op) # Test that we get the same result when calling the higher-level API -@test_broken matrices1.f_x ≈ matrices2.A[1:7, 1:7] -nsys = get_named_comp_sensitivity(model, :y; op) # Test that we get the same result when calling an even higher-level API -@test matrices2.A ≈ nsys.A +# If this somehow passes, mention it on +# https://github.com/SciML/ModelingToolkit.jl/issues/2786 +@test_broken begin + matrices1 = Sf(x, p, 0) + matrices2, _ = Blocks.get_comp_sensitivity(model, :y; op) # Test that we get the same result when calling the higher-level API + @test matrices1.f_x ≈ matrices2.A[1:7, 1:7] + nsys = get_named_comp_sensitivity(model, :y; op) # Test that we get the same result when calling an even higher-level API + @test matrices2.A ≈ nsys.A +end From 4ece7677ce129fd561421afd359231a625c28c5d Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Wed, 12 Jun 2024 15:34:24 +0530 Subject: [PATCH 188/316] refactor: use common implementation of `observedfun` --- src/systems/abstractsystem.jl | 24 +++++ src/systems/diffeqs/abstractodesystem.jl | 94 ++----------------- src/systems/diffeqs/sdesystem.jl | 14 +-- .../discrete_system/discrete_system.jl | 9 +- src/systems/jumps/jumpsystem.jl | 11 +-- .../optimization/optimizationsystem.jl | 22 +---- 6 files changed, 38 insertions(+), 136 deletions(-) diff --git a/src/systems/abstractsystem.jl b/src/systems/abstractsystem.jl index 66d4f6801c..7c842be5a5 100644 --- a/src/systems/abstractsystem.jl +++ b/src/systems/abstractsystem.jl @@ -1192,6 +1192,30 @@ end ### ### System utils ### +struct ObservedFunctionCache{S} + sys::S + dict::Dict{Any, Any} +end + +function ObservedFunctionCache(sys) + return ObservedFunctionCache(sys, Dict()) + let sys = sys, dict = Dict() + function generated_observed(obsvar, args...) + end + end +end + +function (ofc::ObservedFunctionCache)(obsvar, args...) + obs = get!(ofc.dict, value(obsvar)) do + SymbolicIndexingInterface.observed(ofc.sys, obsvar) + end + if args === () + return obs + else + return obs(args...) + end +end + function push_vars!(stmt, name, typ, vars) isempty(vars) && return vars_expr = Expr(:macrocall, typ, nothing) diff --git a/src/systems/diffeqs/abstractodesystem.jl b/src/systems/diffeqs/abstractodesystem.jl index da701560b9..292052ec3e 100644 --- a/src/systems/diffeqs/abstractodesystem.jl +++ b/src/systems/diffeqs/abstractodesystem.jl @@ -404,82 +404,25 @@ function DiffEqBase.ODEFunction{iip, specialize}(sys::AbstractODESystem, obs = observed(sys) observedfun = if steady_state - let sys = sys, dict = Dict(), ps = ps + let sys = sys, dict = Dict() function generated_observed(obsvar, args...) obs = get!(dict, value(obsvar)) do - build_explicit_observed_function(sys, obsvar) + SymbolicIndexingInterface.observed(sys, obsvar) end if args === () - let obs = obs, ps_T = typeof(ps) - (u, p, t = Inf) -> if p isa MTKParameters - obs(u, p..., t) - elseif ps_T <: Tuple - obs(u, p..., t) - else - obs(u, p, t) - end + return let obs = obs + fn1(u, p, t = Inf) = obs(u, p, t) + fn1 end + elseif length(args) == 2 + return obs(args..., Inf) else - if args[2] isa MTKParameters - if length(args) == 2 - u, p = args - obs(u, p..., Inf) - else - u, p, t = args - obs(u, p..., t) - end - elseif ps isa Tuple - if length(args) == 2 - u, p = args - obs(u, p..., Inf) - else - u, p, t = args - obs(u, p..., t) - end - else - if length(args) == 2 - u, p = args - obs(u, p, Inf) - else - u, p, t = args - obs(u, p, t) - end - end + return obs(args...) end end end else - let sys = sys, dict = Dict(), ps = ps - function generated_observed(obsvar, args...) - obs = get!(dict, value(obsvar)) do - build_explicit_observed_function(sys, - obsvar; - checkbounds = checkbounds, - ps) - end - if args === () - let obs = obs, ps_T = typeof(ps) - (u, p, t) -> if p isa MTKParameters - obs(u, p..., t) - elseif ps_T <: Tuple - obs(u, p..., t) - else - obs(u, p, t) - end - end - else - u, p, t = args - if p isa MTKParameters - u, p, t = args - obs(u, p..., t) - elseif ps isa Tuple # split parameters - obs(u, p..., t) - else - obs(args...) - end - end - end - end + ObservedFunctionCache(sys) end jac_prototype = if sparse @@ -571,24 +514,7 @@ function DiffEqBase.DAEFunction{iip}(sys::AbstractODESystem, dvs = unknowns(sys) _jac = nothing end - obs = observed(sys) - observedfun = let sys = sys, dict = Dict() - function generated_observed(obsvar, args...) - obs = get!(dict, value(obsvar)) do - build_explicit_observed_function(sys, obsvar; checkbounds = checkbounds) - end - if args === () - let obs = obs - fun(u, p, t) = obs(u, p, t) - fun(u, p::MTKParameters, t) = obs(u, p..., t) - fun - end - else - u, p, t = args - p isa MTKParameters ? obs(u, p..., t) : obs(u, p, t) - end - end - end + observedfun = ObservedFunctionCache(sys) jac_prototype = if sparse uElType = u0 === nothing ? Float64 : eltype(u0) diff --git a/src/systems/diffeqs/sdesystem.jl b/src/systems/diffeqs/sdesystem.jl index 05d85ecd6b..223f1b3c0a 100644 --- a/src/systems/diffeqs/sdesystem.jl +++ b/src/systems/diffeqs/sdesystem.jl @@ -484,19 +484,7 @@ function DiffEqBase.SDEFunction{iip}(sys::SDESystem, dvs = unknowns(sys), M = calculate_massmatrix(sys) _M = (u0 === nothing || M == I) ? M : ArrayInterface.restructure(u0 .* u0', M) - obs = observed(sys) - observedfun = let sys = sys, dict = Dict() - function generated_observed(obsvar, u, p, t) - obs = get!(dict, value(obsvar)) do - build_explicit_observed_function(sys, obsvar; checkbounds = checkbounds) - end - if p isa MTKParameters - obs(u, p..., t) - else - obs(u, p, t) - end - end - end + observedfun = ObservedFunctionCache(sys) SDEFunction{iip}(f, g, sys = sys, diff --git a/src/systems/discrete_system/discrete_system.jl b/src/systems/discrete_system/discrete_system.jl index 984429a504..18755ebafb 100644 --- a/src/systems/discrete_system/discrete_system.jl +++ b/src/systems/discrete_system/discrete_system.jl @@ -330,14 +330,7 @@ function SciMLBase.DiscreteFunction{iip, specialize}( f = SciMLBase.wrapfun_iip(f, (u0, u0, p, t)) end - observedfun = let sys = sys, dict = Dict() - function generate_observed(obsvar, u, p, t) - obs = get!(dict, value(obsvar)) do - build_explicit_observed_function(sys, obsvar) - end - p isa MTKParameters ? obs(u, p..., t) : obs(u, p, t) - end - end + observedfun = ObservedFunctionCache(sys) DiscreteFunction{iip, specialize}(f; sys = sys, diff --git a/src/systems/jumps/jumpsystem.jl b/src/systems/jumps/jumpsystem.jl index 36b8719e1e..accc0bc39f 100644 --- a/src/systems/jumps/jumpsystem.jl +++ b/src/systems/jumps/jumpsystem.jl @@ -345,16 +345,7 @@ function DiffEqBase.DiscreteProblem(sys::JumpSystem, u0map, tspan::Union{Tuple, f = DiffEqBase.DISCRETE_INPLACE_DEFAULT - # just taken from abstractodesystem.jl for ODEFunction def - obs = observed(sys) - observedfun = let sys = sys, dict = Dict() - function generated_observed(obsvar, u, p, t) - obs = get!(dict, value(obsvar)) do - build_explicit_observed_function(sys, obsvar; checkbounds = checkbounds) - end - p isa MTKParameters ? obs(u, p..., t) : obs(u, p, t) - end - end + observedfun = ObservedFunctionCache(sys) df = DiscreteFunction{true, true}(f; sys = sys, observed = observedfun) DiscreteProblem(df, u0, tspan, p; kwargs...) diff --git a/src/systems/optimization/optimizationsystem.jl b/src/systems/optimization/optimizationsystem.jl index 4494e61074..f017494b13 100644 --- a/src/systems/optimization/optimizationsystem.jl +++ b/src/systems/optimization/optimizationsystem.jl @@ -337,27 +337,7 @@ function DiffEqBase.OptimizationProblem{iip}(sys::OptimizationSystem, u0map, hess_prototype = nothing end - observedfun = let sys = sys, dict = Dict() - function generated_observed(obsvar, args...) - obs = get!(dict, value(obsvar)) do - build_explicit_observed_function(sys, obsvar) - end - if args === () - let obs = obs - _obs(u, p) = obs(u, p) - _obs(u, p::MTKParameters) = obs(u, p...) - _obs - end - else - u, p = args - if p isa MTKParameters - obs(u, p...) - else - obs(u, p) - end - end - end - end + observedfun = ObservedFunctionCache(sys) if length(cstr) > 0 @named cons_sys = ConstraintsSystem(cstr, dvs, ps) From aa96020c39d28aa8bae2df433453f307b717be2e Mon Sep 17 00:00:00 2001 From: Christopher Rackauckas Date: Wed, 12 Jun 2024 08:57:05 -0400 Subject: [PATCH 189/316] =?UTF-8?q?Bye=20bye=20=E2=82=8A,=20use=20var=20sy?= =?UTF-8?q?mbols=20with=20.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is something that was iteratively chipped away at during the v9 changes. The last thing left here was the handling of FuncAffect, which needed to have a funky reindexer to handle the fact that u.resisitor.v is done in two getproperty calls, and so we have to lazily merge the symbols. This is non-breaking because we've been very specific to not rely on any of the symbol names in v9 for a very good reason, and this is that reason. I'm sure someone out there did hardcode ₊ stuff against all recommendations though... I need to fix the printing in SymbolicUtils.jl to really finalize this change. --- Project.toml | 2 ++ docs/src/basics/Composition.md | 32 ++++++++++++------------ docs/src/basics/Events.md | 4 +-- docs/src/tutorials/domain_connections.md | 4 +-- src/ModelingToolkit.jl | 1 + src/inputoutput.jl | 22 ++++++++-------- src/systems/abstractsystem.jl | 20 +++++++-------- src/systems/callbacks.jl | 23 +++++++++++++++-- src/systems/connectors.jl | 2 +- test/funcaffect.jl | 4 +-- test/input_output_handling.jl | 4 +-- test/inputoutput.jl | 6 ++--- test/odesystem.jl | 8 +++--- test/variable_scope.jl | 30 +++++++++++----------- 14 files changed, 92 insertions(+), 70 deletions(-) diff --git a/Project.toml b/Project.toml index 4e226fb697..72be0d6bf8 100644 --- a/Project.toml +++ b/Project.toml @@ -8,6 +8,7 @@ AbstractTrees = "1520ce14-60c1-5f80-bbc7-55ef81b5835c" ArrayInterface = "4fba245c-0d91-5ea0-9b3e-6abc04ee57a9" Combinatorics = "861a8166-3701-5b0c-9a16-15d98fcdc6aa" Compat = "34da2185-b29b-5c13-b0c7-acf172513d20" +ComponentArrays = "b0b7db55-cfe3-40fc-9ded-d10e2dbeff66" ConstructionBase = "187b0558-2788-49d3-abe0-74a17ed4e7c9" DataStructures = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8" DiffEqBase = "2b5f629d-d688-5b77-993f-72d75c75574e" @@ -67,6 +68,7 @@ ArrayInterface = "6, 7" BifurcationKit = "0.3" Combinatorics = "1" Compat = "3.42, 4" +ComponentArrays = "0.15" ConstructionBase = "1" DataStructures = "0.17, 0.18" DeepDiffs = "1" diff --git a/docs/src/basics/Composition.md b/docs/src/basics/Composition.md index 78dec8445b..39721104e8 100644 --- a/docs/src/basics/Composition.md +++ b/docs/src/basics/Composition.md @@ -37,10 +37,10 @@ connected = compose( equations(connected) #4-element Vector{Equation}: -# Differential(t)(decay1₊f(t)) ~ 0 -# decay2₊f(t) ~ decay1₊x(t) -# Differential(t)(decay1₊x(t)) ~ decay1₊f(t) - (decay1₊a*(decay1₊x(t))) -# Differential(t)(decay2₊x(t)) ~ decay2₊f(t) - (decay2₊a*(decay2₊x(t))) +# Differential(t)(decay1.f(t)) ~ 0 +# decay2.f(t) ~ decay1.x(t) +# Differential(t)(decay1.x(t)) ~ decay1.f(t) - (decay1.a*(decay1.x(t))) +# Differential(t)(decay2.x(t)) ~ decay2.f(t) - (decay2.a*(decay2.x(t))) simplified_sys = structural_simplify(connected) @@ -149,27 +149,27 @@ p = [a, b, c, d, e, f] level0 = ODESystem(Equation[], t, [], p; name = :level0) level1 = ODESystem(Equation[], t, [], []; name = :level1) ∘ level0 parameters(level1) -#level0₊a +#level0.a #b #c -#level0₊d -#level0₊e +#level0.d +#level0.e #f level2 = ODESystem(Equation[], t, [], []; name = :level2) ∘ level1 parameters(level2) -#level1₊level0₊a -#level1₊b +#level1.level0.a +#level1.b #c -#level0₊d -#level1₊level0₊e +#level0.d +#level1.level0.e #f level3 = ODESystem(Equation[], t, [], []; name = :level3) ∘ level2 parameters(level3) -#level2₊level1₊level0₊a -#level2₊level1₊b -#level2₊c -#level2₊level0₊d -#level1₊level0₊e +#level2.level1.level0.a +#level2.level1.b +#level2.c +#level2.level0.d +#level1.level0.e #f ``` diff --git a/docs/src/basics/Events.md b/docs/src/basics/Events.md index 33ce84d31e..6f715b153b 100644 --- a/docs/src/basics/Events.md +++ b/docs/src/basics/Events.md @@ -172,10 +172,10 @@ When accessing variables of a sub-system, it can be useful to rename them (alternatively, an affect function may be reused in different contexts): ```julia -[x ~ 0] => (affect!, [resistor₊v => :v, x], [p, q => :p2], [], ctx) +[x ~ 0] => (affect!, [resistor.v => :v, x], [p, q => :p2], [], ctx) ``` -Here, the symbolic variable `resistor₊v` is passed as `v` while the symbolic +Here, the symbolic variable `resistor.v` is passed as `v` while the symbolic parameter `q` has been renamed `p2`. As an example, here is the bouncing ball example from above using the functional diff --git a/docs/src/tutorials/domain_connections.md b/docs/src/tutorials/domain_connections.md index d6dc2d8781..ed165bef66 100644 --- a/docs/src/tutorials/domain_connections.md +++ b/docs/src/tutorials/domain_connections.md @@ -115,7 +115,7 @@ end nothing #hide ``` -To see how the domain works, we can examine the set parameter values for each of the ports `src.port` and `vol.port`. First we assemble the system using `structural_simplify()` and then check the default value of `vol.port.ρ`, whichs points to the setter value `fluid₊ρ`. Likewise, `src.port.ρ`, will also point to the setter value `fluid₊ρ`. Therefore, there is now only 1 defined density value `fluid₊ρ` which sets the density for the connected network. +To see how the domain works, we can examine the set parameter values for each of the ports `src.port` and `vol.port`. First we assemble the system using `structural_simplify()` and then check the default value of `vol.port.ρ`, whichs points to the setter value `fluid.ρ`. Likewise, `src.port.ρ`, will also point to the setter value `fluid.ρ`. Therefore, there is now only 1 defined density value `fluid.ρ` which sets the density for the connected network. ```@repl domain sys = structural_simplify(odesys) @@ -181,7 +181,7 @@ end nothing #hide ``` -After running `structural_simplify()` on `actsys2`, the defaults will show that `act.port_a.ρ` points to `fluid_a₊ρ` and `act.port_b.ρ` points to `fluid_b₊ρ`. This is a special case, in most cases a hydraulic system will have only 1 fluid, however this simple system has 2 separate domain networks. Therefore, we can connect a single fluid to both networks. This does not interfere with the mathematical equations of the system, since no unknown variables are connected. +After running `structural_simplify()` on `actsys2`, the defaults will show that `act.port_a.ρ` points to `fluid_a.ρ` and `act.port_b.ρ` points to `fluid_b.ρ`. This is a special case, in most cases a hydraulic system will have only 1 fluid, however this simple system has 2 separate domain networks. Therefore, we can connect a single fluid to both networks. This does not interfere with the mathematical equations of the system, since no unknown variables are connected. ```@example domain @component function ActuatorSystem1(; name) diff --git a/src/ModelingToolkit.jl b/src/ModelingToolkit.jl index 3b4435a19d..4b302dd404 100644 --- a/src/ModelingToolkit.jl +++ b/src/ModelingToolkit.jl @@ -22,6 +22,7 @@ using DiffEqCallbacks using Graphs import ExprTools: splitdef, combinedef import OrderedCollections +import ComponentArrays using SymbolicIndexingInterface using LinearAlgebra, SparseArrays, LabelledArrays diff --git a/src/inputoutput.jl b/src/inputoutput.jl index f9aa2e920c..41cce8cbe3 100644 --- a/src/inputoutput.jl +++ b/src/inputoutput.jl @@ -71,11 +71,11 @@ function is_bound(sys, u, stack = []) In the following scenario julia> observed(syss) 2-element Vector{Equation}: - sys₊y(tv) ~ sys₊x(tv) - y(tv) ~ sys₊x(tv) - sys₊y(t) is bound to the outer y(t) through the variable sys₊x(t) and should thus return is_bound(sys₊y(t)) = true. - When asking is_bound(sys₊y(t)), we know that we are looking through observed equations and can thus ask - if var is bound, if it is, then sys₊y(t) is also bound. This can lead to an infinite recursion, so we maintain a stack of variables we have previously asked about to be able to break cycles + sys.y(tv) ~ sys.x(tv) + y(tv) ~ sys.x(tv) + sys.y(t) is bound to the outer y(t) through the variable sys.x(t) and should thus return is_bound(sys.y(t)) = true. + When asking is_bound(sys.y(t)), we know that we are looking through observed equations and can thus ask + if var is bound, if it is, then sys.y(t) is also bound. This can lead to an infinite recursion, so we maintain a stack of variables we have previously asked about to be able to break cycles =# u ∈ Set(stack) && return false # Cycle detected eqs = equations(sys) @@ -119,8 +119,8 @@ function same_or_inner_namespace(u, var) nv = get_namespace(var) nu == nv || # namespaces are the same startswith(nv, nu) || # or nv starts with nu, i.e., nv is an inner namespace to nu - occursin('₊', string(getname(var))) && - !occursin('₊', string(getname(u))) # or u is top level but var is internal + occursin('.', string(getname(var))) && + !occursin('.', string(getname(u))) # or u is top level but var is internal end function inner_namespace(u, var) @@ -128,8 +128,8 @@ function inner_namespace(u, var) nv = get_namespace(var) nu == nv && return false startswith(nv, nu) || # or nv starts with nu, i.e., nv is an inner namespace to nu - occursin('₊', string(getname(var))) && - !occursin('₊', string(getname(u))) # or u is top level but var is internal + occursin('.', string(getname(var))) && + !occursin('.', string(getname(u))) # or u is top level but var is internal end """ @@ -139,11 +139,11 @@ Return the namespace of a variable as a string. If the variable is not namespace """ function get_namespace(x) sname = string(getname(x)) - parts = split(sname, '₊') + parts = split(sname, '.') if length(parts) == 1 return "" end - join(parts[1:(end - 1)], '₊') + join(parts[1:(end - 1)], '.') end """ diff --git a/src/systems/abstractsystem.jl b/src/systems/abstractsystem.jl index 52638e2e5f..aa6aa307de 100644 --- a/src/systems/abstractsystem.jl +++ b/src/systems/abstractsystem.jl @@ -365,8 +365,8 @@ function SymbolicIndexingInterface.is_variable(sys::AbstractSystem, sym::Symbol) return is_variable(ic, sym) end return any(isequal(sym), getname.(variable_symbols(sys))) || - count('₊', string(sym)) == 1 && - count(isequal(sym), Symbol.(nameof(sys), :₊, getname.(variable_symbols(sys)))) == + count('.', string(sym)) == 1 && + count(isequal(sym), Symbol.(nameof(sys), :., getname.(variable_symbols(sys)))) == 1 end @@ -399,9 +399,9 @@ function SymbolicIndexingInterface.variable_index(sys::AbstractSystem, sym::Symb idx = findfirst(isequal(sym), getname.(variable_symbols(sys))) if idx !== nothing return idx - elseif count('₊', string(sym)) == 1 + elseif count('.', string(sym)) == 1 return findfirst(isequal(sym), - Symbol.(nameof(sys), :₊, getname.(variable_symbols(sys)))) + Symbol.(nameof(sys), :., getname.(variable_symbols(sys)))) end return nothing end @@ -431,9 +431,9 @@ function SymbolicIndexingInterface.is_parameter(sys::AbstractSystem, sym::Symbol return is_parameter(ic, sym) end return any(isequal(sym), getname.(parameter_symbols(sys))) || - count('₊', string(sym)) == 1 && + count('.', string(sym)) == 1 && count(isequal(sym), - Symbol.(nameof(sys), :₊, getname.(parameter_symbols(sys)))) == 1 + Symbol.(nameof(sys), :., getname.(parameter_symbols(sys)))) == 1 end function SymbolicIndexingInterface.parameter_index(sys::AbstractSystem, sym) @@ -466,9 +466,9 @@ function SymbolicIndexingInterface.parameter_index(sys::AbstractSystem, sym::Sym idx = findfirst(isequal(sym), getname.(parameter_symbols(sys))) if idx !== nothing return idx - elseif count('₊', string(sym)) == 1 + elseif count('.', string(sym)) == 1 return findfirst(isequal(sym), - Symbol.(nameof(sys), :₊, getname.(parameter_symbols(sys)))) + Symbol.(nameof(sys), :., getname.(parameter_symbols(sys)))) end return nothing end @@ -889,7 +889,7 @@ function renamespace(sys, x) elseif x isa AbstractSystem rename(x, renamespace(sys, nameof(x))) else - Symbol(getname(sys), :₊, x) + Symbol(getname(sys), :., x) end end @@ -1248,7 +1248,7 @@ function round_trip_eq(eq::Equation, var2name) syss = get_systems(eq.rhs) call = Expr(:call, connect) for sys in syss - strs = split(string(nameof(sys)), "₊") + strs = split(string(nameof(sys)), ".") s = Symbol(strs[1]) for st in strs[2:end] s = Expr(:., s, Meta.quot(Symbol(st))) diff --git a/src/systems/callbacks.jl b/src/systems/callbacks.jl index 634cf6a01b..49c63493a4 100644 --- a/src/systems/callbacks.jl +++ b/src/systems/callbacks.jl @@ -512,6 +512,24 @@ function generate_rootfinding_callback(cbs, sys::AbstractODESystem, dvs = unknow end end +# Put a wrapper on NamedTuple so that u.resistor.v indexes like u.var"resistor.v" +# Required for hierarchical, but a hack that should be fixed in the future +struct NamedTupleSymbolFix{T} + x::T + sym::Symbol + +end +NamedTupleSymbolFix(x) = NamedTupleSymbolFix(x, Symbol("")) +function Base.getproperty(u::NamedTupleSymbolFix, s::Symbol) + newsym = getfield(u,:sym) == Symbol("") ? s : Symbol(getfield(u,:sym), ".", s) + x = getfield(u,:x) + if newsym in keys(x) + getproperty(x, newsym) + else + NamedTupleSymbolFix(x, newsym) + end +end + function compile_user_affect(affect::FunctionalAffect, sys, dvs, ps; kwargs...) dvs_ind = Dict(reverse(en) for en in enumerate(dvs)) v_inds = map(sym -> dvs_ind[sym], unknowns(affect)) @@ -526,9 +544,10 @@ function compile_user_affect(affect::FunctionalAffect, sys, dvs, ps; kwargs...) # 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 + NamedTuple |> NamedTupleSymbolFix + p = filter(x -> !isnothing(x[2]), collect(zip(parameters_syms(affect), p_inds))) |> - NamedTuple + NamedTuple |> NamedTupleSymbolFix let u = u, p = p, user_affect = func(affect), ctx = context(affect) function (integ) diff --git a/src/systems/connectors.jl b/src/systems/connectors.jl index f4fd5116e8..5fded8f101 100644 --- a/src/systems/connectors.jl +++ b/src/systems/connectors.jl @@ -129,7 +129,7 @@ function generate_isouter(sys::AbstractSystem) function isouter(sys)::Bool s = string(nameof(sys)) isconnector(sys) || error("$s is not a connector!") - idx = findfirst(isequal('₊'), s) + idx = findfirst(isequal('.'), s) parent_name = Symbol(idx === nothing ? s : s[1:prevind(s, idx)]) parent_name in outer_connectors end diff --git a/test/funcaffect.jl b/test/funcaffect.jl index 3004044d61..80afbf2ea9 100644 --- a/test/funcaffect.jl +++ b/test/funcaffect.jl @@ -123,7 +123,7 @@ i8 = findfirst(==(8.0), sol[:t]) ctx = [0] function affect4!(integ, u, p, ctx) ctx[1] += 1 - @test u.resistor₊v == 1 + @test u.resistor.v == 1 end s1 = compose( ODESystem(Equation[], t, [], [], name = :s1, @@ -137,7 +137,7 @@ sol = solve(prob, Tsit5()) include("../examples/rc_model.jl") function affect5!(integ, u, p, ctx) - @test integ.u[u.capacitor₊v] ≈ 0.3 + @test integ.u[u.capacitor.v] ≈ 0.3 integ.ps[p.C] *= 200 end diff --git a/test/input_output_handling.jl b/test/input_output_handling.jl index c63116638b..f5b1c56d43 100644 --- a/test/input_output_handling.jl +++ b/test/input_output_handling.jl @@ -34,8 +34,8 @@ end @test get_namespace(x) == "" @test get_namespace(sys.x) == "sys" @test get_namespace(sys2.x) == "sys2" -@test get_namespace(sys2.sys.x) == "sys2₊sys" -@test get_namespace(sys21.sys1.v) == "sys21₊sys1" +@test get_namespace(sys2.sys.x) == "sys2.sys" +@test get_namespace(sys21.sys1.v) == "sys21.sys1" @test !is_bound(sys, u) @test !is_bound(sys, x) diff --git a/test/inputoutput.jl b/test/inputoutput.jl index fde9c68516..0480a1f549 100644 --- a/test/inputoutput.jl +++ b/test/inputoutput.jl @@ -19,8 +19,8 @@ connected = ODESystem(Equation[], t, [], [], observed = connections, sys = connected -@variables lorenz1₊F lorenz2₊F -@test pins(connected) == Variable[lorenz1₊F, lorenz2₊F] +@variables lorenz1.F lorenz2.F +@test pins(connected) == Variable[lorenz1.F, lorenz2.F] @test isequal(observed(connected), [connections..., lorenz1.u ~ lorenz1.x + lorenz1.y - lorenz1.z, @@ -40,7 +40,7 @@ simplifyeqs(eqs) = Equation.((x -> x.lhs).(eqs), simplify.((x -> x.rhs).(eqs))) @test isequal(simplifyeqs(equations(connected)), simplifyeqs(collapsed_eqs)) -# Variables indicated to be input/output +# Variables indicated to be input/output @variables x [input = true] @test hasmetadata(x, Symbolics.option_to_metadata_type(Val(:input))) @test getmetadata(x, Symbolics.option_to_metadata_type(Val(:input))) == true diff --git a/test/odesystem.jl b/test/odesystem.jl index dade28de81..4d64a72068 100644 --- a/test/odesystem.jl +++ b/test/odesystem.jl @@ -1109,9 +1109,9 @@ function RealExpressionSystem(; name) vars = @variables begin x(t) z(t)[1:1] - end # doing a collect on z doesn't work either. - @named e1 = RealExpression(y = x) # This works perfectly. - @named e2 = RealExpression(y = z[1]) # This bugs. However, `full_equations(e2)` works as expected. + end # doing a collect on z doesn't work either. + @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) end @@ -1166,7 +1166,7 @@ end # Namespacing of array variables @variables x(t)[1:2] @named sys = ODESystem(Equation[], t) -@test getname(unknowns(sys, x)) == :sys₊x +@test getname(unknowns(sys, x)) == Symbol("sys.x") @test size(unknowns(sys, x)) == size(x) # Issue#2667 diff --git a/test/variable_scope.jl b/test/variable_scope.jl index 81a248f08f..00eb383cdf 100644 --- a/test/variable_scope.jl +++ b/test/variable_scope.jl @@ -31,25 +31,25 @@ eqs = [0 ~ a names = ModelingToolkit.getname.(unknowns(sys)) @test :d in names -@test Symbol("sub1₊c") in names -@test Symbol("sub1₊sub2₊b") in names -@test Symbol("sub1₊sub2₊sub3₊a") in names -@test Symbol("sub1₊sub2₊sub4₊a") in names +@test Symbol("sub1.c") in names +@test Symbol("sub1.sub2.b") in names +@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], []) @test ModelingToolkit.getname(ModelingToolkit.namespace_expr( ModelingToolkit.namespace_expr(b, foo), - bar)) == Symbol("bar₊b") + bar)) == Symbol("bar.b") function renamed(nss, sym) ModelingToolkit.getname(foldr(ModelingToolkit.renamespace, nss, init = sym)) end -@test renamed([:foo :bar :baz], a) == Symbol("foo₊bar₊baz₊a") -@test renamed([:foo :bar :baz], b) == Symbol("foo₊bar₊b") -@test renamed([:foo :bar :baz], c) == Symbol("foo₊c") +@test renamed([:foo :bar :baz], a) == Symbol("foo.bar.baz.a") +@test renamed([:foo :bar :baz], b) == Symbol("foo.bar.b") +@test renamed([:foo :bar :baz], c) == Symbol("foo.c") @test renamed([:foo :bar :baz], d) == :d @parameters t a b c d e f @@ -67,12 +67,12 @@ level3 = ODESystem(Equation[], t, [], []; name = :level3) ∘ level2 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[1], Symbol("level2.level1.level0.a")) +@test isequal(ps[2], Symbol("level2.level1.b")) +@test isequal(ps[3], Symbol("level2.c")) +@test isequal(ps[4], Symbol("level2.level0.d")) +@test isequal(ps[5], Symbol("level1.level0.e")) +@test isequal(ps[6], Symbol("f")) # Issue@2252 # Tests from PR#2354 @@ -82,4 +82,4 @@ arr0 = ODESystem(Equation[], t, [], arr_p; name = :arr0) arr1 = ODESystem(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")) +@test isequal(arr_ps[2], Symbol("arr0.xx")) From 5a8742d12332addba489b05d4610d89f64a68adb Mon Sep 17 00:00:00 2001 From: Christopher Rackauckas Date: Wed, 12 Jun 2024 09:43:34 -0400 Subject: [PATCH 190/316] format --- src/systems/callbacks.jl | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/systems/callbacks.jl b/src/systems/callbacks.jl index 49c63493a4..3d282d4521 100644 --- a/src/systems/callbacks.jl +++ b/src/systems/callbacks.jl @@ -517,12 +517,11 @@ end struct NamedTupleSymbolFix{T} x::T sym::Symbol - end NamedTupleSymbolFix(x) = NamedTupleSymbolFix(x, Symbol("")) function Base.getproperty(u::NamedTupleSymbolFix, s::Symbol) - newsym = getfield(u,:sym) == Symbol("") ? s : Symbol(getfield(u,:sym), ".", s) - x = getfield(u,:x) + newsym = getfield(u, :sym) == Symbol("") ? s : Symbol(getfield(u, :sym), ".", s) + x = getfield(u, :x) if newsym in keys(x) getproperty(x, newsym) else From dca58dcbfeba938277efb384ba43442a64419a65 Mon Sep 17 00:00:00 2001 From: Christopher Rackauckas Date: Wed, 12 Jun 2024 10:17:09 -0400 Subject: [PATCH 191/316] pass indexes on --- src/systems/callbacks.jl | 1 + 1 file changed, 1 insertion(+) diff --git a/src/systems/callbacks.jl b/src/systems/callbacks.jl index 3d282d4521..783276ceeb 100644 --- a/src/systems/callbacks.jl +++ b/src/systems/callbacks.jl @@ -528,6 +528,7 @@ function Base.getproperty(u::NamedTupleSymbolFix, s::Symbol) NamedTupleSymbolFix(x, newsym) end end +Base.getindex(u::NamedTupleSymbolFix, idxs...) = getfield(u,:x)[idxs...] function compile_user_affect(affect::FunctionalAffect, sys, dvs, ps; kwargs...) dvs_ind = Dict(reverse(en) for en in enumerate(dvs)) From 6348c30dfc39a05ed8ca81ea95c41f2e5bf14be4 Mon Sep 17 00:00:00 2001 From: Christopher Rackauckas Date: Wed, 12 Jun 2024 10:30:54 -0400 Subject: [PATCH 192/316] format --- src/systems/callbacks.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/systems/callbacks.jl b/src/systems/callbacks.jl index 783276ceeb..6e1e0b16e7 100644 --- a/src/systems/callbacks.jl +++ b/src/systems/callbacks.jl @@ -528,7 +528,7 @@ function Base.getproperty(u::NamedTupleSymbolFix, s::Symbol) NamedTupleSymbolFix(x, newsym) end end -Base.getindex(u::NamedTupleSymbolFix, idxs...) = getfield(u,:x)[idxs...] +Base.getindex(u::NamedTupleSymbolFix, idxs...) = getfield(u, :x)[idxs...] function compile_user_affect(affect::FunctionalAffect, sys, dvs, ps; kwargs...) dvs_ind = Dict(reverse(en) for en in enumerate(dvs)) From 910366243fca0b0a306a6a5ee2a7a03bdc52f0ad Mon Sep 17 00:00:00 2001 From: Christopher Rackauckas Date: Wed, 12 Jun 2024 10:44:41 -0400 Subject: [PATCH 193/316] Update src/systems/callbacks.jl --- src/systems/callbacks.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/systems/callbacks.jl b/src/systems/callbacks.jl index 6e1e0b16e7..5f416d7545 100644 --- a/src/systems/callbacks.jl +++ b/src/systems/callbacks.jl @@ -528,7 +528,7 @@ function Base.getproperty(u::NamedTupleSymbolFix, s::Symbol) NamedTupleSymbolFix(x, newsym) end end -Base.getindex(u::NamedTupleSymbolFix, idxs...) = getfield(u, :x)[idxs...] +Base.getindex(u::NamedTupleSymbolFix, idxs::Int...) = getfield(u, :x)[idxs...] function compile_user_affect(affect::FunctionalAffect, sys, dvs, ps; kwargs...) dvs_ind = Dict(reverse(en) for en in enumerate(dvs)) From f041a772c584b12a58bafbc1bcb43b58deaa998b Mon Sep 17 00:00:00 2001 From: Yingbo Ma Date: Wed, 12 Jun 2024 10:46:09 -0400 Subject: [PATCH 194/316] Update test --- test/initializationsystem.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/initializationsystem.jl b/test/initializationsystem.jl index 101bab5c0d..6e3af256be 100644 --- a/test/initializationsystem.jl +++ b/test/initializationsystem.jl @@ -22,7 +22,7 @@ initprob = ModelingToolkit.InitializationProblem(pend, 0.0, [x => 1, y => 0], [g @test initprob isa NonlinearProblem sol = solve(initprob) @test SciMLBase.successful_retcode(sol) -@test sol.u == [1.0, 0.0, 0.0, 0.0] +@test sol.u == [0.0, 0.0, 0.0] @test maximum(abs.(sol[conditions])) < 1e-14 initprob = ModelingToolkit.InitializationProblem( From cb15b2ff07e7e1619ce5ab9c5eda982fe3914da0 Mon Sep 17 00:00:00 2001 From: Yingbo Ma Date: Wed, 12 Jun 2024 12:01:10 -0400 Subject: [PATCH 195/316] Update Project.toml --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 72be0d6bf8..c6e2557d14 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.17.1" +version = "9.18.0" [deps] AbstractTrees = "1520ce14-60c1-5f80-bbc7-55ef81b5835c" From ca4de6daf6cf40dde6cd247231f5d22b337e21a1 Mon Sep 17 00:00:00 2001 From: Christopher Rackauckas Date: Wed, 12 Jun 2024 12:49:49 -0400 Subject: [PATCH 196/316] Remove componentarrays This slipped in but a separate solution was found that didn't use it, so it's actually unused. --- Project.toml | 2 -- 1 file changed, 2 deletions(-) diff --git a/Project.toml b/Project.toml index c6e2557d14..a035dd01df 100644 --- a/Project.toml +++ b/Project.toml @@ -8,7 +8,6 @@ AbstractTrees = "1520ce14-60c1-5f80-bbc7-55ef81b5835c" ArrayInterface = "4fba245c-0d91-5ea0-9b3e-6abc04ee57a9" Combinatorics = "861a8166-3701-5b0c-9a16-15d98fcdc6aa" Compat = "34da2185-b29b-5c13-b0c7-acf172513d20" -ComponentArrays = "b0b7db55-cfe3-40fc-9ded-d10e2dbeff66" ConstructionBase = "187b0558-2788-49d3-abe0-74a17ed4e7c9" DataStructures = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8" DiffEqBase = "2b5f629d-d688-5b77-993f-72d75c75574e" @@ -68,7 +67,6 @@ ArrayInterface = "6, 7" BifurcationKit = "0.3" Combinatorics = "1" Compat = "3.42, 4" -ComponentArrays = "0.15" ConstructionBase = "1" DataStructures = "0.17, 0.18" DeepDiffs = "1" From 360999c7456a5394626b2bea5d3f4eae37b4087a Mon Sep 17 00:00:00 2001 From: Christopher Rackauckas Date: Wed, 12 Jun 2024 12:50:13 -0400 Subject: [PATCH 197/316] Update ModelingToolkit.jl --- src/ModelingToolkit.jl | 1 - 1 file changed, 1 deletion(-) diff --git a/src/ModelingToolkit.jl b/src/ModelingToolkit.jl index 4b302dd404..3b4435a19d 100644 --- a/src/ModelingToolkit.jl +++ b/src/ModelingToolkit.jl @@ -22,7 +22,6 @@ using DiffEqCallbacks using Graphs import ExprTools: splitdef, combinedef import OrderedCollections -import ComponentArrays using SymbolicIndexingInterface using LinearAlgebra, SparseArrays, LabelledArrays From ae9a98de245cb729e77011f67f6e3eb9295e8f1d Mon Sep 17 00:00:00 2001 From: Yingbo Ma Date: Wed, 12 Jun 2024 15:13:10 -0400 Subject: [PATCH 198/316] =?UTF-8?q?Revert=20"Bye=20bye=20=E2=82=8A,=20use?= =?UTF-8?q?=20var=20symbols=20with=20."?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/src/basics/Composition.md | 32 ++++++++++++------------ docs/src/basics/Events.md | 4 +-- docs/src/tutorials/domain_connections.md | 4 +-- src/inputoutput.jl | 22 ++++++++-------- src/systems/abstractsystem.jl | 20 +++++++-------- src/systems/callbacks.jl | 23 ++--------------- src/systems/connectors.jl | 2 +- test/funcaffect.jl | 4 +-- test/input_output_handling.jl | 4 +-- test/inputoutput.jl | 6 ++--- test/odesystem.jl | 8 +++--- test/variable_scope.jl | 30 +++++++++++----------- 12 files changed, 70 insertions(+), 89 deletions(-) diff --git a/docs/src/basics/Composition.md b/docs/src/basics/Composition.md index 39721104e8..78dec8445b 100644 --- a/docs/src/basics/Composition.md +++ b/docs/src/basics/Composition.md @@ -37,10 +37,10 @@ connected = compose( equations(connected) #4-element Vector{Equation}: -# Differential(t)(decay1.f(t)) ~ 0 -# decay2.f(t) ~ decay1.x(t) -# Differential(t)(decay1.x(t)) ~ decay1.f(t) - (decay1.a*(decay1.x(t))) -# Differential(t)(decay2.x(t)) ~ decay2.f(t) - (decay2.a*(decay2.x(t))) +# Differential(t)(decay1₊f(t)) ~ 0 +# decay2₊f(t) ~ decay1₊x(t) +# Differential(t)(decay1₊x(t)) ~ decay1₊f(t) - (decay1₊a*(decay1₊x(t))) +# Differential(t)(decay2₊x(t)) ~ decay2₊f(t) - (decay2₊a*(decay2₊x(t))) simplified_sys = structural_simplify(connected) @@ -149,27 +149,27 @@ p = [a, b, c, d, e, f] level0 = ODESystem(Equation[], t, [], p; name = :level0) level1 = ODESystem(Equation[], t, [], []; name = :level1) ∘ level0 parameters(level1) -#level0.a +#level0₊a #b #c -#level0.d -#level0.e +#level0₊d +#level0₊e #f level2 = ODESystem(Equation[], t, [], []; name = :level2) ∘ level1 parameters(level2) -#level1.level0.a -#level1.b +#level1₊level0₊a +#level1₊b #c -#level0.d -#level1.level0.e +#level0₊d +#level1₊level0₊e #f level3 = ODESystem(Equation[], t, [], []; name = :level3) ∘ level2 parameters(level3) -#level2.level1.level0.a -#level2.level1.b -#level2.c -#level2.level0.d -#level1.level0.e +#level2₊level1₊level0₊a +#level2₊level1₊b +#level2₊c +#level2₊level0₊d +#level1₊level0₊e #f ``` diff --git a/docs/src/basics/Events.md b/docs/src/basics/Events.md index 6f715b153b..33ce84d31e 100644 --- a/docs/src/basics/Events.md +++ b/docs/src/basics/Events.md @@ -172,10 +172,10 @@ When accessing variables of a sub-system, it can be useful to rename them (alternatively, an affect function may be reused in different contexts): ```julia -[x ~ 0] => (affect!, [resistor.v => :v, x], [p, q => :p2], [], ctx) +[x ~ 0] => (affect!, [resistor₊v => :v, x], [p, q => :p2], [], ctx) ``` -Here, the symbolic variable `resistor.v` is passed as `v` while the symbolic +Here, the symbolic variable `resistor₊v` is passed as `v` while the symbolic parameter `q` has been renamed `p2`. As an example, here is the bouncing ball example from above using the functional diff --git a/docs/src/tutorials/domain_connections.md b/docs/src/tutorials/domain_connections.md index ed165bef66..d6dc2d8781 100644 --- a/docs/src/tutorials/domain_connections.md +++ b/docs/src/tutorials/domain_connections.md @@ -115,7 +115,7 @@ end nothing #hide ``` -To see how the domain works, we can examine the set parameter values for each of the ports `src.port` and `vol.port`. First we assemble the system using `structural_simplify()` and then check the default value of `vol.port.ρ`, whichs points to the setter value `fluid.ρ`. Likewise, `src.port.ρ`, will also point to the setter value `fluid.ρ`. Therefore, there is now only 1 defined density value `fluid.ρ` which sets the density for the connected network. +To see how the domain works, we can examine the set parameter values for each of the ports `src.port` and `vol.port`. First we assemble the system using `structural_simplify()` and then check the default value of `vol.port.ρ`, whichs points to the setter value `fluid₊ρ`. Likewise, `src.port.ρ`, will also point to the setter value `fluid₊ρ`. Therefore, there is now only 1 defined density value `fluid₊ρ` which sets the density for the connected network. ```@repl domain sys = structural_simplify(odesys) @@ -181,7 +181,7 @@ end nothing #hide ``` -After running `structural_simplify()` on `actsys2`, the defaults will show that `act.port_a.ρ` points to `fluid_a.ρ` and `act.port_b.ρ` points to `fluid_b.ρ`. This is a special case, in most cases a hydraulic system will have only 1 fluid, however this simple system has 2 separate domain networks. Therefore, we can connect a single fluid to both networks. This does not interfere with the mathematical equations of the system, since no unknown variables are connected. +After running `structural_simplify()` on `actsys2`, the defaults will show that `act.port_a.ρ` points to `fluid_a₊ρ` and `act.port_b.ρ` points to `fluid_b₊ρ`. This is a special case, in most cases a hydraulic system will have only 1 fluid, however this simple system has 2 separate domain networks. Therefore, we can connect a single fluid to both networks. This does not interfere with the mathematical equations of the system, since no unknown variables are connected. ```@example domain @component function ActuatorSystem1(; name) diff --git a/src/inputoutput.jl b/src/inputoutput.jl index 41cce8cbe3..f9aa2e920c 100644 --- a/src/inputoutput.jl +++ b/src/inputoutput.jl @@ -71,11 +71,11 @@ function is_bound(sys, u, stack = []) In the following scenario julia> observed(syss) 2-element Vector{Equation}: - sys.y(tv) ~ sys.x(tv) - y(tv) ~ sys.x(tv) - sys.y(t) is bound to the outer y(t) through the variable sys.x(t) and should thus return is_bound(sys.y(t)) = true. - When asking is_bound(sys.y(t)), we know that we are looking through observed equations and can thus ask - if var is bound, if it is, then sys.y(t) is also bound. This can lead to an infinite recursion, so we maintain a stack of variables we have previously asked about to be able to break cycles + sys₊y(tv) ~ sys₊x(tv) + y(tv) ~ sys₊x(tv) + sys₊y(t) is bound to the outer y(t) through the variable sys₊x(t) and should thus return is_bound(sys₊y(t)) = true. + When asking is_bound(sys₊y(t)), we know that we are looking through observed equations and can thus ask + if var is bound, if it is, then sys₊y(t) is also bound. This can lead to an infinite recursion, so we maintain a stack of variables we have previously asked about to be able to break cycles =# u ∈ Set(stack) && return false # Cycle detected eqs = equations(sys) @@ -119,8 +119,8 @@ function same_or_inner_namespace(u, var) nv = get_namespace(var) nu == nv || # namespaces are the same startswith(nv, nu) || # or nv starts with nu, i.e., nv is an inner namespace to nu - occursin('.', string(getname(var))) && - !occursin('.', string(getname(u))) # or u is top level but var is internal + occursin('₊', string(getname(var))) && + !occursin('₊', string(getname(u))) # or u is top level but var is internal end function inner_namespace(u, var) @@ -128,8 +128,8 @@ function inner_namespace(u, var) nv = get_namespace(var) nu == nv && return false startswith(nv, nu) || # or nv starts with nu, i.e., nv is an inner namespace to nu - occursin('.', string(getname(var))) && - !occursin('.', string(getname(u))) # or u is top level but var is internal + occursin('₊', string(getname(var))) && + !occursin('₊', string(getname(u))) # or u is top level but var is internal end """ @@ -139,11 +139,11 @@ Return the namespace of a variable as a string. If the variable is not namespace """ function get_namespace(x) sname = string(getname(x)) - parts = split(sname, '.') + parts = split(sname, '₊') if length(parts) == 1 return "" end - join(parts[1:(end - 1)], '.') + join(parts[1:(end - 1)], '₊') end """ diff --git a/src/systems/abstractsystem.jl b/src/systems/abstractsystem.jl index aa6aa307de..52638e2e5f 100644 --- a/src/systems/abstractsystem.jl +++ b/src/systems/abstractsystem.jl @@ -365,8 +365,8 @@ function SymbolicIndexingInterface.is_variable(sys::AbstractSystem, sym::Symbol) return is_variable(ic, sym) end return any(isequal(sym), getname.(variable_symbols(sys))) || - count('.', string(sym)) == 1 && - count(isequal(sym), Symbol.(nameof(sys), :., getname.(variable_symbols(sys)))) == + count('₊', string(sym)) == 1 && + count(isequal(sym), Symbol.(nameof(sys), :₊, getname.(variable_symbols(sys)))) == 1 end @@ -399,9 +399,9 @@ function SymbolicIndexingInterface.variable_index(sys::AbstractSystem, sym::Symb idx = findfirst(isequal(sym), getname.(variable_symbols(sys))) if idx !== nothing return idx - elseif count('.', string(sym)) == 1 + elseif count('₊', string(sym)) == 1 return findfirst(isequal(sym), - Symbol.(nameof(sys), :., getname.(variable_symbols(sys)))) + Symbol.(nameof(sys), :₊, getname.(variable_symbols(sys)))) end return nothing end @@ -431,9 +431,9 @@ function SymbolicIndexingInterface.is_parameter(sys::AbstractSystem, sym::Symbol return is_parameter(ic, sym) end return any(isequal(sym), getname.(parameter_symbols(sys))) || - count('.', string(sym)) == 1 && + count('₊', string(sym)) == 1 && count(isequal(sym), - Symbol.(nameof(sys), :., getname.(parameter_symbols(sys)))) == 1 + Symbol.(nameof(sys), :₊, getname.(parameter_symbols(sys)))) == 1 end function SymbolicIndexingInterface.parameter_index(sys::AbstractSystem, sym) @@ -466,9 +466,9 @@ function SymbolicIndexingInterface.parameter_index(sys::AbstractSystem, sym::Sym idx = findfirst(isequal(sym), getname.(parameter_symbols(sys))) if idx !== nothing return idx - elseif count('.', string(sym)) == 1 + elseif count('₊', string(sym)) == 1 return findfirst(isequal(sym), - Symbol.(nameof(sys), :., getname.(parameter_symbols(sys)))) + Symbol.(nameof(sys), :₊, getname.(parameter_symbols(sys)))) end return nothing end @@ -889,7 +889,7 @@ function renamespace(sys, x) elseif x isa AbstractSystem rename(x, renamespace(sys, nameof(x))) else - Symbol(getname(sys), :., x) + Symbol(getname(sys), :₊, x) end end @@ -1248,7 +1248,7 @@ function round_trip_eq(eq::Equation, var2name) syss = get_systems(eq.rhs) call = Expr(:call, connect) for sys in syss - strs = split(string(nameof(sys)), ".") + strs = split(string(nameof(sys)), "₊") s = Symbol(strs[1]) for st in strs[2:end] s = Expr(:., s, Meta.quot(Symbol(st))) diff --git a/src/systems/callbacks.jl b/src/systems/callbacks.jl index 5f416d7545..634cf6a01b 100644 --- a/src/systems/callbacks.jl +++ b/src/systems/callbacks.jl @@ -512,24 +512,6 @@ function generate_rootfinding_callback(cbs, sys::AbstractODESystem, dvs = unknow end end -# Put a wrapper on NamedTuple so that u.resistor.v indexes like u.var"resistor.v" -# Required for hierarchical, but a hack that should be fixed in the future -struct NamedTupleSymbolFix{T} - x::T - sym::Symbol -end -NamedTupleSymbolFix(x) = NamedTupleSymbolFix(x, Symbol("")) -function Base.getproperty(u::NamedTupleSymbolFix, s::Symbol) - newsym = getfield(u, :sym) == Symbol("") ? s : Symbol(getfield(u, :sym), ".", s) - x = getfield(u, :x) - if newsym in keys(x) - getproperty(x, newsym) - else - NamedTupleSymbolFix(x, newsym) - end -end -Base.getindex(u::NamedTupleSymbolFix, idxs::Int...) = getfield(u, :x)[idxs...] - function compile_user_affect(affect::FunctionalAffect, sys, dvs, ps; kwargs...) dvs_ind = Dict(reverse(en) for en in enumerate(dvs)) v_inds = map(sym -> dvs_ind[sym], unknowns(affect)) @@ -544,10 +526,9 @@ function compile_user_affect(affect::FunctionalAffect, sys, dvs, ps; kwargs...) # 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 |> NamedTupleSymbolFix - + NamedTuple p = filter(x -> !isnothing(x[2]), collect(zip(parameters_syms(affect), p_inds))) |> - NamedTuple |> NamedTupleSymbolFix + NamedTuple let u = u, p = p, user_affect = func(affect), ctx = context(affect) function (integ) diff --git a/src/systems/connectors.jl b/src/systems/connectors.jl index 5fded8f101..f4fd5116e8 100644 --- a/src/systems/connectors.jl +++ b/src/systems/connectors.jl @@ -129,7 +129,7 @@ function generate_isouter(sys::AbstractSystem) function isouter(sys)::Bool s = string(nameof(sys)) isconnector(sys) || error("$s is not a connector!") - idx = findfirst(isequal('.'), s) + idx = findfirst(isequal('₊'), s) parent_name = Symbol(idx === nothing ? s : s[1:prevind(s, idx)]) parent_name in outer_connectors end diff --git a/test/funcaffect.jl b/test/funcaffect.jl index 80afbf2ea9..3004044d61 100644 --- a/test/funcaffect.jl +++ b/test/funcaffect.jl @@ -123,7 +123,7 @@ i8 = findfirst(==(8.0), sol[:t]) ctx = [0] function affect4!(integ, u, p, ctx) ctx[1] += 1 - @test u.resistor.v == 1 + @test u.resistor₊v == 1 end s1 = compose( ODESystem(Equation[], t, [], [], name = :s1, @@ -137,7 +137,7 @@ sol = solve(prob, Tsit5()) include("../examples/rc_model.jl") function affect5!(integ, u, p, ctx) - @test integ.u[u.capacitor.v] ≈ 0.3 + @test integ.u[u.capacitor₊v] ≈ 0.3 integ.ps[p.C] *= 200 end diff --git a/test/input_output_handling.jl b/test/input_output_handling.jl index f5b1c56d43..c63116638b 100644 --- a/test/input_output_handling.jl +++ b/test/input_output_handling.jl @@ -34,8 +34,8 @@ end @test get_namespace(x) == "" @test get_namespace(sys.x) == "sys" @test get_namespace(sys2.x) == "sys2" -@test get_namespace(sys2.sys.x) == "sys2.sys" -@test get_namespace(sys21.sys1.v) == "sys21.sys1" +@test get_namespace(sys2.sys.x) == "sys2₊sys" +@test get_namespace(sys21.sys1.v) == "sys21₊sys1" @test !is_bound(sys, u) @test !is_bound(sys, x) diff --git a/test/inputoutput.jl b/test/inputoutput.jl index 0480a1f549..fde9c68516 100644 --- a/test/inputoutput.jl +++ b/test/inputoutput.jl @@ -19,8 +19,8 @@ connected = ODESystem(Equation[], t, [], [], observed = connections, sys = connected -@variables lorenz1.F lorenz2.F -@test pins(connected) == Variable[lorenz1.F, lorenz2.F] +@variables lorenz1₊F lorenz2₊F +@test pins(connected) == Variable[lorenz1₊F, lorenz2₊F] @test isequal(observed(connected), [connections..., lorenz1.u ~ lorenz1.x + lorenz1.y - lorenz1.z, @@ -40,7 +40,7 @@ simplifyeqs(eqs) = Equation.((x -> x.lhs).(eqs), simplify.((x -> x.rhs).(eqs))) @test isequal(simplifyeqs(equations(connected)), simplifyeqs(collapsed_eqs)) -# Variables indicated to be input/output +# Variables indicated to be input/output @variables x [input = true] @test hasmetadata(x, Symbolics.option_to_metadata_type(Val(:input))) @test getmetadata(x, Symbolics.option_to_metadata_type(Val(:input))) == true diff --git a/test/odesystem.jl b/test/odesystem.jl index 4d64a72068..dade28de81 100644 --- a/test/odesystem.jl +++ b/test/odesystem.jl @@ -1109,9 +1109,9 @@ function RealExpressionSystem(; name) vars = @variables begin x(t) z(t)[1:1] - end # doing a collect on z doesn't work either. - @named e1 = RealExpression(y = x) # This works perfectly. - @named e2 = RealExpression(y = z[1]) # This bugs. However, `full_equations(e2)` works as expected. + end # doing a collect on z doesn't work either. + @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) end @@ -1166,7 +1166,7 @@ end # Namespacing of array variables @variables x(t)[1:2] @named sys = ODESystem(Equation[], t) -@test getname(unknowns(sys, x)) == Symbol("sys.x") +@test getname(unknowns(sys, x)) == :sys₊x @test size(unknowns(sys, x)) == size(x) # Issue#2667 diff --git a/test/variable_scope.jl b/test/variable_scope.jl index 00eb383cdf..81a248f08f 100644 --- a/test/variable_scope.jl +++ b/test/variable_scope.jl @@ -31,25 +31,25 @@ eqs = [0 ~ a names = ModelingToolkit.getname.(unknowns(sys)) @test :d in names -@test Symbol("sub1.c") in names -@test Symbol("sub1.sub2.b") in names -@test Symbol("sub1.sub2.sub3.a") in names -@test Symbol("sub1.sub2.sub4.a") in names +@test Symbol("sub1₊c") in names +@test Symbol("sub1₊sub2₊b") in names +@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], []) @test ModelingToolkit.getname(ModelingToolkit.namespace_expr( ModelingToolkit.namespace_expr(b, foo), - bar)) == Symbol("bar.b") + bar)) == Symbol("bar₊b") function renamed(nss, sym) ModelingToolkit.getname(foldr(ModelingToolkit.renamespace, nss, init = sym)) end -@test renamed([:foo :bar :baz], a) == Symbol("foo.bar.baz.a") -@test renamed([:foo :bar :baz], b) == Symbol("foo.bar.b") -@test renamed([:foo :bar :baz], c) == Symbol("foo.c") +@test renamed([:foo :bar :baz], a) == Symbol("foo₊bar₊baz₊a") +@test renamed([:foo :bar :baz], b) == Symbol("foo₊bar₊b") +@test renamed([:foo :bar :baz], c) == Symbol("foo₊c") @test renamed([:foo :bar :baz], d) == :d @parameters t a b c d e f @@ -67,12 +67,12 @@ level3 = ODESystem(Equation[], t, [], []; name = :level3) ∘ level2 ps = ModelingToolkit.getname.(parameters(level3)) -@test isequal(ps[1], Symbol("level2.level1.level0.a")) -@test isequal(ps[2], Symbol("level2.level1.b")) -@test isequal(ps[3], Symbol("level2.c")) -@test isequal(ps[4], Symbol("level2.level0.d")) -@test isequal(ps[5], Symbol("level1.level0.e")) -@test isequal(ps[6], Symbol("f")) +@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) # Issue@2252 # Tests from PR#2354 @@ -82,4 +82,4 @@ arr0 = ODESystem(Equation[], t, [], arr_p; name = :arr0) arr1 = ODESystem(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")) +@test isequal(arr_ps[2], Symbol("arr0₊xx")) From c38aafafc532694599f54c27895ed930984d9ae8 Mon Sep 17 00:00:00 2001 From: Yingbo Ma Date: Wed, 12 Jun 2024 15:26:23 -0400 Subject: [PATCH 199/316] Use NAMESPACE_SEPARATOR --- src/ModelingToolkit.jl | 1 + src/inputoutput.jl | 12 ++++++------ src/systems/abstractsystem.jl | 24 ++++++++++++++---------- src/systems/connectors.jl | 2 +- 4 files changed, 22 insertions(+), 17 deletions(-) diff --git a/src/ModelingToolkit.jl b/src/ModelingToolkit.jl index 3b4435a19d..5be9e6dbb2 100644 --- a/src/ModelingToolkit.jl +++ b/src/ModelingToolkit.jl @@ -61,6 +61,7 @@ using Symbolics: _parse_vars, value, @derivatives, get_variables, NAMESPACE_SEPARATOR, set_scalar_metadata, setdefaultval, initial_state, transition, activeState, entry, hasnode, ticksInState, timeInState, fixpoint_sub, fast_substitute +const NAMESPACE_SEPARATOR_SYMBOL = Symbol(NAMESPACE_SEPARATOR) import Symbolics: rename, get_variables!, _solve, hessian_sparsity, jacobian_sparsity, isaffine, islinear, _iszero, _isone, tosymbol, lower_varname, diff2term, var_from_nested_derivative, diff --git a/src/inputoutput.jl b/src/inputoutput.jl index f9aa2e920c..31dc393418 100644 --- a/src/inputoutput.jl +++ b/src/inputoutput.jl @@ -119,8 +119,8 @@ function same_or_inner_namespace(u, var) nv = get_namespace(var) nu == nv || # namespaces are the same startswith(nv, nu) || # or nv starts with nu, i.e., nv is an inner namespace to nu - occursin('₊', string(getname(var))) && - !occursin('₊', string(getname(u))) # or u is top level but var is internal + occursin(NAMESPACE_SEPARATOR, string(getname(var))) && + !occursin(NAMESPACE_SEPARATOR, string(getname(u))) # or u is top level but var is internal end function inner_namespace(u, var) @@ -128,8 +128,8 @@ function inner_namespace(u, var) nv = get_namespace(var) nu == nv && return false startswith(nv, nu) || # or nv starts with nu, i.e., nv is an inner namespace to nu - occursin('₊', string(getname(var))) && - !occursin('₊', string(getname(u))) # or u is top level but var is internal + occursin(NAMESPACE_SEPARATOR, string(getname(var))) && + !occursin(NAMESPACE_SEPARATOR, string(getname(u))) # or u is top level but var is internal end """ @@ -139,11 +139,11 @@ Return the namespace of a variable as a string. If the variable is not namespace """ function get_namespace(x) sname = string(getname(x)) - parts = split(sname, '₊') + parts = split(sname, NAMESPACE_SEPARATOR) if length(parts) == 1 return "" end - join(parts[1:(end - 1)], '₊') + join(parts[1:(end - 1)], NAMESPACE_SEPARATOR) end """ diff --git a/src/systems/abstractsystem.jl b/src/systems/abstractsystem.jl index 52638e2e5f..6b23750133 100644 --- a/src/systems/abstractsystem.jl +++ b/src/systems/abstractsystem.jl @@ -365,8 +365,9 @@ function SymbolicIndexingInterface.is_variable(sys::AbstractSystem, sym::Symbol) return is_variable(ic, sym) end return any(isequal(sym), getname.(variable_symbols(sys))) || - count('₊', string(sym)) == 1 && - count(isequal(sym), Symbol.(nameof(sys), :₊, getname.(variable_symbols(sys)))) == + count(NAMESPACE_SEPARATOR, string(sym)) == 1 && + count(isequal(sym), + Symbol.(nameof(sys), NAMESPACE_SEPARATOR_SYMBOL, getname.(variable_symbols(sys)))) == 1 end @@ -399,9 +400,10 @@ function SymbolicIndexingInterface.variable_index(sys::AbstractSystem, sym::Symb idx = findfirst(isequal(sym), getname.(variable_symbols(sys))) if idx !== nothing return idx - elseif count('₊', string(sym)) == 1 + elseif count(NAMESPACE_SEPARATOR, string(sym)) == 1 return findfirst(isequal(sym), - Symbol.(nameof(sys), :₊, getname.(variable_symbols(sys)))) + Symbol.( + nameof(sys), NAMESPACE_SEPARATOR_SYMBOL, getname.(variable_symbols(sys)))) end return nothing end @@ -431,9 +433,10 @@ function SymbolicIndexingInterface.is_parameter(sys::AbstractSystem, sym::Symbol return is_parameter(ic, sym) end return any(isequal(sym), getname.(parameter_symbols(sys))) || - count('₊', string(sym)) == 1 && + count(NAMESPACE_SEPARATOR, string(sym)) == 1 && count(isequal(sym), - Symbol.(nameof(sys), :₊, getname.(parameter_symbols(sys)))) == 1 + Symbol.(nameof(sys), NAMESPACE_SEPARATOR_SYMBOL, getname.(parameter_symbols(sys)))) == + 1 end function SymbolicIndexingInterface.parameter_index(sys::AbstractSystem, sym) @@ -466,9 +469,10 @@ function SymbolicIndexingInterface.parameter_index(sys::AbstractSystem, sym::Sym idx = findfirst(isequal(sym), getname.(parameter_symbols(sys))) if idx !== nothing return idx - elseif count('₊', string(sym)) == 1 + elseif count(NAMESPACE_SEPARATOR, string(sym)) == 1 return findfirst(isequal(sym), - Symbol.(nameof(sys), :₊, getname.(parameter_symbols(sys)))) + Symbol.( + nameof(sys), NAMESPACE_SEPARATOR_SYMBOL, getname.(parameter_symbols(sys)))) end return nothing end @@ -889,7 +893,7 @@ function renamespace(sys, x) elseif x isa AbstractSystem rename(x, renamespace(sys, nameof(x))) else - Symbol(getname(sys), :₊, x) + Symbol(getname(sys), NAMESPACE_SEPARATOR_SYMBOL, x) end end @@ -1248,7 +1252,7 @@ function round_trip_eq(eq::Equation, var2name) syss = get_systems(eq.rhs) call = Expr(:call, connect) for sys in syss - strs = split(string(nameof(sys)), "₊") + strs = split(string(nameof(sys)), NAMESPACE_SEPARATOR) s = Symbol(strs[1]) for st in strs[2:end] s = Expr(:., s, Meta.quot(Symbol(st))) diff --git a/src/systems/connectors.jl b/src/systems/connectors.jl index f4fd5116e8..ba40a57d38 100644 --- a/src/systems/connectors.jl +++ b/src/systems/connectors.jl @@ -129,7 +129,7 @@ function generate_isouter(sys::AbstractSystem) function isouter(sys)::Bool s = string(nameof(sys)) isconnector(sys) || error("$s is not a connector!") - idx = findfirst(isequal('₊'), s) + idx = findfirst(isequal(NAMESPACE_SEPARATOR), s) parent_name = Symbol(idx === nothing ? s : s[1:prevind(s, idx)]) parent_name in outer_connectors end From 7fe6c6f83d6e15c5d57f3dc337a455880b07d361 Mon Sep 17 00:00:00 2001 From: Yingbo Ma Date: Wed, 12 Jun 2024 16:29:14 -0400 Subject: [PATCH 200/316] Update Project.toml --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index a035dd01df..34ddf71dcb 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.18.0" +version = "9.18.1" [deps] AbstractTrees = "1520ce14-60c1-5f80-bbc7-55ef81b5835c" From 76976af5669b85b4eb53c2eceeded7fcf68a9e35 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Wed, 22 May 2024 11:00:25 +0530 Subject: [PATCH 201/316] feat: support inplace observed --- src/systems/abstractsystem.jl | 11 +++++++++++ src/systems/diffeqs/odesystem.jl | 27 +++++++++++++++++++++------ test/odesystem.jl | 12 ++++++++++++ 3 files changed, 44 insertions(+), 6 deletions(-) diff --git a/src/systems/abstractsystem.jl b/src/systems/abstractsystem.jl index 6b23750133..013705e34b 100644 --- a/src/systems/abstractsystem.jl +++ b/src/systems/abstractsystem.jl @@ -201,6 +201,17 @@ function generate_custom_function(sys::AbstractSystem, exprs, dvs = unknowns(sys end end +function wrap_assignments(isscalar, assignments; let_block = false) + function wrapper(expr) + Func(expr.args, [], Let(assignments, expr.body, let_block)) + end + if isscalar + wrapper + else + wrapper, wrapper + end +end + function wrap_array_vars(sys::AbstractSystem, exprs; dvs = unknowns(sys)) isscalar = !(exprs isa AbstractArray) array_vars = Dict{Any, AbstractArray{Int}}() diff --git a/src/systems/diffeqs/odesystem.jl b/src/systems/diffeqs/odesystem.jl index 9c11f65cfd..612e4eca86 100644 --- a/src/systems/diffeqs/odesystem.jl +++ b/src/systems/diffeqs/odesystem.jl @@ -382,6 +382,7 @@ function build_explicit_observed_function(sys, ts; checkbounds = true, drop_expr = drop_expr, ps = full_parameters(sys), + return_inplace = false, op = Operator, throw = true) if (isscalar = symbolic_type(ts) !== NotSymbolic()) @@ -479,16 +480,30 @@ function build_explicit_observed_function(sys, ts; if inputs === nothing args = [dvs, ps..., ivs...] else - ipts = DestructuredArgs(inputs, inbounds = !checkbounds) + ipts = DestructuredArgs(unwrap.(inputs), inbounds = !checkbounds) args = [dvs, ipts, ps..., ivs...] end pre = get_postprocess_fbody(sys) - ex = Func(args, [], - pre(Let(obsexprs, - isscalar ? ts[1] : MakeArray(ts, output_type), - false))) |> wrap_array_vars(sys, ts)[1] |> toexpr - expression ? ex : drop_expr(@RuntimeGeneratedFunction(ex)) + # Need to keep old method of building the function since it uses `output_type`, + # which can't be provided to `build_function` + oop_fn = Func(args, [], + pre(Let(obsexprs, + isscalar ? ts[1] : MakeArray(ts, output_type), + false))) |> wrap_array_vars(sys, ts)[1] |> toexpr + oop_fn = expression ? oop_fn : drop_expr(@RuntimeGeneratedFunction(oop_fn)) + + iip_fn = build_function(isscalar ? ts[1] : ts, + args...; + postprocess_fbody = pre, + wrap_code = wrap_array_vars( + sys, isscalar ? ts[1] : ts) .∘ wrap_assignments(isscalar, obsexprs), + expression = Val{expression})[2] + if isscalar || return_inplace + return oop_fn, iip_fn + else + return oop_fn + end end function _eq_unordered(a, b) diff --git a/test/odesystem.jl b/test/odesystem.jl index dade28de81..ab28f5cb47 100644 --- a/test/odesystem.jl +++ b/test/odesystem.jl @@ -1182,3 +1182,15 @@ end @test_nowarn ForwardDiff.derivative(P -> x_at_1(P), 1.0) end + +@testset "Inplace observed functions" begin + @parameters P + @variables x(t) + sys = structural_simplify(ODESystem([D(x) ~ P], t, [x], [P]; name = :sys)) + obsfn = ModelingToolkit.build_explicit_observed_function( + sys, [x + 1, x + P, x + t], return_inplace = true)[2] + ps = ModelingToolkit.MTKParameters(sys, [P => 2.0]) + buffer = zeros(3) + @test_nowarn obsfn(buffer, [1.0], ps..., 3.0) + @test buffer ≈ [2.0, 3.0, 4.0] +end From 4e745a60ef24257fc6a8ee69abb56900c2c387ce Mon Sep 17 00:00:00 2001 From: Yingbo Ma Date: Thu, 13 Jun 2024 18:07:29 -0400 Subject: [PATCH 202/316] Fix specialize overdetemined system in tearing --- downstream/Project.toml | 2 + .../bipartite_tearing/modia_tearing.jl | 43 ++++++++----------- src/structural_transformation/tearing.jl | 13 ++++++ test/nonlinearsystem.jl | 8 ++++ 4 files changed, 40 insertions(+), 26 deletions(-) create mode 100644 downstream/Project.toml diff --git a/downstream/Project.toml b/downstream/Project.toml new file mode 100644 index 0000000000..536c1c16cd --- /dev/null +++ b/downstream/Project.toml @@ -0,0 +1,2 @@ +[deps] +ModelingToolkit = "961ee093-0014-501f-94e3-6117800e7a78" diff --git a/src/structural_transformation/bipartite_tearing/modia_tearing.jl b/src/structural_transformation/bipartite_tearing/modia_tearing.jl index 46c0d701c6..cef2f5f6d7 100644 --- a/src/structural_transformation/bipartite_tearing/modia_tearing.jl +++ b/src/structural_transformation/bipartite_tearing/modia_tearing.jl @@ -89,45 +89,36 @@ function tear_graph_modia(structure::SystemStructure, isder::F = nothing, ieqs = Int[] filtered_vars = BitSet() - seen_eqs = falses(nsrcs(graph)) + free_eqs = free_equations(graph, var_sccs, var_eq_matching, varfilter) + is_overdetemined = !isempty(free_eqs) for vars in var_sccs for var in vars if varfilter(var) push!(filtered_vars, var) if var_eq_matching[var] !== unassigned ieq = var_eq_matching[var] - seen_eqs[ieq] = true push!(ieqs, ieq) end end var_eq_matching[var] = unassigned end - tear_block!(vargraph, vars, - var_eq_matching, ict, solvable_graph, - ieqs, filtered_vars, isder) + tear_graph_block_modia!(var_eq_matching, ict, solvable_graph, ieqs, + filtered_vars, isder) + # If the systems is overdetemined, we cannot assume the free equations + # will not form algebraic loops with equations in the sccs. + if !is_overdetemined + vargraph.ne = 0 + for var in vars + vargraph.matching[var] = unassigned + end + end + empty!(ieqs) + empty!(filtered_vars) end - free_eqs = findall(!, seen_eqs) - if !isempty(free_eqs) + if is_overdetemined free_vars = findall(x -> !(x isa Int), var_eq_matching) - tear_block!(vargraph, (), - var_eq_matching, ict, solvable_graph, - free_eqs, BitSet(free_vars), isder) + tear_graph_block_modia!(var_eq_matching, ict, solvable_graph, free_eqs, + BitSet(free_vars), isder) end return var_eq_matching, full_var_eq_matching, var_sccs end - -function tear_block!(vargraph, vars, - var_eq_matching, ict, solvable_graph, ieqs, - filtered_vars, isder) - tear_graph_block_modia!(var_eq_matching, ict, solvable_graph, ieqs, - filtered_vars, - isder) - - # clear cache - vargraph.ne = 0 - for var in vars - vargraph.matching[var] = unassigned - end - empty!(ieqs) - empty!(filtered_vars) -end diff --git a/src/structural_transformation/tearing.jl b/src/structural_transformation/tearing.jl index aa62a449dd..d37eedc853 100644 --- a/src/structural_transformation/tearing.jl +++ b/src/structural_transformation/tearing.jl @@ -68,3 +68,16 @@ function algebraic_variables_scc(state::TearingState) return var_eq_matching, var_sccs end + +function free_equations(graph, vars_scc, var_eq_matching, varfilter::F) where {F} + ne = nsrcs(graph) + seen_eqs = falses(ne) + for vars in vars_scc, var in vars + varfilter(var) || continue + ieq = var_eq_matching[var] + if ieq isa Int + seen_eqs[ieq] = true + end + end + findall(!, seen_eqs) +end diff --git a/test/nonlinearsystem.jl b/test/nonlinearsystem.jl index ea31cff644..cb6bbf76e5 100644 --- a/test/nonlinearsystem.jl +++ b/test/nonlinearsystem.jl @@ -266,3 +266,11 @@ alg_eqs = [0 ~ p - d * X] sys = @test_nowarn NonlinearSystem(alg_eqs; name = :name) @test isequal(only(unknowns(sys)), X) @test all(isequal.(parameters(sys), [p, d])) + +# Over-determined sys +@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]) +sys = structural_simplify(ns; fully_determined = false) +@test length(unknowns(sys)) == 1 From 7cdb1315773753bf399202d28008bcba1c605482 Mon Sep 17 00:00:00 2001 From: Christopher Rackauckas Date: Thu, 13 Jun 2024 21:18:10 -0400 Subject: [PATCH 203/316] Update Project.toml --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 34ddf71dcb..47b098587a 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.18.1" +version = "9.19.0" [deps] AbstractTrees = "1520ce14-60c1-5f80-bbc7-55ef81b5835c" From 8ea472d2795beb6efd24f89aef073d57fc61c3d7 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Thu, 13 Jun 2024 18:40:05 +0530 Subject: [PATCH 204/316] fix: only build iip function when input is nonscalar --- src/systems/diffeqs/odesystem.jl | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/systems/diffeqs/odesystem.jl b/src/systems/diffeqs/odesystem.jl index 612e4eca86..4a7a269ad7 100644 --- a/src/systems/diffeqs/odesystem.jl +++ b/src/systems/diffeqs/odesystem.jl @@ -493,16 +493,17 @@ function build_explicit_observed_function(sys, ts; false))) |> wrap_array_vars(sys, ts)[1] |> toexpr oop_fn = expression ? oop_fn : drop_expr(@RuntimeGeneratedFunction(oop_fn)) - iip_fn = build_function(isscalar ? ts[1] : ts, - args...; - postprocess_fbody = pre, - wrap_code = wrap_array_vars( - sys, isscalar ? ts[1] : ts) .∘ wrap_assignments(isscalar, obsexprs), - expression = Val{expression})[2] - if isscalar || return_inplace - return oop_fn, iip_fn - else + if !isscalar + iip_fn = build_function(ts, + args...; + postprocess_fbody = pre, + wrap_code = wrap_array_vars(sys, ts) .∘ wrap_assignments(isscalar, obsexprs), + expression = Val{expression})[2] + end + if isscalar || !return_inplace return oop_fn + else + return oop_fn, iip_fn end end From 46997c4fba0d408696111c9259ee254b8b9ea4c3 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Wed, 12 Jun 2024 15:18:13 +0530 Subject: [PATCH 205/316] fix: fix observed function generation for systems with inputs --- src/systems/diffeqs/odesystem.jl | 6 +++++- test/input_output_handling.jl | 13 +++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/src/systems/diffeqs/odesystem.jl b/src/systems/diffeqs/odesystem.jl index 4a7a269ad7..619a896810 100644 --- a/src/systems/diffeqs/odesystem.jl +++ b/src/systems/diffeqs/odesystem.jl @@ -473,6 +473,9 @@ function build_explicit_observed_function(sys, ts; ps = DestructuredArgs.(ps, inbounds = !checkbounds) elseif has_index_cache(sys) && get_index_cache(sys) !== nothing ps = DestructuredArgs.(reorder_parameters(get_index_cache(sys), ps)) + if isempty(ps) && inputs !== nothing + ps = (:EMPTY,) + end else ps = (DestructuredArgs(ps, inbounds = !checkbounds),) end @@ -480,7 +483,8 @@ function build_explicit_observed_function(sys, ts; if inputs === nothing args = [dvs, ps..., ivs...] else - ipts = DestructuredArgs(unwrap.(inputs), inbounds = !checkbounds) + inputs = unwrap.(inputs) + ipts = DestructuredArgs(inputs, inbounds = !checkbounds) args = [dvs, ipts, ps..., ivs...] end pre = get_postprocess_fbody(sys) diff --git a/test/input_output_handling.jl b/test/input_output_handling.jl index c63116638b..778e02db0a 100644 --- a/test/input_output_handling.jl +++ b/test/input_output_handling.jl @@ -378,3 +378,16 @@ matrices, ssys = linearize(augmented_sys, # P = ss(A,B,C,0) # G = ss(matrices...) # @test sminreal(G[1, 3]) ≈ sminreal(P[1,1])*dist + +@testset "Observed functions with inputs" begin + @variables x(t)=0 u(t)=0 [input = true] + eqs = [ + D(x) ~ -x + u + ] + + @named sys = ODESystem(eqs, t) + (; io_sys,) = ModelingToolkit.generate_control_function(sys, simplify = true) + obsfn = ModelingToolkit.build_explicit_observed_function( + io_sys, [x + u * t]; inputs = [u]) + @test obsfn([1.0], [2.0], nothing, 3.0) == [7.0] +end From 6534e2b146f5695ac87f6a36a7b945bd378f4c31 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Wed, 12 Jun 2024 16:11:36 +0530 Subject: [PATCH 206/316] refactor: use common observed for `NonlinearSystem` --- src/systems/nonlinear/nonlinearsystem.jl | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/src/systems/nonlinear/nonlinearsystem.jl b/src/systems/nonlinear/nonlinearsystem.jl index dd6243ef00..d1d3f56247 100644 --- a/src/systems/nonlinear/nonlinearsystem.jl +++ b/src/systems/nonlinear/nonlinearsystem.jl @@ -306,18 +306,7 @@ function SciMLBase.NonlinearFunction{iip}(sys::NonlinearSystem, dvs = unknowns(s _jac = nothing end - observedfun = let sys = sys, dict = Dict() - function generated_observed(obsvar, u, p) - obs = get!(dict, value(obsvar)) do - build_explicit_observed_function(sys, obsvar) - end - if p isa MTKParameters - obs(u, p...) - else - obs(u, p) - end - end - end + observedfun = ObservedFunctionCache(sys) NonlinearFunction{iip}(f, sys = sys, From 80f2b198d345575474e7667784a42c324c8ed77a Mon Sep 17 00:00:00 2001 From: Yingbo Ma Date: Fri, 14 Jun 2024 18:57:31 -0400 Subject: [PATCH 207/316] Add `conservative` kwarg in `structural_transformation` `conservative=true` limits tearing to only solve for trivial linear systems where the coefficient has the absolute value of 1. This is useful for debugging numerical stability issues after tearing. By default, we set `conservative=false`. --- src/structural_transformation/pantelides.jl | 5 +++-- src/structural_transformation/partial_state_selection.jl | 2 +- src/structural_transformation/symbolics_tearing.jl | 5 +++-- src/structural_transformation/utils.jl | 5 ++++- src/systems/systems.jl | 6 ++++-- src/systems/systemstructure.jl | 9 ++++++--- 6 files changed, 21 insertions(+), 11 deletions(-) diff --git a/src/structural_transformation/pantelides.jl b/src/structural_transformation/pantelides.jl index 2d80e28f40..7cbd934ff8 100644 --- a/src/structural_transformation/pantelides.jl +++ b/src/structural_transformation/pantelides.jl @@ -123,7 +123,8 @@ end Perform Pantelides algorithm. """ -function pantelides!(state::TransformationState; finalize = true, maxiters = 8000) +function pantelides!( + state::TransformationState; finalize = true, maxiters = 8000, kwargs...) @unpack graph, solvable_graph, var_to_diff, eq_to_diff = state.structure neqs = nsrcs(graph) nvars = nv(var_to_diff) @@ -181,7 +182,7 @@ function pantelides!(state::TransformationState; finalize = true, maxiters = 800 ecolor[eq] || continue # introduce a new equation neqs += 1 - eq_derivative!(state, eq) + eq_derivative!(state, eq; kwargs...) end for var in eachindex(vcolor) diff --git a/src/structural_transformation/partial_state_selection.jl b/src/structural_transformation/partial_state_selection.jl index f47b6a973e..53dfc669e0 100644 --- a/src/structural_transformation/partial_state_selection.jl +++ b/src/structural_transformation/partial_state_selection.jl @@ -173,7 +173,7 @@ function dummy_derivative_graph!(state::TransformationState, jac = nothing; state_priority = nothing, log = Val(false), kwargs...) state.structure.solvable_graph === nothing && find_solvables!(state; kwargs...) complete!(state.structure) - var_eq_matching = complete(pantelides!(state)) + var_eq_matching = complete(pantelides!(state; kwargs...)) dummy_derivative_graph!(state.structure, var_eq_matching, jac, state_priority, log) end diff --git a/src/structural_transformation/symbolics_tearing.jl b/src/structural_transformation/symbolics_tearing.jl index 1b46ba2e80..6f6e89d86d 100644 --- a/src/structural_transformation/symbolics_tearing.jl +++ b/src/structural_transformation/symbolics_tearing.jl @@ -56,7 +56,7 @@ function eq_derivative_graph!(s::SystemStructure, eq::Int) return eq_diff end -function eq_derivative!(ts::TearingState{ODESystem}, ieq::Int) +function eq_derivative!(ts::TearingState{ODESystem}, ieq::Int; kwargs...) s = ts.structure eq_diff = eq_derivative_graph!(s, ieq) @@ -75,7 +75,8 @@ function eq_derivative!(ts::TearingState{ODESystem}, ieq::Int) add_edge!(s.graph, eq_diff, s.var_to_diff[var]) end s.solvable_graph === nothing || - find_eq_solvables!(ts, eq_diff; may_be_zero = true, allow_symbolic = false) + find_eq_solvables!( + ts, eq_diff; may_be_zero = true, allow_symbolic = false, kwargs...) return eq_diff end diff --git a/src/structural_transformation/utils.jl b/src/structural_transformation/utils.jl index cb46599178..7f5039f872 100644 --- a/src/structural_transformation/utils.jl +++ b/src/structural_transformation/utils.jl @@ -181,7 +181,9 @@ end function find_eq_solvables!(state::TearingState, ieq, to_rm = Int[], coeffs = nothing; may_be_zero = false, - allow_symbolic = false, allow_parameter = true, kwargs...) + allow_symbolic = false, allow_parameter = true, + conservative = false, + kwargs...) fullvars = state.fullvars @unpack graph, solvable_graph = state.structure eq = equations(state)[ieq] @@ -220,6 +222,7 @@ function find_eq_solvables!(state::TearingState, ieq, to_rm = Int[], coeffs = no coeffs === nothing || push!(coeffs, convert(Int, a)) else all_int_vars = false + conservative && continue end if a != 0 add_edge!(solvable_graph, ieq, j) diff --git a/src/systems/systems.jl b/src/systems/systems.jl index 124ffdfa8b..15dd9d3077 100644 --- a/src/systems/systems.jl +++ b/src/systems/systems.jl @@ -9,8 +9,10 @@ $(SIGNATURES) Structurally simplify algebraic equations in a system and compute the topological sort of the observed equations. When `simplify=true`, the `simplify` function will be applied during the tearing process. It also takes kwargs -`allow_symbolic=false` and `allow_parameter=true` which limits the coefficient -types during tearing. +`allow_symbolic=false`, `allow_parameter=true`, and `conservative=false` which +limits 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``. The optional argument `io` may take a tuple `(inputs, outputs)`. This will convert all `inputs` to parameters and allow them to be unconnected, i.e., diff --git a/src/systems/systemstructure.jl b/src/systems/systemstructure.jl index befc0ac0bd..ff26552c79 100644 --- a/src/systems/systemstructure.jl +++ b/src/systems/systemstructure.jl @@ -691,15 +691,18 @@ function _structural_simplify!(state::TearingState, io; simplify = false, ModelingToolkit.check_consistency(state, orig_inputs) end if fully_determined && dummy_derivative - sys = ModelingToolkit.dummy_derivative(sys, state; simplify, mm, check_consistency) + sys = ModelingToolkit.dummy_derivative( + sys, state; simplify, mm, check_consistency, kwargs...) elseif fully_determined var_eq_matching = pantelides!(state; finalize = false, kwargs...) sys = pantelides_reassemble(state, var_eq_matching) state = TearingState(sys) sys, mm = ModelingToolkit.alias_elimination!(state; kwargs...) - sys = ModelingToolkit.dummy_derivative(sys, state; simplify, mm, check_consistency) + sys = ModelingToolkit.dummy_derivative( + sys, state; simplify, mm, check_consistency, kwargs...) else - sys = ModelingToolkit.tearing(sys, state; simplify, mm, check_consistency) + sys = ModelingToolkit.tearing( + sys, state; simplify, mm, check_consistency, kwargs...) end fullunknowns = [map(eq -> eq.lhs, observed(sys)); unknowns(sys)] @set! sys.observed = ModelingToolkit.topsort_equations(observed(sys), fullunknowns) From 898e592111734e63b9ff5882aad8bbf6a6d73b50 Mon Sep 17 00:00:00 2001 From: Yingbo Ma Date: Fri, 14 Jun 2024 19:05:42 -0400 Subject: [PATCH 208/316] Add test --- test/nonlinearsystem.jl | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/test/nonlinearsystem.jl b/test/nonlinearsystem.jl index cb6bbf76e5..75c94a6d60 100644 --- a/test/nonlinearsystem.jl +++ b/test/nonlinearsystem.jl @@ -274,3 +274,12 @@ eqs = [u3 ~ u1 + u2, u4 ~ 2 * (u1 + u2), u3 + u4 ~ 3 * (u1 + u2)] @named ns = NonlinearSystem(eqs, [u1, u2], [u3, u4]) sys = structural_simplify(ns; fully_determined = false) @test length(unknowns(sys)) == 1 + +# Conservative +@variables X(t) +alg_eqs = [1 ~ 2X] +@named ns = NonlinearSystem(alg_eqs) +sys = structural_simplify(ns) +@test length(equations(sys)) == 0 +sys = structural_simplify(ns; conservative = true) +@test length(equations(sys)) == 1 From 46866522f9e3f496679218ae1acf65df2cea935e Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Mon, 17 Jun 2024 11:23:10 +0530 Subject: [PATCH 209/316] fix: error properly on missing parameter values --- src/systems/parameter_buffer.jl | 2 +- test/mtkparameters.jl | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/systems/parameter_buffer.jl b/src/systems/parameter_buffer.jl index 86891bab52..d1f3aea02a 100644 --- a/src/systems/parameter_buffer.jl +++ b/src/systems/parameter_buffer.jl @@ -139,7 +139,7 @@ function MTKParameters( val = unwrap(val) ctype = symtype(sym) if symbolic_type(val) !== NotSymbolic() - continue + error("Could not evaluate value of parameter $sym. Missing values for variables in expression $val.") end val = symconvert(ctype, val) done = set_value(sym, val) diff --git a/test/mtkparameters.jl b/test/mtkparameters.jl index aac71dd43e..d5e16bb071 100644 --- a/test/mtkparameters.jl +++ b/test/mtkparameters.jl @@ -283,3 +283,10 @@ VVDual = Vector{<:Vector{<:ForwardDiff.Dual}} @test_throws TypeError remake_buffer(sys, ps, Dict(e => Foo(2.0))) # need exact same type for nonnumeric @test_nowarn remake_buffer(sys, ps, Dict(f => Foo(:a))) end + +@testset "Error on missing parameter defaults" begin + @parameters a b c + @named sys = ODESystem(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 From 730a423332f0d9d0700720c1aaefc91ce3c1de7b Mon Sep 17 00:00:00 2001 From: "Bowen S. Zhu" Date: Mon, 24 Jun 2024 22:18:54 -0700 Subject: [PATCH 210/316] Replace deprecated `unsorted_arguments` with `arguments` --- src/systems/connectors.jl | 2 +- src/utils.jl | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/systems/connectors.jl b/src/systems/connectors.jl index ba40a57d38..ace057adfe 100644 --- a/src/systems/connectors.jl +++ b/src/systems/connectors.jl @@ -85,7 +85,7 @@ function collect_instream!(set, expr, occurs = false) iscall(expr) || return occurs op = operation(expr) op === instream && (push!(set, expr); occurs = true) - for a in SymbolicUtils.unsorted_arguments(expr) + for a in SymbolicUtils.arguments(expr) occurs |= collect_instream!(set, a, occurs) end return occurs diff --git a/src/utils.jl b/src/utils.jl index 53f9130ea1..58eacb38c5 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -281,7 +281,7 @@ function _check_operator_variables(eq, op::T, expr = eq.rhs) where {T} throw_invalid_operator(expr, eq, op) end foreach(expr -> _check_operator_variables(eq, op, expr), - SymbolicUtils.unsorted_arguments(expr)) + SymbolicUtils.arguments(expr)) end """ Check if all the LHS are unique From 560b5ffa6a2bd976cbd53cd1147ca30ae5ebf135 Mon Sep 17 00:00:00 2001 From: Christopher Rackauckas Date: Tue, 25 Jun 2024 17:21:36 +0800 Subject: [PATCH 211/316] Update Project.toml --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 47b098587a..c176879c73 100644 --- a/Project.toml +++ b/Project.toml @@ -108,7 +108,7 @@ SparseArrays = "1" SpecialFunctions = "0.7, 0.8, 0.9, 0.10, 1.0, 2" StaticArrays = "0.10, 0.11, 0.12, 1.0" SymbolicIndexingInterface = "0.3.12" -SymbolicUtils = "2" +SymbolicUtils = "2.1" Symbolics = "5.30.1" URIs = "1" UnPack = "0.1, 1.0" From 59cd46bc1ceb717013bd398b2ec266ec230b9829 Mon Sep 17 00:00:00 2001 From: Christopher Rackauckas Date: Thu, 27 Jun 2024 05:16:43 +0800 Subject: [PATCH 212/316] Update Project.toml --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index c176879c73..4477ac387d 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.19.0" +version = "9.20.0" [deps] AbstractTrees = "1520ce14-60c1-5f80-bbc7-55ef81b5835c" From 7e901bf4c905aab1c5b184cd4197dd03e239f3d3 Mon Sep 17 00:00:00 2001 From: Christopher Rackauckas Date: Thu, 27 Jun 2024 09:33:24 +0800 Subject: [PATCH 213/316] Add nameof dispatches on operators This previously relied on `Operator <: Function` and `Function` just happens to have `nameof` defined. --- Project.toml | 2 +- src/discretedomain.jl | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 4477ac387d..ad35a28521 100644 --- a/Project.toml +++ b/Project.toml @@ -109,7 +109,7 @@ SpecialFunctions = "0.7, 0.8, 0.9, 0.10, 1.0, 2" StaticArrays = "0.10, 0.11, 0.12, 1.0" SymbolicIndexingInterface = "0.3.12" SymbolicUtils = "2.1" -Symbolics = "5.30.1" +Symbolics = "5.32" URIs = "1" UnPack = "0.1, 1.0" Unitful = "1.1" diff --git a/src/discretedomain.jl b/src/discretedomain.jl index 401b8e6f46..bb09bca71e 100644 --- a/src/discretedomain.jl +++ b/src/discretedomain.jl @@ -4,6 +4,7 @@ struct SampleTime <: Operator SampleTime() = SymbolicUtils.term(SampleTime, type = Real) end SymbolicUtils.promote_symtype(::Type{<:SampleTime}, t...) = Real +Base.nameof(::SampleTime) = :SampleTime # Shift @@ -32,6 +33,8 @@ struct Shift <: Operator end Shift(steps::Int) = new(nothing, steps) normalize_to_differential(s::Shift) = Differential(s.t)^s.steps +Base.nameof(::Shift) = :Shift + function (D::Shift)(x, allow_zero = false) !allow_zero && D.steps == 0 && return x Term{symtype(x)}(D, Any[x]) @@ -108,6 +111,7 @@ Sample(x) = Sample()(x) (D::Sample)(x) = Term{symtype(x)}(D, Any[x]) (D::Sample)(x::Num) = Num(D(value(x))) SymbolicUtils.promote_symtype(::Sample, x) = x +Base.nameof(::Sample) = :Sample Base.show(io::IO, D::Sample) = print(io, "Sample(", D.clock, ")") @@ -137,6 +141,7 @@ end (D::Hold)(x) = Term{symtype(x)}(D, Any[x]) (D::Hold)(x::Num) = Num(D(value(x))) SymbolicUtils.promote_symtype(::Hold, x) = x +Base.nameof(::Hold) = :Hold Hold(x) = Hold()(x) From 78a18c40e13f717922a588663cf5c8a0a194d5f9 Mon Sep 17 00:00:00 2001 From: Christopher Rackauckas Date: Thu, 27 Jun 2024 09:36:14 +0800 Subject: [PATCH 214/316] Add isbinop dispatches --- src/discretedomain.jl | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/discretedomain.jl b/src/discretedomain.jl index bb09bca71e..34f628a8b3 100644 --- a/src/discretedomain.jl +++ b/src/discretedomain.jl @@ -5,6 +5,7 @@ struct SampleTime <: Operator end SymbolicUtils.promote_symtype(::Type{<:SampleTime}, t...) = Real Base.nameof(::SampleTime) = :SampleTime +SymbolicUtils.isbinop(::SampleTime) = false # Shift @@ -34,6 +35,7 @@ end Shift(steps::Int) = new(nothing, steps) normalize_to_differential(s::Shift) = Differential(s.t)^s.steps Base.nameof(::Shift) = :Shift +SymbolicUtils.isbinop(::Shift) = false function (D::Shift)(x, allow_zero = false) !allow_zero && D.steps == 0 && return x @@ -112,6 +114,7 @@ Sample(x) = Sample()(x) (D::Sample)(x::Num) = Num(D(value(x))) SymbolicUtils.promote_symtype(::Sample, x) = x Base.nameof(::Sample) = :Sample +SymbolicUtils.isbinop(::Sample) = false Base.show(io::IO, D::Sample) = print(io, "Sample(", D.clock, ")") @@ -142,6 +145,7 @@ end (D::Hold)(x::Num) = Num(D(value(x))) SymbolicUtils.promote_symtype(::Hold, x) = x Base.nameof(::Hold) = :Hold +SymbolicUtils.isbinop(::Hold) = false Hold(x) = Hold()(x) From 898307a9115793fbe9c1ff6be223b6a942e4e0c7 Mon Sep 17 00:00:00 2001 From: Christopher Rackauckas Date: Thu, 27 Jun 2024 22:32:03 +0800 Subject: [PATCH 215/316] Update initializationsystem.jl --- test/initializationsystem.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/initializationsystem.jl b/test/initializationsystem.jl index 6e3af256be..5be31da76c 100644 --- a/test/initializationsystem.jl +++ b/test/initializationsystem.jl @@ -22,7 +22,7 @@ initprob = ModelingToolkit.InitializationProblem(pend, 0.0, [x => 1, y => 0], [g @test initprob isa NonlinearProblem sol = solve(initprob) @test SciMLBase.successful_retcode(sol) -@test sol.u == [0.0, 0.0, 0.0] +@test sol.u == [0.0, 0.0, 0.0, 0.0] @test maximum(abs.(sol[conditions])) < 1e-14 initprob = ModelingToolkit.InitializationProblem( From 049dd9e1c1f542d514b54ecda8696b0b18a2f5b4 Mon Sep 17 00:00:00 2001 From: Yingbo Ma Date: Thu, 27 Jun 2024 15:02:43 -0400 Subject: [PATCH 216/316] Accelerate connection sets merging with union-find Baseline: ```julia julia> @time run_and_time_julia!(ss_times, times, max_sizes, 1, 100) 0.960393 seconds (8.15 M allocations: 540.022 MiB, 4.47% gc time) 0.888558584 julia> @time run_and_time_julia!(ss_times, times, max_sizes, 1, 200) 2.593054 seconds (17.95 M allocations: 1.131 GiB, 3.75% gc time) 2.465012458 julia> @time run_and_time_julia!(ss_times, times, max_sizes, 1, 300) 5.065673 seconds (29.41 M allocations: 1.821 GiB, 5.90% gc time) 4.861177375 ``` PR: ```julia julia> @time run_and_time_julia!(ss_times, times, max_sizes, 1, 100); 0.748587 seconds (7.61 M allocations: 513.135 MiB, 7.15% gc time) julia> @time run_and_time_julia!(ss_times, times, max_sizes, 1, 200); 1.681521 seconds (15.75 M allocations: 1.027 GiB, 7.71% gc time) julia> @time run_and_time_julia!(ss_times, times, max_sizes, 1, 300); 2.931254 seconds (24.43 M allocations: 1.590 GiB, 11.97% gc time) ``` --- src/systems/connectors.jl | 68 ++++++++++++++++----------------------- 1 file changed, 28 insertions(+), 40 deletions(-) diff --git a/src/systems/connectors.jl b/src/systems/connectors.jl index ace057adfe..83e70f8009 100644 --- a/src/systems/connectors.jl +++ b/src/systems/connectors.jl @@ -187,6 +187,7 @@ end struct ConnectionSet set::Vector{ConnectionElement} # namespace.sys, var, isouter end +ConnectionSet() = ConnectionSet(ConnectionElement[]) Base.copy(c::ConnectionSet) = ConnectionSet(copy(c.set)) function Base.show(io::IO, c::ConnectionSet) @@ -373,51 +374,38 @@ function generate_connection_set!(connectionsets, domain_csets, end function Base.merge(csets::AbstractVector{<:ConnectionSet}, allouter = false) - csets, merged = partial_merge(csets, allouter) - while merged - csets, merged = partial_merge(csets) - end - csets -end - -function partial_merge(csets::AbstractVector{<:ConnectionSet}, allouter = false) - mcsets = ConnectionSet[] ele2idx = Dict{ConnectionElement, Int}() - cacheset = Set{ConnectionElement}() - merged = false - for (j, cset) in enumerate(csets) - if allouter - cset = ConnectionSet(map(withtrueouter, cset.set)) - end - idx = nothing - for e in cset.set - idx = get(ele2idx, e, nothing) - if idx !== nothing - merged = true - break + idx2ele = ConnectionElement[] + union_find = IntDisjointSets(0) + prev_id = Ref(-1) + for cset in csets, (j, s) in enumerate(cset.set) + v = allouter ? withtrueouter(s) : s + id = let ele2idx = ele2idx, idx2ele = idx2ele + get!(ele2idx, v) do + push!(idx2ele, v) + id = length(idx2ele) + id′ = push!(union_find) + @assert id == id′ + id end end - if idx === nothing - push!(mcsets, copy(cset)) - for e in cset.set - ele2idx[e] = length(mcsets) - end - else - for e in mcsets[idx].set - push!(cacheset, e) - end - for e in cset.set - push!(cacheset, e) - end - empty!(mcsets[idx].set) - for e in cacheset - ele2idx[e] = idx - push!(mcsets[idx].set, e) - end - empty!(cacheset) + if j > 1 + union!(union_find, prev_id[], id) + end + prev_id[] = id + end + id2set = Dict{Int, ConnectionSet}() + merged_set = ConnectionSet[] + for (id, ele) in enumerate(idx2ele) + rid = find_root(union_find, id) + set = get!(id2set, rid) do + set = ConnectionSet() + push!(merged_set, set) + set end + push!(set.set, ele) end - mcsets, merged + merged_set end function generate_connection_equations_and_stream_connections(csets::AbstractVector{ From 53eab1e5ac45d8a0ab418c16ceffc26cd5eafac0 Mon Sep 17 00:00:00 2001 From: Yingbo Ma Date: Thu, 27 Jun 2024 16:48:35 -0400 Subject: [PATCH 217/316] Fix domain connectors --- src/systems/connectors.jl | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/src/systems/connectors.jl b/src/systems/connectors.jl index 83e70f8009..c62371928c 100644 --- a/src/systems/connectors.jl +++ b/src/systems/connectors.jl @@ -163,11 +163,16 @@ end Base.nameof(l::ConnectionElement) = renamespace(nameof(l.sys), getname(l.v)) Base.isequal(l1::ConnectionElement, l2::ConnectionElement) = l1 == l2 function Base.:(==)(l1::ConnectionElement, l2::ConnectionElement) - nameof(l1.sys) == nameof(l2.sys) && isequal(l1.v, l2.v) && l1.isouter == l2.isouter + l1.isouter == l2.isouter && nameof(l1.sys) == nameof(l2.sys) && isequal(l1.v, l2.v) end const _debug_mode = Base.JLOptions().check_bounds == 1 +function Base.show(io::IO, c::ConnectionElement) + @unpack sys, v, isouter = c + print(io, nameof(sys), ".", v, "::", isouter ? "outer" : "inner") +end + function Base.hash(e::ConnectionElement, salt::UInt) if _debug_mode @assert e.h === _hash_impl(e.sys, e.v, e.isouter) @@ -189,6 +194,8 @@ struct ConnectionSet end ConnectionSet() = ConnectionSet(ConnectionElement[]) Base.copy(c::ConnectionSet) = ConnectionSet(copy(c.set)) +Base.:(==)(a::ConnectionSet, b::ConnectionSet) = a.set == b.set +Base.sort(a::ConnectionSet) = ConnectionSet(sort(a.set, by = string)) function Base.show(io::IO, c::ConnectionSet) print(io, "<") @@ -389,21 +396,25 @@ function Base.merge(csets::AbstractVector{<:ConnectionSet}, allouter = false) id end end + # isequal might not be equal? lol + if v.sys.namespace !== nothing + idx2ele[id] = v + end if j > 1 union!(union_find, prev_id[], id) end prev_id[] = id end - id2set = Dict{Int, ConnectionSet}() + id2set = Dict{Int, Int}() merged_set = ConnectionSet[] for (id, ele) in enumerate(idx2ele) rid = find_root(union_find, id) - set = get!(id2set, rid) do + set_idx = get!(id2set, rid) do set = ConnectionSet() push!(merged_set, set) - set + length(merged_set) end - push!(set.set, ele) + push!(merged_set[set_idx].set, ele) end merged_set end From 39b15133b0ecb7595a2de7ace3d65e6e33bb8b13 Mon Sep 17 00:00:00 2001 From: Christopher Rackauckas Date: Fri, 28 Jun 2024 05:56:07 +0800 Subject: [PATCH 218/316] Make eval great again Okay, this one needs a little bit of explanation. So in the stone ages we used `eval`. https://github.com/SciML/ModelingToolkit.jl/blame/f9a837c775e15495921947f4995cf7ab97b5a45a/src/systems/diffeqs/abstractodesystem.jl#L122 All was good. But then that made world-age issues. All was bad. But then GeneralizedGenerated came, which was then replaced by RuntimeGeneratedFunctions, which replaced our use of eval. And all was good. https://github.com/SciML/ModelingToolkit.jl/blame/8c6b8249aed97f18a6de471cb5544689befd58e4/src/systems/diffeqs/abstractodesystem.jl#L131 This had no issues with world-age, so you can now use everything more smoothly. However, RGFs didn't play nicely with precompilation. That was fixed by caching things in the dictionary of the module, and adding `eval_module` which was a required argument for this context to allow the user to properly share where the functions would be evaluated into. All was good again. But then Julia v1.9 came around with package images. It turns out that package images attempt to cache the binaries, yay! But the data and information for an RGF is not in the binary. Oh no. https://github.com/SciML/DiffEqProblemLibrary.jl/commit/3444ffecae14a5d27325ea8532fef9795cd0475e ``` Remove precompilation for MTK support Can revert later when this is fixed. ``` This was noticed in DiffEqProblemLibrary, but I subsequently forgot as all of the other v1.9 release chaos happened. So now we are trying to precompile models and failing. What did we do wrong? Well... there's a hard fix... and there's an easy fix. And the easy fix is nice and robust and has guarantees to work by Julia's compiler team itself. Awesome, so let's do that. That fix is... use eval. So at first early in the package, we added the argument `eval_expression` for whether to take the MTK generated function expression and eval it. Eval was then replaced with GG and then RGFs. However, getting the expression was then replaced with ODEFunctionExpr, ODEProblemExpr, etc. Not only that, but the expression themselves were no longer directly returned but put inside of another function, and the reason for that other function is due to adding dispatches for handling splits etc. https://github.com/SciML/ModelingToolkit.jl/blob/v9.20.0/src/systems/diffeqs/abstractodesystem.jl#L333-L337 ```julia f_oop, f_iip = eval_expression ? (drop_expr(@RuntimeGeneratedFunction(eval_module, ex)) for ex in f_gen) : f_gen f(u, p, t) = f_oop(u, p, t) f(du, u, p, t) = f_iip(du, u, p, t) f(u, p::Tuple{Vararg{Number}}, t) = f_oop(u, p, t) f(du, u, p::Tuple{Vararg{Number}}, t) = f_iip(du, u, p, t) f(u, p::Tuple, t) = f_oop(u, p..., t) f(du, u, p::Tuple, t) = f_iip(du, u, p..., t) f(u, p::MTKParameters, t) = f_oop(u, p..., t) f(du, u, p::MTKParameters, t) = f_iip(du, u, p..., t) ``` But now obviously, this reads like nonsense then. `eval_expression=false` returns a Julia expression, which is then put inside of a Julia function, and if you actually tried to call it you get an error that Expr isn't callable. All the meanwhile, `eval_expression=true` doesn't actually `eval` the expression, because remember it used to eval it but that was replaced with RGFs. So we have a `eval_expression` keyword argument that is undocumented and also pretty much nonsense, and I want to add a feature where instead of using RGFs I want to eval the function... :thinking_face: So I re-re-cooped this keyword argument so it now means what it used to mean before it meant what it, i.e. `eval_expression=true` means we `eval` the expression. This means that `eval_expression=false` means we do the RGF thing, and that should be the default as that makes world-age work. But, we also have `eval_module` already, so we just eval into whatever module the user gives. If the user evals into their current module, then the functions in the generated code is exactly a standard Julia function, and it all works with package images. So... what about the tests. Well we had tests on this, but that's as interesting of a story. If we flip the default, then the tests only test the RGF stuff, which they are then setup to do... actually correctly, in a sense. The no-eval was simply testing the parent module ```julia @test parentmodule(typeof(ODEPrecompileTest.f_noeval_good.f.f_oop).parameters[2]) == ODEPrecompileTest ``` and indeed the parent module is in the right module, it's just an Expr there and not really what we were looking for? So in a bizarre sense the tests actually passed for the Exprs that couldn't actually do anything. So I just added tests for the eval path to the precompile test. Now it turns out the "precompile test" is actually just a test that we can put things in a module and it can work. It does not test the package image building, which is why the RGFs did not fail there. I am unsure how to do this on CI. But, I think the tests are likely good enough for now, and this gives a good solution to folks wanting to precompile. We should make sure it actually gets documented this time around. --- src/systems/clock_inference.jl | 8 ++-- src/systems/diffeqs/abstractodesystem.jl | 44 +++++++++---------- src/systems/diffeqs/sdesystem.jl | 41 +++++++++-------- .../discrete_system/discrete_system.jl | 13 +++--- src/systems/nonlinear/nonlinearsystem.jl | 17 ++++--- test/precompile_test.jl | 8 ++++ test/precompile_test/ODEPrecompileTest.jl | 8 ++++ 7 files changed, 75 insertions(+), 64 deletions(-) diff --git a/src/systems/clock_inference.jl b/src/systems/clock_inference.jl index d7a47f1f1f..ae4f0c44f2 100644 --- a/src/systems/clock_inference.jl +++ b/src/systems/clock_inference.jl @@ -192,7 +192,7 @@ end function generate_discrete_affect( osys::AbstractODESystem, syss, inputs, continuous_id, id_to_clock; checkbounds = true, - eval_module = @__MODULE__, eval_expression = true) + eval_module = @__MODULE__, eval_expression = false) @static if VERSION < v"1.7" error("The `generate_discrete_affect` function requires at least Julia 1.7") end @@ -412,15 +412,15 @@ function generate_discrete_affect( push!(svs, sv) end if eval_expression + affects = map(a -> eval_module.eval(toexpr(LiteralExpr(a))), affect_funs) + inits = map(a -> eval_module.eval(toexpr(LiteralExpr(a))), init_funs) + else affects = map(affect_funs) do a drop_expr(@RuntimeGeneratedFunction(eval_module, toexpr(LiteralExpr(a)))) end inits = map(init_funs) do a drop_expr(@RuntimeGeneratedFunction(eval_module, toexpr(LiteralExpr(a)))) end - else - affects = map(a -> toexpr(LiteralExpr(a)), affect_funs) - inits = map(a -> toexpr(LiteralExpr(a)), init_funs) end defaults = Dict{Any, Any}(v => 0.0 for v in Iterators.flatten(inputs)) return affects, inits, clocks, svs, appended_parameters, defaults diff --git a/src/systems/diffeqs/abstractodesystem.jl b/src/systems/diffeqs/abstractodesystem.jl index 292052ec3e..e8b714ea85 100644 --- a/src/systems/diffeqs/abstractodesystem.jl +++ b/src/systems/diffeqs/abstractodesystem.jl @@ -313,7 +313,7 @@ function DiffEqBase.ODEFunction{iip, specialize}(sys::AbstractODESystem, version = nothing, tgrad = false, jac = false, p = nothing, t = nothing, - eval_expression = true, + eval_expression = false, sparse = false, simplify = false, eval_module = @__MODULE__, steady_state = false, @@ -327,12 +327,12 @@ function DiffEqBase.ODEFunction{iip, specialize}(sys::AbstractODESystem, 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{eval_expression}, + f_gen = generate_function(sys, dvs, ps; expression = Val{!eval_expression}, expression_module = eval_module, checkbounds = checkbounds, kwargs...) - f_oop, f_iip = eval_expression ? - (drop_expr(@RuntimeGeneratedFunction(eval_module, ex)) for ex in f_gen) : - f_gen + f_oop, f_iip = eval_expression ? eval_module.eval.(f_gen) : + (drop_expr(@RuntimeGeneratedFunction(eval_module, ex)) for ex in f_gen) + f(u, p, t) = f_oop(u, p, t) f(du, u, p, t) = f_iip(du, u, p, t) f(u, p::Tuple{Vararg{Number}}, t) = f_oop(u, p, t) @@ -352,12 +352,11 @@ function DiffEqBase.ODEFunction{iip, specialize}(sys::AbstractODESystem, if tgrad tgrad_gen = generate_tgrad(sys, dvs, ps; simplify = simplify, - expression = Val{eval_expression}, + expression = Val{!eval_expression}, expression_module = eval_module, checkbounds = checkbounds, kwargs...) - tgrad_oop, tgrad_iip = eval_expression ? - (drop_expr(@RuntimeGeneratedFunction(eval_module, ex)) for ex in tgrad_gen) : - tgrad_gen + tgrad_oop, tgrad_iip = eval_expression ? eval_module.eval.(tgrad_gen) : + (drop_expr(@RuntimeGeneratedFunction(eval_module, ex)) for ex in tgrad_gen) if p isa Tuple __tgrad(u, p, t) = tgrad_oop(u, p..., t) __tgrad(J, u, p, t) = tgrad_iip(J, u, p..., t) @@ -374,12 +373,12 @@ function DiffEqBase.ODEFunction{iip, specialize}(sys::AbstractODESystem, if jac jac_gen = generate_jacobian(sys, dvs, ps; simplify = simplify, sparse = sparse, - expression = Val{eval_expression}, + expression = Val{!eval_expression}, expression_module = eval_module, checkbounds = checkbounds, kwargs...) - jac_oop, jac_iip = eval_expression ? - (drop_expr(@RuntimeGeneratedFunction(eval_module, ex)) for ex in jac_gen) : - jac_gen + jac_oop, jac_iip = eval_expression ? eval_module.eval.(jac_gen) : + (drop_expr(@RuntimeGeneratedFunction(eval_module, ex)) for ex in jac_gen) + _jac(u, p, t) = jac_oop(u, p, t) _jac(J, u, p, t) = jac_iip(J, u, p, t) _jac(u, p::Tuple{Vararg{Number}}, t) = jac_oop(u, p, t) @@ -474,7 +473,7 @@ function DiffEqBase.DAEFunction{iip}(sys::AbstractODESystem, dvs = unknowns(sys) ddvs = map(diff2term ∘ Differential(get_iv(sys)), dvs), version = nothing, p = nothing, jac = false, - eval_expression = true, + eval_expression = false, sparse = false, simplify = false, eval_module = @__MODULE__, checkbounds = false, @@ -485,12 +484,11 @@ function DiffEqBase.DAEFunction{iip}(sys::AbstractODESystem, dvs = unknowns(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{eval_expression}, + expression = Val{!eval_expression}, expression_module = eval_module, checkbounds = checkbounds, kwargs...) - f_oop, f_iip = eval_expression ? - (drop_expr(@RuntimeGeneratedFunction(eval_module, ex)) for ex in f_gen) : - f_gen + f_oop, f_iip = eval_expression ? eval_module.eval.(f_gen) : + (drop_expr(@RuntimeGeneratedFunction(eval_module, ex)) for ex in f_gen) f(du, u, p, t) = f_oop(du, u, p, t) f(du, u, p::MTKParameters, t) = f_oop(du, u, p..., t) f(out, du, u, p, t) = f_iip(out, du, u, p, t) @@ -499,12 +497,12 @@ function DiffEqBase.DAEFunction{iip}(sys::AbstractODESystem, dvs = unknowns(sys) if jac jac_gen = generate_dae_jacobian(sys, dvs, ps; simplify = simplify, sparse = sparse, - expression = Val{eval_expression}, + expression = Val{!eval_expression}, expression_module = eval_module, checkbounds = checkbounds, kwargs...) - jac_oop, jac_iip = eval_expression ? - (drop_expr(@RuntimeGeneratedFunction(eval_module, ex)) for ex in jac_gen) : - jac_gen + jac_oop, jac_iip = eval_expression ? eval_module.eval.(jac_gen) : + (drop_expr(@RuntimeGeneratedFunction(eval_module, ex)) for ex in jac_gen) + _jac(du, u, p, ˍ₋gamma, t) = jac_oop(du, u, p, ˍ₋gamma, t) _jac(du, u, p::MTKParameters, ˍ₋gamma, t) = jac_oop(du, u, p..., ˍ₋gamma, t) @@ -770,7 +768,7 @@ function process_DEProblem(constructor, sys::AbstractODESystem, u0map, parammap; checkbounds = false, sparse = false, simplify = false, linenumbers = true, parallel = SerialForm(), - eval_expression = true, + eval_expression = false, use_union = true, tofloat = true, symbolic_u0 = false, diff --git a/src/systems/diffeqs/sdesystem.jl b/src/systems/diffeqs/sdesystem.jl index 223f1b3c0a..1ddaa7b2d6 100644 --- a/src/systems/diffeqs/sdesystem.jl +++ b/src/systems/diffeqs/sdesystem.jl @@ -407,7 +407,7 @@ function DiffEqBase.SDEFunction{iip}(sys::SDESystem, dvs = unknowns(sys), ps = parameters(sys), u0 = nothing; version = nothing, tgrad = false, sparse = false, - jac = false, Wfact = false, eval_expression = true, + jac = false, Wfact = false, eval_expression = false, checkbounds = false, kwargs...) where {iip} if !iscomplete(sys) @@ -415,13 +415,13 @@ function DiffEqBase.SDEFunction{iip}(sys::SDESystem, dvs = unknowns(sys), end dvs = scalarize.(dvs) - f_gen = generate_function(sys, dvs, ps; expression = Val{eval_expression}, kwargs...) - f_oop, f_iip = eval_expression ? - (drop_expr(@RuntimeGeneratedFunction(ex)) for ex in f_gen) : f_gen - g_gen = generate_diffusion_function(sys, dvs, ps; expression = Val{eval_expression}, + f_gen = generate_function(sys, dvs, ps; expression = Val{!eval_expression}, kwargs...) + f_oop, f_iip = eval_expression ? eval_module.eval.(f_gen) : + (drop_expr(@RuntimeGeneratedFunction(ex)) for ex in f_gen) + g_gen = generate_diffusion_function(sys, dvs, ps; expression = Val{!eval_expression}, kwargs...) - g_oop, g_iip = eval_expression ? - (drop_expr(@RuntimeGeneratedFunction(ex)) for ex in g_gen) : g_gen + g_oop, g_iip = eval_expression ? eval_module.eval.(g_gen) : + (drop_expr(@RuntimeGeneratedFunction(ex)) for ex in g_gen) f(u, p, t) = f_oop(u, p, t) f(u, p::MTKParameters, t) = f_oop(u, p..., t) @@ -433,11 +433,11 @@ function DiffEqBase.SDEFunction{iip}(sys::SDESystem, dvs = unknowns(sys), g(du, u, p::MTKParameters, t) = g_iip(du, u, p..., t) if tgrad - tgrad_gen = generate_tgrad(sys, dvs, ps; expression = Val{eval_expression}, + tgrad_gen = generate_tgrad(sys, dvs, ps; expression = Val{!eval_expression}, kwargs...) - tgrad_oop, tgrad_iip = eval_expression ? - (drop_expr(@RuntimeGeneratedFunction(ex)) for ex in tgrad_gen) : - tgrad_gen + tgrad_oop, tgrad_iip = eval_expression ? eval_module.eval.(tgrad_gen) : + (drop_expr(@RuntimeGeneratedFunction(ex)) for ex in tgrad_gen) + _tgrad(u, p, t) = tgrad_oop(u, p, t) _tgrad(u, p::MTKParameters, t) = tgrad_oop(u, p..., t) _tgrad(J, u, p, t) = tgrad_iip(J, u, p, t) @@ -447,11 +447,11 @@ function DiffEqBase.SDEFunction{iip}(sys::SDESystem, dvs = unknowns(sys), end if jac - jac_gen = generate_jacobian(sys, dvs, ps; expression = Val{eval_expression}, + jac_gen = generate_jacobian(sys, dvs, ps; expression = Val{!eval_expression}, sparse = sparse, kwargs...) - jac_oop, jac_iip = eval_expression ? - (drop_expr(@RuntimeGeneratedFunction(ex)) for ex in jac_gen) : - jac_gen + jac_oop, jac_iip = eval_expression ? eval_module.eval.(jac_gen) : + (drop_expr(@RuntimeGeneratedFunction(ex)) for ex in jac_gen) + _jac(u, p, t) = jac_oop(u, p, t) _jac(u, p::MTKParameters, t) = jac_oop(u, p..., t) _jac(J, u, p, t) = jac_iip(J, u, p, t) @@ -463,12 +463,11 @@ function DiffEqBase.SDEFunction{iip}(sys::SDESystem, dvs = unknowns(sys), if Wfact tmp_Wfact, tmp_Wfact_t = generate_factorized_W(sys, dvs, ps, true; expression = Val{true}, kwargs...) - Wfact_oop, Wfact_iip = eval_expression ? - (drop_expr(@RuntimeGeneratedFunction(ex)) for ex in tmp_Wfact) : - tmp_Wfact - Wfact_oop_t, Wfact_iip_t = eval_expression ? - (drop_expr(@RuntimeGeneratedFunction(ex)) for ex in tmp_Wfact_t) : - tmp_Wfact_t + Wfact_oop, Wfact_iip = eval_expression ? eval_module.eval.(tmp_Wfact) : + (drop_expr(@RuntimeGeneratedFunction(ex)) for ex in tmp_Wfact) + Wfact_oop_t, Wfact_iip_t = eval_expression ? eval_module.eval.(tmp_Wfact_t) : + (drop_expr(@RuntimeGeneratedFunction(ex)) for ex in tmp_Wfact_t) + _Wfact(u, p, dtgamma, t) = Wfact_oop(u, p, dtgamma, t) _Wfact(u, p::MTKParameters, dtgamma, t) = Wfact_oop(u, p..., dtgamma, t) _Wfact(W, u, p, dtgamma, t) = Wfact_iip(W, u, p, dtgamma, t) diff --git a/src/systems/discrete_system/discrete_system.jl b/src/systems/discrete_system/discrete_system.jl index 18755ebafb..e9819645d9 100644 --- a/src/systems/discrete_system/discrete_system.jl +++ b/src/systems/discrete_system/discrete_system.jl @@ -222,7 +222,7 @@ end function process_DiscreteProblem(constructor, sys::DiscreteSystem, u0map, parammap; linenumbers = true, parallel = SerialForm(), - eval_expression = true, + eval_expression = false, use_union = false, tofloat = !use_union, kwargs...) @@ -271,7 +271,7 @@ function SciMLBase.DiscreteProblem( sys::DiscreteSystem, u0map = [], tspan = get_tspan(sys), parammap = SciMLBase.NullParameters(); eval_module = @__MODULE__, - eval_expression = true, + eval_expression = false, use_union = false, kwargs... ) @@ -308,18 +308,17 @@ function SciMLBase.DiscreteFunction{iip, specialize}( version = nothing, p = nothing, t = nothing, - eval_expression = true, + eval_expression = false, eval_module = @__MODULE__, analytic = nothing, 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{eval_expression}, + f_gen = generate_function(sys, dvs, ps; expression = Val{!eval_expression}, expression_module = eval_module, kwargs...) - f_oop, f_iip = eval_expression ? - (drop_expr(@RuntimeGeneratedFunction(eval_module, ex)) for ex in f_gen) : - f_gen + f_oop, f_iip = eval_expression ? eval_module.eval.(f_gen) : + (drop_expr(@RuntimeGeneratedFunction(eval_module, ex)) for ex in f_gen) f(u, p, t) = f_oop(u, p, t) f(du, u, p, t) = f_iip(du, u, p, t) diff --git a/src/systems/nonlinear/nonlinearsystem.jl b/src/systems/nonlinear/nonlinearsystem.jl index d1d3f56247..77b2721e93 100644 --- a/src/systems/nonlinear/nonlinearsystem.jl +++ b/src/systems/nonlinear/nonlinearsystem.jl @@ -277,15 +277,15 @@ function SciMLBase.NonlinearFunction{iip}(sys::NonlinearSystem, dvs = unknowns(s ps = full_parameters(sys), u0 = nothing; version = nothing, jac = false, - eval_expression = true, + eval_expression = 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 `NonlinearFunction`") end - f_gen = generate_function(sys, dvs, ps; expression = Val{eval_expression}, kwargs...) - f_oop, f_iip = eval_expression ? - (drop_expr(@RuntimeGeneratedFunction(ex)) for ex in f_gen) : f_gen + f_gen = generate_function(sys, dvs, ps; expression = Val{!eval_expression}, kwargs...) + f_oop, f_iip = eval_expression ? eval_module.eval.(f_gen) : + (drop_expr(@RuntimeGeneratedFunction(ex)) for ex in f_gen) f(u, p) = f_oop(u, p) f(u, p::MTKParameters) = f_oop(u, p...) f(du, u, p) = f_iip(du, u, p) @@ -294,10 +294,9 @@ function SciMLBase.NonlinearFunction{iip}(sys::NonlinearSystem, dvs = unknowns(s if jac jac_gen = generate_jacobian(sys, dvs, ps; simplify = simplify, sparse = sparse, - expression = Val{eval_expression}, kwargs...) - jac_oop, jac_iip = eval_expression ? - (drop_expr(@RuntimeGeneratedFunction(ex)) for ex in jac_gen) : - jac_gen + expression = Val{!eval_expression}, kwargs...) + jac_oop, jac_iip = eval_expression ? eval_module.eval.(jac_gen) : + (drop_expr(@RuntimeGeneratedFunction(ex)) for ex in jac_gen) _jac(u, p) = jac_oop(u, p) _jac(u, p::MTKParameters) = jac_oop(u, p...) _jac(J, u, p) = jac_iip(J, u, p) @@ -376,7 +375,7 @@ function process_NonlinearProblem(constructor, sys::NonlinearSystem, u0map, para checkbounds = false, sparse = false, simplify = false, linenumbers = true, parallel = SerialForm(), - eval_expression = true, + eval_expression = false, use_union = false, tofloat = !use_union, kwargs...) diff --git a/test/precompile_test.jl b/test/precompile_test.jl index 206459f500..fe8f2dd875 100644 --- a/test/precompile_test.jl +++ b/test/precompile_test.jl @@ -30,3 +30,11 @@ end @test parentmodule(typeof(ODEPrecompileTest.f_noeval_good.f.f_oop).parameters[2]) == ODEPrecompileTest @test ODEPrecompileTest.f_noeval_good(u, p, 0.1) == [4, 0, -16] + +@test_throws KeyError ODEPrecompileTest.f_eval_bad(u, p, 0.1) + +@test parentmodule(typeof(ODEPrecompileTest.f_eval_good.f.f_iip).parameters[2]) == + ODEPrecompileTest +@test parentmodule(typeof(ODEPrecompileTest.f_eval_good.f.f_oop).parameters[2]) == + ODEPrecompileTest +@test ODEPrecompileTest.f_eval_good(u, p, 0.1) == [4, 0, -16] diff --git a/test/precompile_test/ODEPrecompileTest.jl b/test/precompile_test/ODEPrecompileTest.jl index 3e25fa21e2..f1ef601350 100644 --- a/test/precompile_test/ODEPrecompileTest.jl +++ b/test/precompile_test/ODEPrecompileTest.jl @@ -27,4 +27,12 @@ const f_noeval_bad = system(; eval_expression = false) using RuntimeGeneratedFunctions RuntimeGeneratedFunctions.init(@__MODULE__) const f_noeval_good = system(; eval_expression = false, eval_module = @__MODULE__) + +# Eval the expression but into MTK's module, which means it won't be properly cached by +# the package image +const f_eval_bad = system(; eval_expression = true, eval_module = @__MODULE__) + +# Change the module the eval'd function is eval'd into to be the containing module, +# which should make it be in the package image +const f_eval_good = system(; eval_expression = true, eval_module = @__MODULE__) end From bab10f4150b25515299648ac66a98e0b880c2f96 Mon Sep 17 00:00:00 2001 From: Christopher Rackauckas Date: Fri, 28 Jun 2024 06:26:14 +0800 Subject: [PATCH 219/316] add some docs --- docs/pages.jl | 1 + docs/src/basics/Precompilation.md | 117 +++++++++++++++++++++++ src/systems/diffeqs/abstractodesystem.jl | 4 +- src/systems/diffeqs/sdesystem.jl | 6 +- 4 files changed, 123 insertions(+), 5 deletions(-) create mode 100644 docs/src/basics/Precompilation.md diff --git a/docs/pages.jl b/docs/pages.jl index ca265ccef5..ff499177bf 100644 --- a/docs/pages.jl +++ b/docs/pages.jl @@ -31,6 +31,7 @@ pages = [ "basics/MTKModel_Connector.md", "basics/Validation.md", "basics/DependencyGraphs.md", + "basics/Precompilation.md", "basics/FAQ.md"], "System Types" => Any["systems/ODESystem.md", "systems/SDESystem.md", diff --git a/docs/src/basics/Precompilation.md b/docs/src/basics/Precompilation.md new file mode 100644 index 0000000000..97111f0d6b --- /dev/null +++ b/docs/src/basics/Precompilation.md @@ -0,0 +1,117 @@ +# Working with Precompilation and Binary Building + +## tl;dr, I just want precompilation to work + +The tl;dr is, if you want to make precompilation work then instead of + +```julia +ODEProblem(sys, u0, tspan, p) +``` + +use: + +```julia +ODEProblem(sys, u0, tspan, p, eval_module = @__MODULE__, eval_expression = true) +``` + +As a full example, here's an example of a module that would precompile effectively: + +```julia +module PrecompilationMWE +using ModelingToolkit + +@variables x(ModelingToolkit.t_nounits) +@named sys = ODESystem([ModelingToolkit.D_nounits(x) ~ -x + 1], ModelingToolkit.t_nounits) +prob = ODEProblem(structural_simplify(sys), [x => 30.0], (0, 100), [], + eval_expression = true, eval_module = @__MODULE__) + +end +``` + +If you use that in your package's code then 99% of the time that's the right answer to get +precompilation working. + +## I'm doing something fancier and need a bit more of an explanation + +Oh you dapper soul, time for the bigger explanation. Julia's `eval` function evaluates a +function into a module at a specified world-age. If you evaluate a function within a function +and try to call it from within that same function, you will hit a world-age error. This looks like: + +```julia +function worldageerror() + f = eval(:((x) -> 2x)) + f(2) +end +``` + +``` +julia> worldageerror() +ERROR: MethodError: no method matching (::var"#5#6")(::Int64) + +Closest candidates are: + (::var"#5#6")(::Any) (method too new to be called from this world context.) + @ Main REPL[12]:2 +``` + +This is done for many reasons, in particular if the code that is called within a function could change +at any time, then Julia functions could not ever properly optimize because the meaning of any function +or dispatch could always change and you would lose performance by guarding against that. For a full +discussion of world-age, see [this paper](https://arxiv.org/abs/2010.07516). + +However, this would be greatly inhibiting to standard ModelingToolkit usage because then something as +simple as building an ODEProblem in a function and then using it would get a world age error: + +```julia +function wouldworldage() + prob = ODEProblem(sys, [], (0.0, 1.0)) + sol = solve(prob) +end +``` + +The reason is because `prob.f` would be constructed via `eval`, and thus `prob.f` could not be called +in the function, which means that no solve could ever work in the same function that generated the +problem. That does mean that: + +```julia +function wouldworldage() + prob = ODEProblem(sys, [], (0.0, 1.0)) +end +sol = solve(prob) +``` + +is fine, or putting + +```julia +prob = ODEProblem(sys, [], (0.0, 1.0)) +sol = solve(prob) +``` + +at the top level of a module is perfectly fine too. They just cannot happen in the same function. + +This would be a major limitation to ModelingToolkit, and thus we developed +[RuntimeGeneratedFunctions](https://github.com/SciML/RuntimeGeneratedFunctions.jl) to get around +this limitation. It will not be described beyond that, it is dark art and should not be investigated. +But it does the job. But that does mean that it plays... oddly with Julia's compilation. + +There are ways to force RuntimeGeneratedFunctions to perform their evaluation and caching within +a given module, but that is not recommended because it does not play nicely with Julia v1.9's +introduction of package images for binary caching. + +Thus when trying to make things work with precompilation, we recommend using `eval`. This is +done by simply adding `eval_expression=true` to the problem constructor. However, this is not +a silver bullet because the moment you start using eval, all potential world-age restrictions +apply, and thus it is recommended this is simply used for evaluating at the top level of modules +for the purpose of precompilation and ensuring binaries of your MTK functions are built correctly. + +However, there is one caveat that `eval` in Julia works depending on the module that it is given. +If you have `MyPackage` that you are precompiling into, or say you are using `juliac` or PackageCompiler +or some other static ahead-of-time (AOT) Julia compiler, then you don't want to accidentally `eval` +that function to live in ModelingToolkit and instead want to make sure it is `eval`'d to live in `MyPackage` +(since otherwise it will not cache into the binary). ModelingToolkit cannot know that in advance, and thus +you have to pass in the module you wish for the functions to "live" in. This is done via the `eval_module` +argument. + +Hence `ODEProblem(sys, u0, tspan, p, eval_module=@__MODULE__, eval_expression=true)` will work if you +are running this expression in the scope of the module you wish to be precompiling. However, if you are +attempting to AOT compile a different module, this means that `eval_module` needs to be appropriately +chosen. And, because `eval_expression=true`, all caveats of world-age apply. diff --git a/src/systems/diffeqs/abstractodesystem.jl b/src/systems/diffeqs/abstractodesystem.jl index e8b714ea85..da6a96a886 100644 --- a/src/systems/diffeqs/abstractodesystem.jl +++ b/src/systems/diffeqs/abstractodesystem.jl @@ -378,7 +378,7 @@ function DiffEqBase.ODEFunction{iip, specialize}(sys::AbstractODESystem, checkbounds = checkbounds, kwargs...) jac_oop, jac_iip = eval_expression ? eval_module.eval.(jac_gen) : (drop_expr(@RuntimeGeneratedFunction(eval_module, ex)) for ex in jac_gen) - + _jac(u, p, t) = jac_oop(u, p, t) _jac(J, u, p, t) = jac_iip(J, u, p, t) _jac(u, p::Tuple{Vararg{Number}}, t) = jac_oop(u, p, t) @@ -502,7 +502,7 @@ function DiffEqBase.DAEFunction{iip}(sys::AbstractODESystem, dvs = unknowns(sys) checkbounds = checkbounds, kwargs...) jac_oop, jac_iip = eval_expression ? eval_module.eval.(jac_gen) : (drop_expr(@RuntimeGeneratedFunction(eval_module, ex)) for ex in jac_gen) - + _jac(du, u, p, ˍ₋gamma, t) = jac_oop(du, u, p, ˍ₋gamma, t) _jac(du, u, p::MTKParameters, ˍ₋gamma, t) = jac_oop(du, u, p..., ˍ₋gamma, t) diff --git a/src/systems/diffeqs/sdesystem.jl b/src/systems/diffeqs/sdesystem.jl index 1ddaa7b2d6..0604cf8db7 100644 --- a/src/systems/diffeqs/sdesystem.jl +++ b/src/systems/diffeqs/sdesystem.jl @@ -437,7 +437,7 @@ function DiffEqBase.SDEFunction{iip}(sys::SDESystem, dvs = unknowns(sys), kwargs...) tgrad_oop, tgrad_iip = eval_expression ? eval_module.eval.(tgrad_gen) : (drop_expr(@RuntimeGeneratedFunction(ex)) for ex in tgrad_gen) - + _tgrad(u, p, t) = tgrad_oop(u, p, t) _tgrad(u, p::MTKParameters, t) = tgrad_oop(u, p..., t) _tgrad(J, u, p, t) = tgrad_iip(J, u, p, t) @@ -449,9 +449,9 @@ function DiffEqBase.SDEFunction{iip}(sys::SDESystem, dvs = unknowns(sys), if jac jac_gen = generate_jacobian(sys, dvs, ps; expression = Val{!eval_expression}, sparse = sparse, kwargs...) - jac_oop, jac_iip = eval_expression ? eval_module.eval.(jac_gen) : + jac_oop, jac_iip = eval_expression ? eval_module.eval.(jac_gen) : (drop_expr(@RuntimeGeneratedFunction(ex)) for ex in jac_gen) - + _jac(u, p, t) = jac_oop(u, p, t) _jac(u, p::MTKParameters, t) = jac_oop(u, p..., t) _jac(J, u, p, t) = jac_iip(J, u, p, t) From a2e9ee7850e50eb7f4716a03b4aab0db7d5ce2f9 Mon Sep 17 00:00:00 2001 From: Christopher Rackauckas Date: Fri, 28 Jun 2024 06:48:59 +0800 Subject: [PATCH 220/316] fix expression bool --- src/systems/diffeqs/abstractodesystem.jl | 10 +++++----- src/systems/diffeqs/sdesystem.jl | 8 ++++---- src/systems/discrete_system/discrete_system.jl | 2 +- src/systems/nonlinear/nonlinearsystem.jl | 4 ++-- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/systems/diffeqs/abstractodesystem.jl b/src/systems/diffeqs/abstractodesystem.jl index da6a96a886..83fe24361d 100644 --- a/src/systems/diffeqs/abstractodesystem.jl +++ b/src/systems/diffeqs/abstractodesystem.jl @@ -327,7 +327,7 @@ function DiffEqBase.ODEFunction{iip, specialize}(sys::AbstractODESystem, 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{!eval_expression}, + f_gen = generate_function(sys, dvs, ps; expression = Val{true}, expression_module = eval_module, checkbounds = checkbounds, kwargs...) f_oop, f_iip = eval_expression ? eval_module.eval.(f_gen) : @@ -352,7 +352,7 @@ function DiffEqBase.ODEFunction{iip, specialize}(sys::AbstractODESystem, if tgrad tgrad_gen = generate_tgrad(sys, dvs, ps; simplify = simplify, - expression = Val{!eval_expression}, + expression = Val{true}, expression_module = eval_module, checkbounds = checkbounds, kwargs...) tgrad_oop, tgrad_iip = eval_expression ? eval_module.eval.(tgrad_gen) : @@ -373,7 +373,7 @@ function DiffEqBase.ODEFunction{iip, specialize}(sys::AbstractODESystem, if jac jac_gen = generate_jacobian(sys, dvs, ps; simplify = simplify, sparse = sparse, - expression = Val{!eval_expression}, + expression = Val{true}, expression_module = eval_module, checkbounds = checkbounds, kwargs...) jac_oop, jac_iip = eval_expression ? eval_module.eval.(jac_gen) : @@ -484,7 +484,7 @@ function DiffEqBase.DAEFunction{iip}(sys::AbstractODESystem, dvs = unknowns(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{!eval_expression}, + expression = Val{true}, expression_module = eval_module, checkbounds = checkbounds, kwargs...) f_oop, f_iip = eval_expression ? eval_module.eval.(f_gen) : @@ -497,7 +497,7 @@ function DiffEqBase.DAEFunction{iip}(sys::AbstractODESystem, dvs = unknowns(sys) if jac jac_gen = generate_dae_jacobian(sys, dvs, ps; simplify = simplify, sparse = sparse, - expression = Val{!eval_expression}, + expression = Val{true}, expression_module = eval_module, checkbounds = checkbounds, kwargs...) jac_oop, jac_iip = eval_expression ? eval_module.eval.(jac_gen) : diff --git a/src/systems/diffeqs/sdesystem.jl b/src/systems/diffeqs/sdesystem.jl index 0604cf8db7..a81a6694e7 100644 --- a/src/systems/diffeqs/sdesystem.jl +++ b/src/systems/diffeqs/sdesystem.jl @@ -415,10 +415,10 @@ function DiffEqBase.SDEFunction{iip}(sys::SDESystem, dvs = unknowns(sys), end dvs = scalarize.(dvs) - f_gen = generate_function(sys, dvs, ps; expression = Val{!eval_expression}, kwargs...) + f_gen = generate_function(sys, dvs, ps; expression = Val{true}, kwargs...) f_oop, f_iip = eval_expression ? eval_module.eval.(f_gen) : (drop_expr(@RuntimeGeneratedFunction(ex)) for ex in f_gen) - g_gen = generate_diffusion_function(sys, dvs, ps; expression = Val{!eval_expression}, + g_gen = generate_diffusion_function(sys, dvs, ps; expression = Val{true}, kwargs...) g_oop, g_iip = eval_expression ? eval_module.eval.(g_gen) : (drop_expr(@RuntimeGeneratedFunction(ex)) for ex in g_gen) @@ -433,7 +433,7 @@ function DiffEqBase.SDEFunction{iip}(sys::SDESystem, dvs = unknowns(sys), g(du, u, p::MTKParameters, t) = g_iip(du, u, p..., t) if tgrad - tgrad_gen = generate_tgrad(sys, dvs, ps; expression = Val{!eval_expression}, + tgrad_gen = generate_tgrad(sys, dvs, ps; expression = Val{true}, kwargs...) tgrad_oop, tgrad_iip = eval_expression ? eval_module.eval.(tgrad_gen) : (drop_expr(@RuntimeGeneratedFunction(ex)) for ex in tgrad_gen) @@ -447,7 +447,7 @@ function DiffEqBase.SDEFunction{iip}(sys::SDESystem, dvs = unknowns(sys), end if jac - jac_gen = generate_jacobian(sys, dvs, ps; expression = Val{!eval_expression}, + jac_gen = generate_jacobian(sys, dvs, ps; expression = Val{true}, sparse = sparse, kwargs...) jac_oop, jac_iip = eval_expression ? eval_module.eval.(jac_gen) : (drop_expr(@RuntimeGeneratedFunction(ex)) for ex in jac_gen) diff --git a/src/systems/discrete_system/discrete_system.jl b/src/systems/discrete_system/discrete_system.jl index e9819645d9..d6f4532e23 100644 --- a/src/systems/discrete_system/discrete_system.jl +++ b/src/systems/discrete_system/discrete_system.jl @@ -315,7 +315,7 @@ function SciMLBase.DiscreteFunction{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{!eval_expression}, + f_gen = generate_function(sys, dvs, ps; expression = Val{true}, expression_module = eval_module, kwargs...) f_oop, f_iip = eval_expression ? eval_module.eval.(f_gen) : (drop_expr(@RuntimeGeneratedFunction(eval_module, ex)) for ex in f_gen) diff --git a/src/systems/nonlinear/nonlinearsystem.jl b/src/systems/nonlinear/nonlinearsystem.jl index 77b2721e93..00f526028d 100644 --- a/src/systems/nonlinear/nonlinearsystem.jl +++ b/src/systems/nonlinear/nonlinearsystem.jl @@ -283,7 +283,7 @@ function SciMLBase.NonlinearFunction{iip}(sys::NonlinearSystem, dvs = unknowns(s 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{!eval_expression}, kwargs...) + f_gen = generate_function(sys, dvs, ps; expression = Val{true}, kwargs...) f_oop, f_iip = eval_expression ? eval_module.eval.(f_gen) : (drop_expr(@RuntimeGeneratedFunction(ex)) for ex in f_gen) f(u, p) = f_oop(u, p) @@ -294,7 +294,7 @@ function SciMLBase.NonlinearFunction{iip}(sys::NonlinearSystem, dvs = unknowns(s if jac jac_gen = generate_jacobian(sys, dvs, ps; simplify = simplify, sparse = sparse, - expression = Val{!eval_expression}, kwargs...) + expression = Val{true}, kwargs...) jac_oop, jac_iip = eval_expression ? eval_module.eval.(jac_gen) : (drop_expr(@RuntimeGeneratedFunction(ex)) for ex in jac_gen) _jac(u, p) = jac_oop(u, p) From 26bbb37d877e3e63594c2d3e8559001c508ce083 Mon Sep 17 00:00:00 2001 From: Christopher Rackauckas Date: Fri, 28 Jun 2024 07:18:45 +0800 Subject: [PATCH 221/316] fixtest --- test/precompile_test.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/precompile_test.jl b/test/precompile_test.jl index fe8f2dd875..17b05689d2 100644 --- a/test/precompile_test.jl +++ b/test/precompile_test.jl @@ -33,8 +33,8 @@ end @test_throws KeyError ODEPrecompileTest.f_eval_bad(u, p, 0.1) -@test parentmodule(typeof(ODEPrecompileTest.f_eval_good.f.f_iip).parameters[2]) == +@test parentmodule(typeof(ODEPrecompileTest.f_eval_good.f.f_iip)) == ODEPrecompileTest -@test parentmodule(typeof(ODEPrecompileTest.f_eval_good.f.f_oop).parameters[2]) == +@test parentmodule(typeof(ODEPrecompileTest.f_eval_good.f.f_oop)) == ODEPrecompileTest @test ODEPrecompileTest.f_eval_good(u, p, 0.1) == [4, 0, -16] From e1b098b618333b745cda203d0a59ddc4a6918705 Mon Sep 17 00:00:00 2001 From: Christopher Rackauckas Date: Fri, 28 Jun 2024 08:45:36 +0800 Subject: [PATCH 222/316] fix RGF evaluation modules --- src/systems/clock_inference.jl | 4 ++-- src/systems/diffeqs/abstractodesystem.jl | 14 +++++++------- src/systems/discrete_system/discrete_system.jl | 2 +- src/systems/pde/pdesystem.jl | 2 +- test/precompile_test.jl | 2 +- 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/systems/clock_inference.jl b/src/systems/clock_inference.jl index ae4f0c44f2..dcb9e8d827 100644 --- a/src/systems/clock_inference.jl +++ b/src/systems/clock_inference.jl @@ -416,10 +416,10 @@ function generate_discrete_affect( inits = map(a -> eval_module.eval(toexpr(LiteralExpr(a))), init_funs) else affects = map(affect_funs) do a - drop_expr(@RuntimeGeneratedFunction(eval_module, toexpr(LiteralExpr(a)))) + drop_expr(RuntimeGeneratedFunction(eval_module, eval_module, toexpr(LiteralExpr(a)))) end inits = map(init_funs) do a - drop_expr(@RuntimeGeneratedFunction(eval_module, toexpr(LiteralExpr(a)))) + drop_expr(RuntimeGeneratedFunction(eval_module, eval_module, toexpr(LiteralExpr(a)))) end end defaults = Dict{Any, Any}(v => 0.0 for v in Iterators.flatten(inputs)) diff --git a/src/systems/diffeqs/abstractodesystem.jl b/src/systems/diffeqs/abstractodesystem.jl index 83fe24361d..cd46f86d4e 100644 --- a/src/systems/diffeqs/abstractodesystem.jl +++ b/src/systems/diffeqs/abstractodesystem.jl @@ -331,7 +331,7 @@ function DiffEqBase.ODEFunction{iip, specialize}(sys::AbstractODESystem, expression_module = eval_module, checkbounds = checkbounds, kwargs...) f_oop, f_iip = eval_expression ? eval_module.eval.(f_gen) : - (drop_expr(@RuntimeGeneratedFunction(eval_module, ex)) for ex in f_gen) + (drop_expr(RuntimeGeneratedFunction(eval_module, eval_module, ex)) for ex in f_gen) f(u, p, t) = f_oop(u, p, t) f(du, u, p, t) = f_iip(du, u, p, t) @@ -356,7 +356,7 @@ function DiffEqBase.ODEFunction{iip, specialize}(sys::AbstractODESystem, expression_module = eval_module, checkbounds = checkbounds, kwargs...) tgrad_oop, tgrad_iip = eval_expression ? eval_module.eval.(tgrad_gen) : - (drop_expr(@RuntimeGeneratedFunction(eval_module, ex)) for ex in tgrad_gen) + (drop_expr(RuntimeGeneratedFunction(eval_module, eval_module, ex)) for ex in tgrad_gen) if p isa Tuple __tgrad(u, p, t) = tgrad_oop(u, p..., t) __tgrad(J, u, p, t) = tgrad_iip(J, u, p..., t) @@ -377,7 +377,7 @@ function DiffEqBase.ODEFunction{iip, specialize}(sys::AbstractODESystem, expression_module = eval_module, checkbounds = checkbounds, kwargs...) jac_oop, jac_iip = eval_expression ? eval_module.eval.(jac_gen) : - (drop_expr(@RuntimeGeneratedFunction(eval_module, ex)) for ex in jac_gen) + (drop_expr(RuntimeGeneratedFunction(eval_module, eval_module, ex)) for ex in jac_gen) _jac(u, p, t) = jac_oop(u, p, t) _jac(J, u, p, t) = jac_iip(J, u, p, t) @@ -488,7 +488,7 @@ function DiffEqBase.DAEFunction{iip}(sys::AbstractODESystem, dvs = unknowns(sys) expression_module = eval_module, checkbounds = checkbounds, kwargs...) f_oop, f_iip = eval_expression ? eval_module.eval.(f_gen) : - (drop_expr(@RuntimeGeneratedFunction(eval_module, ex)) for ex in f_gen) + (drop_expr(RuntimeGeneratedFunction(eval_module, eval_module, ex)) for ex in f_gen) f(du, u, p, t) = f_oop(du, u, p, t) f(du, u, p::MTKParameters, t) = f_oop(du, u, p..., t) f(out, du, u, p, t) = f_iip(out, du, u, p, t) @@ -501,7 +501,7 @@ function DiffEqBase.DAEFunction{iip}(sys::AbstractODESystem, dvs = unknowns(sys) expression_module = eval_module, checkbounds = checkbounds, kwargs...) jac_oop, jac_iip = eval_expression ? eval_module.eval.(jac_gen) : - (drop_expr(@RuntimeGeneratedFunction(eval_module, ex)) for ex in jac_gen) + (drop_expr(RuntimeGeneratedFunction(eval_module, eval_module, ex)) for ex in jac_gen) _jac(du, u, p, ˍ₋gamma, t) = jac_oop(du, u, p, ˍ₋gamma, t) _jac(du, u, p::MTKParameters, ˍ₋gamma, t) = jac_oop(du, u, p..., ˍ₋gamma, t) @@ -553,7 +553,7 @@ function DiffEqBase.DDEFunction{iip}(sys::AbstractODESystem, dvs = unknowns(sys) expression = Val{true}, expression_module = eval_module, checkbounds = checkbounds, kwargs...) - f_oop, f_iip = (drop_expr(@RuntimeGeneratedFunction(eval_module, ex)) for ex in f_gen) + f_oop, f_iip = (drop_expr(RuntimeGeneratedFunction(eval_module, eval_module, ex)) for ex in f_gen) f(u, h, p, t) = f_oop(u, h, p, t) f(u, h, p::MTKParameters, t) = f_oop(u, h, p..., t) f(du, u, h, p, t) = f_iip(du, u, h, p, t) @@ -578,7 +578,7 @@ function DiffEqBase.SDDEFunction{iip}(sys::AbstractODESystem, dvs = unknowns(sys expression = Val{true}, expression_module = eval_module, checkbounds = checkbounds, kwargs...) - f_oop, f_iip = (drop_expr(@RuntimeGeneratedFunction(eval_module, ex)) for ex in f_gen) + f_oop, f_iip = (drop_expr(RuntimeGeneratedFunction(eval_module, eval_module, ex)) for ex in f_gen) g_gen = generate_diffusion_function(sys, dvs, ps; expression = Val{true}, isdde = true, kwargs...) g_oop, g_iip = (drop_expr(@RuntimeGeneratedFunction(ex)) for ex in g_gen) diff --git a/src/systems/discrete_system/discrete_system.jl b/src/systems/discrete_system/discrete_system.jl index d6f4532e23..d19c93435d 100644 --- a/src/systems/discrete_system/discrete_system.jl +++ b/src/systems/discrete_system/discrete_system.jl @@ -318,7 +318,7 @@ function SciMLBase.DiscreteFunction{iip, specialize}( f_gen = generate_function(sys, dvs, ps; expression = Val{true}, expression_module = eval_module, kwargs...) f_oop, f_iip = eval_expression ? eval_module.eval.(f_gen) : - (drop_expr(@RuntimeGeneratedFunction(eval_module, ex)) for ex in f_gen) + (drop_expr(RuntimeGeneratedFunction(eval_module, eval_module, ex)) for ex in f_gen) f(u, p, t) = f_oop(u, p, t) f(du, u, p, t) = f_iip(du, u, p, t) diff --git a/src/systems/pde/pdesystem.jl b/src/systems/pde/pdesystem.jl index 3735b8f6ea..3a54dd5d03 100644 --- a/src/systems/pde/pdesystem.jl +++ b/src/systems/pde/pdesystem.jl @@ -116,7 +116,7 @@ struct PDESystem <: ModelingToolkit.AbstractMultivariateSystem p = ps isa SciMLBase.NullParameters ? [] : ps args = vcat(DestructuredArgs(p), args) ex = Func(args, [], eq.rhs) |> toexpr - eq.lhs => drop_expr(@RuntimeGeneratedFunction(eval_module, ex)) + eq.lhs => drop_expr(RuntimeGeneratedFunction(eval_module, eval_module, ex)) end end end diff --git a/test/precompile_test.jl b/test/precompile_test.jl index 17b05689d2..6e31317de2 100644 --- a/test/precompile_test.jl +++ b/test/precompile_test.jl @@ -31,7 +31,7 @@ end ODEPrecompileTest @test ODEPrecompileTest.f_noeval_good(u, p, 0.1) == [4, 0, -16] -@test_throws KeyError ODEPrecompileTest.f_eval_bad(u, p, 0.1) +ODEPrecompileTest.f_eval_bad(u, p, 0.1) @test parentmodule(typeof(ODEPrecompileTest.f_eval_good.f.f_iip)) == ODEPrecompileTest From adf98ba43cd61cddf41da67b8012b11e5ca0e8cc Mon Sep 17 00:00:00 2001 From: Christopher Rackauckas Date: Fri, 28 Jun 2024 08:51:39 +0800 Subject: [PATCH 223/316] format --- src/systems/clock_inference.jl | 6 ++++-- src/systems/diffeqs/abstractodesystem.jl | 9 ++++++--- src/systems/pde/pdesystem.jl | 3 ++- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/systems/clock_inference.jl b/src/systems/clock_inference.jl index dcb9e8d827..dc1d612d73 100644 --- a/src/systems/clock_inference.jl +++ b/src/systems/clock_inference.jl @@ -416,10 +416,12 @@ function generate_discrete_affect( inits = map(a -> eval_module.eval(toexpr(LiteralExpr(a))), init_funs) else affects = map(affect_funs) do a - drop_expr(RuntimeGeneratedFunction(eval_module, eval_module, toexpr(LiteralExpr(a)))) + drop_expr(RuntimeGeneratedFunction( + eval_module, eval_module, toexpr(LiteralExpr(a)))) end inits = map(init_funs) do a - drop_expr(RuntimeGeneratedFunction(eval_module, eval_module, toexpr(LiteralExpr(a)))) + drop_expr(RuntimeGeneratedFunction( + eval_module, eval_module, toexpr(LiteralExpr(a)))) end end defaults = Dict{Any, Any}(v => 0.0 for v in Iterators.flatten(inputs)) diff --git a/src/systems/diffeqs/abstractodesystem.jl b/src/systems/diffeqs/abstractodesystem.jl index cd46f86d4e..e62d6a77fe 100644 --- a/src/systems/diffeqs/abstractodesystem.jl +++ b/src/systems/diffeqs/abstractodesystem.jl @@ -356,7 +356,8 @@ function DiffEqBase.ODEFunction{iip, specialize}(sys::AbstractODESystem, expression_module = eval_module, checkbounds = checkbounds, kwargs...) tgrad_oop, tgrad_iip = eval_expression ? eval_module.eval.(tgrad_gen) : - (drop_expr(RuntimeGeneratedFunction(eval_module, eval_module, ex)) for ex in tgrad_gen) + (drop_expr(RuntimeGeneratedFunction( + eval_module, eval_module, ex)) for ex in tgrad_gen) if p isa Tuple __tgrad(u, p, t) = tgrad_oop(u, p..., t) __tgrad(J, u, p, t) = tgrad_iip(J, u, p..., t) @@ -377,7 +378,8 @@ function DiffEqBase.ODEFunction{iip, specialize}(sys::AbstractODESystem, expression_module = eval_module, checkbounds = checkbounds, kwargs...) jac_oop, jac_iip = eval_expression ? eval_module.eval.(jac_gen) : - (drop_expr(RuntimeGeneratedFunction(eval_module, eval_module, ex)) for ex in jac_gen) + (drop_expr(RuntimeGeneratedFunction( + eval_module, eval_module, ex)) for ex in jac_gen) _jac(u, p, t) = jac_oop(u, p, t) _jac(J, u, p, t) = jac_iip(J, u, p, t) @@ -501,7 +503,8 @@ function DiffEqBase.DAEFunction{iip}(sys::AbstractODESystem, dvs = unknowns(sys) expression_module = eval_module, checkbounds = checkbounds, kwargs...) jac_oop, jac_iip = eval_expression ? eval_module.eval.(jac_gen) : - (drop_expr(RuntimeGeneratedFunction(eval_module, eval_module, ex)) for ex in jac_gen) + (drop_expr(RuntimeGeneratedFunction( + eval_module, eval_module, ex)) for ex in jac_gen) _jac(du, u, p, ˍ₋gamma, t) = jac_oop(du, u, p, ˍ₋gamma, t) _jac(du, u, p::MTKParameters, ˍ₋gamma, t) = jac_oop(du, u, p..., ˍ₋gamma, t) diff --git a/src/systems/pde/pdesystem.jl b/src/systems/pde/pdesystem.jl index 3a54dd5d03..e14c59f440 100644 --- a/src/systems/pde/pdesystem.jl +++ b/src/systems/pde/pdesystem.jl @@ -116,7 +116,8 @@ struct PDESystem <: ModelingToolkit.AbstractMultivariateSystem p = ps isa SciMLBase.NullParameters ? [] : ps args = vcat(DestructuredArgs(p), args) ex = Func(args, [], eq.rhs) |> toexpr - eq.lhs => drop_expr(RuntimeGeneratedFunction(eval_module, eval_module, ex)) + eq.lhs => drop_expr(RuntimeGeneratedFunction( + eval_module, eval_module, ex)) end end end From b0c4b2b8c86bd70e5fa5e2863ee0c4a386b2eff9 Mon Sep 17 00:00:00 2001 From: Christopher Rackauckas Date: Fri, 28 Jun 2024 09:45:05 +0800 Subject: [PATCH 224/316] Update Project.toml --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index ad35a28521..6b7ff33d13 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.20.0" +version = "9.21.0" [deps] AbstractTrees = "1520ce14-60c1-5f80-bbc7-55ef81b5835c" From add87d524ed675b9caa9095713e63da7127a541a Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Fri, 28 Jun 2024 17:53:43 +0530 Subject: [PATCH 225/316] feat: complete `eval_expression` and `eval_module` support --- src/inputoutput.jl | 9 +- src/systems/abstractsystem.jl | 126 +++++++++--------- src/systems/callbacks.jl | 22 +-- src/systems/connectors.jl | 2 +- src/systems/diffeqs/abstractodesystem.jl | 82 +++++++----- src/systems/diffeqs/odesystem.jl | 9 +- src/systems/diffeqs/sdesystem.jl | 23 ++-- .../discrete_system/discrete_system.jl | 10 +- src/systems/jumps/jumpsystem.jl | 37 +++-- src/systems/nonlinear/nonlinearsystem.jl | 15 ++- .../optimization/optimizationsystem.jl | 98 +++++++++----- src/systems/parameter_buffer.jl | 18 ++- src/utils.jl | 8 ++ 13 files changed, 265 insertions(+), 194 deletions(-) diff --git a/src/inputoutput.jl b/src/inputoutput.jl index 31dc393418..e07d2ed976 100644 --- a/src/inputoutput.jl +++ b/src/inputoutput.jl @@ -195,6 +195,8 @@ function generate_control_function(sys::AbstractODESystem, inputs = unbound_inpu disturbance_inputs = disturbances(sys); implicit_dae = false, simplify = false, + eval_expression = false, + eval_module = @__MODULE__, kwargs...) isempty(inputs) && @warn("No unbound inputs were found in system.") @@ -240,7 +242,8 @@ function generate_control_function(sys::AbstractODESystem, inputs = unbound_inpu end process = get_postprocess_fbody(sys) f = build_function(rhss, args...; postprocess_fbody = process, - expression = Val{false}, kwargs...) + expression = Val{true}, kwargs...) + f = eval_or_rgf.(f; eval_expression, eval_module) (; f, dvs, ps, io_sys = sys) end @@ -395,7 +398,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) +function add_input_disturbance(sys, dist::DisturbanceModel, inputs = nothing; kwargs...) t = get_iv(sys) @variables d(t)=0 [disturbance = true] @variables u(t)=0 [input = true] # New system input @@ -418,6 +421,6 @@ function add_input_disturbance(sys, dist::DisturbanceModel, inputs = nothing) augmented_sys = extend(augmented_sys, sys) (f_oop, f_ip), dvs, p = generate_control_function(augmented_sys, all_inputs, - [d]) + [d]; kwargs...) (f_oop, f_ip), augmented_sys, dvs, p end diff --git a/src/systems/abstractsystem.jl b/src/systems/abstractsystem.jl index 013705e34b..81950b8a26 100644 --- a/src/systems/abstractsystem.jl +++ b/src/systems/abstractsystem.jl @@ -161,7 +161,8 @@ time-independent systems. If `split=true` (the default) was passed to [`complete object. """ function generate_custom_function(sys::AbstractSystem, exprs, dvs = unknowns(sys), - ps = parameters(sys); wrap_code = nothing, postprocess_fbody = nothing, states = nothing, kwargs...) + ps = parameters(sys); wrap_code = nothing, postprocess_fbody = nothing, states = nothing, + expression = Val{true}, eval_expression = false, eval_module = @__MODULE__, kwargs...) if !iscomplete(sys) error("A completed system is required. Call `complete` or `structural_simplify` on the system.") end @@ -177,8 +178,8 @@ function generate_custom_function(sys::AbstractSystem, exprs, dvs = unknowns(sys if states === nothing states = sol_states end - if is_time_dependent(sys) - return build_function(exprs, + fnexpr = if is_time_dependent(sys) + build_function(exprs, dvs, p..., get_iv(sys); @@ -186,19 +187,29 @@ function generate_custom_function(sys::AbstractSystem, exprs, dvs = unknowns(sys postprocess_fbody, states, wrap_code = wrap_code .∘ wrap_mtkparameters(sys, isscalar) .∘ - wrap_array_vars(sys, exprs; dvs) + wrap_array_vars(sys, exprs; dvs), + expression = Val{true} ) else - return build_function(exprs, + build_function(exprs, dvs, p...; kwargs..., postprocess_fbody, states, wrap_code = wrap_code .∘ wrap_mtkparameters(sys, isscalar) .∘ - wrap_array_vars(sys, exprs; dvs) + wrap_array_vars(sys, exprs; dvs), + expression = Val{true} ) end + if expression == Val{true} + return fnexpr + end + if fnexpr isa Tuple + return eval_or_rgf.(fnexpr; eval_expression, eval_module) + else + return eval_or_rgf(fnexpr; eval_expression, eval_module) + end end function wrap_assignments(isscalar, assignments; let_block = false) @@ -509,7 +520,8 @@ function SymbolicIndexingInterface.is_observed(sys::AbstractSystem, sym) !is_independent_variable(sys, sym) && symbolic_type(sym) != NotSymbolic() end -function SymbolicIndexingInterface.observed(sys::AbstractSystem, sym) +function SymbolicIndexingInterface.observed( + sys::AbstractSystem, sym; eval_expression = false, eval_module = @__MODULE__) if has_index_cache(sys) && (ic = get_index_cache(sys)) !== nothing if sym isa Symbol _sym = get(ic.symbol_to_variable, sym, nothing) @@ -531,7 +543,8 @@ function SymbolicIndexingInterface.observed(sys::AbstractSystem, sym) end end end - _fn = build_explicit_observed_function(sys, sym) + _fn = build_explicit_observed_function(sys, sym; eval_expression, eval_module) + if is_time_dependent(sys) return let _fn = _fn fn1(u, p, t) = _fn(u, p, t) @@ -1210,19 +1223,30 @@ end struct ObservedFunctionCache{S} sys::S dict::Dict{Any, Any} + eval_expression::Bool + eval_module::Module end -function ObservedFunctionCache(sys) - return ObservedFunctionCache(sys, Dict()) - let sys = sys, dict = Dict() - function generated_observed(obsvar, args...) - end - end +function ObservedFunctionCache(sys; eval_expression = false, eval_module = @__MODULE__) + return ObservedFunctionCache(sys, Dict(), eval_expression, eval_module) +end + +# This is hit because ensemble problems do a deepcopy +function Base.deepcopy_internal(ofc::ObservedFunctionCache, stackdict::IdDict) + sys = deepcopy(ofc.sys) + dict = deepcopy(ofc.dict) + eval_expression = ofc.eval_expression + eval_module = ofc.eval_module + newofc = ObservedFunctionCache(sys, dict, eval_expression, eval_module) + stackdict[ofc] = newofc + return newofc end function (ofc::ObservedFunctionCache)(obsvar, args...) obs = get!(ofc.dict, value(obsvar)) do - SymbolicIndexingInterface.observed(ofc.sys, obsvar) + SymbolicIndexingInterface.observed( + ofc.sys, obsvar; eval_expression = ofc.eval_expression, + eval_module = ofc.eval_module) end if args === () return obs @@ -1871,6 +1895,7 @@ function linearization_function(sys::AbstractSystem, inputs, p = DiffEqBase.NullParameters(), zero_dummy_der = false, initialization_solver_alg = TrustRegion(), + eval_expression = false, eval_module = @__MODULE__, kwargs...) inputs isa AbstractVector || (inputs = [inputs]) outputs isa AbstractVector || (outputs = [outputs]) @@ -1895,65 +1920,36 @@ function linearization_function(sys::AbstractSystem, inputs, end x0 = merge(defaults_and_guesses(sys), op) if has_index_cache(sys) && get_index_cache(sys) !== nothing - sys_ps = MTKParameters(sys, p, x0) + sys_ps = MTKParameters(sys, p, x0; eval_expression, eval_module) else sys_ps = varmap_to_vars(p, parameters(sys); defaults = x0) end p[get_iv(sys)] = NaN if has_index_cache(initsys) && get_index_cache(initsys) !== nothing - oldps = MTKParameters(initsys, p, merge(guesses(sys), defaults(sys), op)) + oldps = MTKParameters(initsys, p, merge(guesses(sys), defaults(sys), op); + eval_expression, eval_module) initsys_ps = parameters(initsys) - initsys_idxs = [parameter_index(initsys, param) for param in initsys_ps] - tunable_ps = [initsys_ps[i] - for i in eachindex(initsys_ps) - if initsys_idxs[i].portion == SciMLStructures.Tunable()] - tunable_getter = isempty(tunable_ps) ? nothing : getu(sys, tunable_ps) - discrete_ps = [initsys_ps[i] - for i in eachindex(initsys_ps) - if initsys_idxs[i].portion == SciMLStructures.Discrete()] - disc_getter = isempty(discrete_ps) ? nothing : getu(sys, discrete_ps) - constant_ps = [initsys_ps[i] - for i in eachindex(initsys_ps) - if initsys_idxs[i].portion == SciMLStructures.Constants()] - const_getter = isempty(constant_ps) ? nothing : getu(sys, constant_ps) - nonnum_ps = [initsys_ps[i] - for i in eachindex(initsys_ps) - if initsys_idxs[i].portion == NONNUMERIC_PORTION] - nonnum_getter = isempty(nonnum_ps) ? nothing : getu(sys, nonnum_ps) + p_getter = build_explicit_observed_function( + sys, initsys_ps; eval_expression, eval_module) + u_getter = isempty(unknowns(initsys)) ? (_...) -> nothing : - getu(sys, unknowns(initsys)) - get_initprob_u_p = let tunable_getter = tunable_getter, - disc_getter = disc_getter, - const_getter = const_getter, - nonnum_getter = nonnum_getter, - oldps = oldps, + build_explicit_observed_function( + sys, unknowns(initsys); eval_expression, eval_module) + get_initprob_u_p = let p_getter, + p_setter! = setp(initsys, initsys_ps), u_getter = u_getter function (u, p, t) state = ProblemState(; u, p, t) - if tunable_getter !== nothing - SciMLStructures.replace!( - SciMLStructures.Tunable(), oldps, tunable_getter(state)) - end - if disc_getter !== nothing - SciMLStructures.replace!( - SciMLStructures.Discrete(), oldps, disc_getter(state)) - end - if const_getter !== nothing - SciMLStructures.replace!( - SciMLStructures.Constants(), oldps, const_getter(state)) - end - if nonnum_getter !== nothing - SciMLStructures.replace!( - NONNUMERIC_PORTION, oldps, nonnum_getter(state)) - end + p_setter!(oldps, p_getter(state)) newu = u_getter(state) return newu, oldps end end else get_initprob_u_p = let p_getter = getu(sys, parameters(initsys)), - u_getter = getu(sys, unknowns(initsys)) + u_getter = build_explicit_observed_function( + sys, unknowns(initsys); eval_expression, eval_module) function (u, p, t) state = ProblemState(; u, p, t) @@ -1961,19 +1957,21 @@ function linearization_function(sys::AbstractSystem, inputs, end end end - initfn = NonlinearFunction(initsys) - initprobmap = getu(initsys, unknowns(sys)) + initfn = NonlinearFunction(initsys; eval_expression, eval_module) + initprobmap = build_explicit_observed_function( + initsys, unknowns(sys); eval_expression, eval_module) ps = full_parameters(sys) + h = build_explicit_observed_function(sys, outputs; eval_expression, eval_module) lin_fun = let diff_idxs = diff_idxs, alge_idxs = alge_idxs, input_idxs = input_idxs, sts = unknowns(sys), get_initprob_u_p = get_initprob_u_p, fun = ODEFunction{true, SciMLBase.FullSpecialize}( - sys, unknowns(sys), ps), + sys, unknowns(sys), ps; eval_expression, eval_module), initfn = initfn, initprobmap = initprobmap, - h = build_explicit_observed_function(sys, outputs), + h = h, chunk = ForwardDiff.Chunk(input_idxs), sys_ps = sys_ps, initialize = initialize, @@ -2056,6 +2054,7 @@ where `x` are differential unknown variables, `z` algebraic variables, `u` input """ 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, @@ -2065,10 +2064,11 @@ function linearize_symbolic(sys::AbstractSystem, inputs, ps = full_parameters(sys) p = reorder_parameters(sys, ps) - fun = generate_function(sys, sts, ps; expression = Val{false})[1] + fun_expr = generate_function(sys, sts, ps; expression = Val{true})[1] + fun = eval_or_rgf(fun_expr; eval_expression, eval_module) dx = fun(sts, p..., t) - h = build_explicit_observed_function(sys, outputs) + h = build_explicit_observed_function(sys, outputs; eval_expression, eval_module) y = h(sts, p..., t) fg_xz = Symbolics.jacobian(dx, sts) diff --git a/src/systems/callbacks.jl b/src/systems/callbacks.jl index 634cf6a01b..3fe1f7f006 100644 --- a/src/systems/callbacks.jl +++ b/src/systems/callbacks.jl @@ -343,7 +343,7 @@ Notes - `kwargs` are passed through to `Symbolics.build_function`. """ function compile_condition(cb::SymbolicDiscreteCallback, sys, dvs, ps; - expression = Val{true}, kwargs...) + 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)) t = get_iv(sys) @@ -353,8 +353,13 @@ function compile_condition(cb::SymbolicDiscreteCallback, sys, dvs, ps; cmap = map(x -> x => getdefault(x), cs) condit = substitute(condit, cmap) end - build_function(condit, u, t, p...; expression, wrap_code = condition_header(sys), + expr = build_function( + condit, u, t, p...; expression = Val{true}, 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...) @@ -379,7 +384,8 @@ Notes - `kwargs` are passed through to `Symbolics.build_function`. """ function compile_affect(eqs::Vector{Equation}, sys, dvs, ps; outputidxs = nothing, - expression = Val{true}, checkvars = true, + expression = Val{true}, checkvars = true, eval_expression = false, + eval_module = @__MODULE__, postprocess_affect_expr! = nothing, kwargs...) if isempty(eqs) if expression == Val{true} @@ -432,9 +438,8 @@ function compile_affect(eqs::Vector{Equation}, sys, dvs, ps; outputidxs = nothin end t = get_iv(sys) integ = gensym(:MTKIntegrator) - getexpr = (postprocess_affect_expr! === nothing) ? expression : Val{true} pre = get_preprocess_constants(rhss) - rf_oop, rf_ip = build_function(rhss, u, p..., t; expression = getexpr, + rf_oop, rf_ip = build_function(rhss, u, p..., t; expression = Val{true}, wrap_code = add_integrator_header(sys, integ, outvar), outputidxs = update_inds, postprocess_fbody = pre, @@ -442,10 +447,11 @@ function compile_affect(eqs::Vector{Equation}, sys, dvs, ps; outputidxs = nothin # applied user-provided function to the generated expression if postprocess_affect_expr! !== nothing postprocess_affect_expr!(rf_ip, integ) - (expression == Val{false}) && - (return drop_expr(@RuntimeGeneratedFunction(rf_ip))) end - rf_ip + if expression == Val{false} + return eval_or_rgf(rf_ip; eval_expression, eval_module) + end + return rf_ip end end diff --git a/src/systems/connectors.jl b/src/systems/connectors.jl index c62371928c..227b4624bf 100644 --- a/src/systems/connectors.jl +++ b/src/systems/connectors.jl @@ -408,7 +408,7 @@ function Base.merge(csets::AbstractVector{<:ConnectionSet}, allouter = false) id2set = Dict{Int, Int}() merged_set = ConnectionSet[] for (id, ele) in enumerate(idx2ele) - rid = find_root(union_find, id) + rid = find_root!(union_find, id) set_idx = get!(id2set, rid) do set = ConnectionSet() push!(merged_set, set) diff --git a/src/systems/diffeqs/abstractodesystem.jl b/src/systems/diffeqs/abstractodesystem.jl index e62d6a77fe..74eb4c2809 100644 --- a/src/systems/diffeqs/abstractodesystem.jl +++ b/src/systems/diffeqs/abstractodesystem.jl @@ -330,8 +330,7 @@ function DiffEqBase.ODEFunction{iip, specialize}(sys::AbstractODESystem, f_gen = generate_function(sys, dvs, ps; expression = Val{true}, expression_module = eval_module, checkbounds = checkbounds, kwargs...) - f_oop, f_iip = eval_expression ? eval_module.eval.(f_gen) : - (drop_expr(RuntimeGeneratedFunction(eval_module, eval_module, ex)) for ex in f_gen) + f_oop, f_iip = eval_or_rgf.(f_gen; eval_expression, eval_module) f(u, p, t) = f_oop(u, p, t) f(du, u, p, t) = f_iip(du, u, p, t) @@ -355,9 +354,8 @@ function DiffEqBase.ODEFunction{iip, specialize}(sys::AbstractODESystem, expression = Val{true}, expression_module = eval_module, checkbounds = checkbounds, kwargs...) - tgrad_oop, tgrad_iip = eval_expression ? eval_module.eval.(tgrad_gen) : - (drop_expr(RuntimeGeneratedFunction( - eval_module, eval_module, ex)) for ex in tgrad_gen) + tgrad_oop, tgrad_iip = eval_or_rgf.(tgrad_gen; eval_expression, eval_module) + if p isa Tuple __tgrad(u, p, t) = tgrad_oop(u, p..., t) __tgrad(J, u, p, t) = tgrad_iip(J, u, p..., t) @@ -377,9 +375,7 @@ function DiffEqBase.ODEFunction{iip, specialize}(sys::AbstractODESystem, expression = Val{true}, expression_module = eval_module, checkbounds = checkbounds, kwargs...) - jac_oop, jac_iip = eval_expression ? eval_module.eval.(jac_gen) : - (drop_expr(RuntimeGeneratedFunction( - eval_module, eval_module, ex)) for ex in jac_gen) + jac_oop, jac_iip = eval_or_rgf.(jac_gen; eval_expression, eval_module) _jac(u, p, t) = jac_oop(u, p, t) _jac(J, u, p, t) = jac_iip(J, u, p, t) @@ -408,7 +404,8 @@ function DiffEqBase.ODEFunction{iip, specialize}(sys::AbstractODESystem, let sys = sys, dict = Dict() function generated_observed(obsvar, args...) obs = get!(dict, value(obsvar)) do - SymbolicIndexingInterface.observed(sys, obsvar) + SymbolicIndexingInterface.observed( + sys, obsvar; eval_expression, eval_module) end if args === () return let obs = obs @@ -423,7 +420,7 @@ function DiffEqBase.ODEFunction{iip, specialize}(sys::AbstractODESystem, end end else - ObservedFunctionCache(sys) + ObservedFunctionCache(sys; eval_expression, eval_module) end jac_prototype = if sparse @@ -489,8 +486,7 @@ function DiffEqBase.DAEFunction{iip}(sys::AbstractODESystem, dvs = unknowns(sys) expression = Val{true}, expression_module = eval_module, checkbounds = checkbounds, kwargs...) - f_oop, f_iip = eval_expression ? eval_module.eval.(f_gen) : - (drop_expr(RuntimeGeneratedFunction(eval_module, eval_module, ex)) for ex in f_gen) + f_oop, f_iip = eval_or_rgf.(f_gen; eval_expression, eval_module) f(du, u, p, t) = f_oop(du, u, p, t) f(du, u, p::MTKParameters, t) = f_oop(du, u, p..., t) f(out, du, u, p, t) = f_iip(out, du, u, p, t) @@ -502,9 +498,7 @@ function DiffEqBase.DAEFunction{iip}(sys::AbstractODESystem, dvs = unknowns(sys) expression = Val{true}, expression_module = eval_module, checkbounds = checkbounds, kwargs...) - jac_oop, jac_iip = eval_expression ? eval_module.eval.(jac_gen) : - (drop_expr(RuntimeGeneratedFunction( - eval_module, eval_module, ex)) for ex in jac_gen) + jac_oop, jac_iip = eval_or_rgf.(jac_gen; eval_expression, eval_module) _jac(du, u, p, ˍ₋gamma, t) = jac_oop(du, u, p, ˍ₋gamma, t) _jac(du, u, p::MTKParameters, ˍ₋gamma, t) = jac_oop(du, u, p..., ˍ₋gamma, t) @@ -515,7 +509,7 @@ function DiffEqBase.DAEFunction{iip}(sys::AbstractODESystem, dvs = unknowns(sys) _jac = nothing end - observedfun = ObservedFunctionCache(sys) + observedfun = ObservedFunctionCache(sys; eval_expression, eval_module) jac_prototype = if sparse uElType = u0 === nothing ? Float64 : eltype(u0) @@ -546,6 +540,7 @@ end function DiffEqBase.DDEFunction{iip}(sys::AbstractODESystem, dvs = unknowns(sys), ps = parameters(sys), u0 = nothing; + eval_expression = false, eval_module = @__MODULE__, checkbounds = false, kwargs...) where {iip} @@ -556,7 +551,7 @@ function DiffEqBase.DDEFunction{iip}(sys::AbstractODESystem, dvs = unknowns(sys) expression = Val{true}, expression_module = eval_module, checkbounds = checkbounds, kwargs...) - f_oop, f_iip = (drop_expr(RuntimeGeneratedFunction(eval_module, eval_module, ex)) for ex in f_gen) + f_oop, f_iip = eval_or_rgf.(f_gen; eval_expression, eval_module) f(u, h, p, t) = f_oop(u, h, p, t) f(u, h, p::MTKParameters, t) = f_oop(u, h, p..., t) f(du, u, h, p, t) = f_iip(du, u, h, p, t) @@ -571,6 +566,7 @@ end function DiffEqBase.SDDEFunction{iip}(sys::AbstractODESystem, dvs = unknowns(sys), ps = parameters(sys), u0 = nothing; + eval_expression = false, eval_module = @__MODULE__, checkbounds = false, kwargs...) where {iip} @@ -581,10 +577,10 @@ function DiffEqBase.SDDEFunction{iip}(sys::AbstractODESystem, dvs = unknowns(sys expression = Val{true}, expression_module = eval_module, checkbounds = checkbounds, kwargs...) - f_oop, f_iip = (drop_expr(RuntimeGeneratedFunction(eval_module, eval_module, ex)) for ex in f_gen) + f_oop, f_iip = eval_or_rgf.(f_gen; eval_expression, eval_module) g_gen = generate_diffusion_function(sys, dvs, ps; expression = Val{true}, isdde = true, kwargs...) - g_oop, g_iip = (drop_expr(@RuntimeGeneratedFunction(ex)) for ex in g_gen) + g_oop, g_iip = eval_or_rgf.(g_gen; eval_expression, eval_module) f(u, h, p, t) = f_oop(u, h, p, t) f(u, h, p::MTKParameters, t) = f_oop(u, h, p..., t) f(du, u, h, p, t) = f_iip(du, u, h, p, t) @@ -772,6 +768,7 @@ function process_DEProblem(constructor, sys::AbstractODESystem, u0map, parammap; simplify = false, linenumbers = true, parallel = SerialForm(), eval_expression = false, + eval_module = @__MODULE__, use_union = true, tofloat = true, symbolic_u0 = false, @@ -853,7 +850,8 @@ function process_DEProblem(constructor, sys::AbstractODESystem, u0map, parammap; u0map = Dict() end initializeprob = ModelingToolkit.InitializationProblem( - sys, t, u0map, parammap; guesses, warn_initialize_determined, initialization_eqs) + sys, t, u0map, parammap; guesses, warn_initialize_determined, + initialization_eqs, eval_expression, eval_module) initializeprobmap = getu(initializeprob, unknowns(sys)) zerovars = Dict(setdiff(unknowns(sys), keys(defaults(sys))) .=> 0.0) @@ -873,7 +871,7 @@ function process_DEProblem(constructor, sys::AbstractODESystem, u0map, parammap; parammap == SciMLBase.NullParameters() && isempty(defs) nothing else - MTKParameters(sys, parammap, trueinit) + MTKParameters(sys, parammap, trueinit; eval_expression, eval_module) end else u0, p, defs = get_u0_p(sys, @@ -906,6 +904,7 @@ function process_DEProblem(constructor, sys::AbstractODESystem, u0map, parammap; checkbounds = checkbounds, p = p, linenumbers = linenumbers, parallel = parallel, simplify = simplify, sparse = sparse, eval_expression = eval_expression, + eval_module = eval_module, initializeprob = initializeprob, initializeprobmap = initializeprobmap, kwargs...) @@ -1012,17 +1011,20 @@ function DiffEqBase.ODEProblem{iip, specialize}(sys::AbstractODESystem, u0map = 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 f, u0, p = process_DEProblem(ODEFunction{iip, specialize}, sys, u0map, parammap; t = tspan !== nothing ? tspan[1] : tspan, - check_length, warn_initialize_determined, kwargs...) - cbs = process_events(sys; callback, kwargs...) + check_length, warn_initialize_determined, eval_expression, eval_module, kwargs...) + cbs = process_events(sys; callback, eval_expression, eval_module, kwargs...) inits = [] if has_discrete_subsystems(sys) && (dss = get_discrete_subsystems(sys)) !== nothing - affects, inits, clocks, svs = ModelingToolkit.generate_discrete_affect(sys, dss...) + affects, inits, clocks, svs = ModelingToolkit.generate_discrete_affect( + sys, dss...; eval_expression, eval_module) discrete_cbs = map(affects, clocks, svs) do affect, clock, sv if clock isa Clock PeriodicCallback(DiscreteSaveAffect(affect, sv), clock.dt; @@ -1107,9 +1109,9 @@ function DiffEqBase.DAEProblem{iip}(sys::AbstractODESystem, du0map, u0map, tspan kwargs...) end -function generate_history(sys::AbstractODESystem, u0; kwargs...) +function generate_history(sys::AbstractODESystem, u0; expression = Val{false}, kwargs...) p = reorder_parameters(sys, full_parameters(sys)) - build_function(u0, p..., get_iv(sys); expression = Val{false}, kwargs...) + build_function(u0, p..., get_iv(sys); expression, kwargs...) end function DiffEqBase.DDEProblem(sys::AbstractODESystem, args...; kwargs...) @@ -1120,6 +1122,8 @@ function DiffEqBase.DDEProblem{iip}(sys::AbstractODESystem, u0map = [], parammap = DiffEqBase.NullParameters(); callback = nothing, 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 `DDEProblem`") @@ -1127,14 +1131,16 @@ function DiffEqBase.DDEProblem{iip}(sys::AbstractODESystem, u0map = [], f, u0, p = process_DEProblem(DDEFunction{iip}, sys, u0map, parammap; t = tspan !== nothing ? tspan[1] : tspan, symbolic_u0 = true, - check_length, kwargs...) - h_oop, h_iip = generate_history(sys, u0) + check_length, eval_expression, eval_module, kwargs...) + h_gen = generate_history(sys, u0; expression = Val{true}) + h_oop, h_iip = eval_or_rgf.(h_gen; eval_expression, eval_module) h(p, t) = h_oop(p, t) h(p::MTKParameters, t) = h_oop(p..., t) u0 = h(p, tspan[1]) - cbs = process_events(sys; callback, kwargs...) + cbs = process_events(sys; callback, eval_expression, eval_module, kwargs...) if has_discrete_subsystems(sys) && (dss = get_discrete_subsystems(sys)) !== nothing - affects, clocks, svs = ModelingToolkit.generate_discrete_affect(sys, dss...) + affects, clocks, svs = ModelingToolkit.generate_discrete_affect( + sys, dss...; eval_expression, eval_module) discrete_cbs = map(affects, clocks, svs) do affect, clock, sv if clock isa Clock PeriodicCallback(DiscreteSaveAffect(affect, sv), clock.dt; @@ -1176,23 +1182,27 @@ function DiffEqBase.SDDEProblem{iip}(sys::AbstractODESystem, u0map = [], callback = nothing, check_length = true, sparsenoise = nothing, + 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 `SDDEProblem`") end f, u0, p = process_DEProblem(SDDEFunction{iip}, sys, u0map, parammap; t = tspan !== nothing ? tspan[1] : tspan, - symbolic_u0 = true, + symbolic_u0 = true, eval_expression, eval_module, check_length, kwargs...) - h_oop, h_iip = generate_history(sys, u0) + h_gen = generate_history(sys, u0; expression = Val{true}) + h_oop, h_iip = eval_or_rgf.(h_gen; eval_expression, eval_module) h(out, p, t) = h_iip(out, p, t) h(p, t) = h_oop(p, t) h(p::MTKParameters, t) = h_oop(p..., t) h(out, p::MTKParameters, t) = h_iip(out, p..., t) u0 = h(p, tspan[1]) - cbs = process_events(sys; callback, kwargs...) + cbs = process_events(sys; callback, eval_expression, eval_module, kwargs...) if has_discrete_subsystems(sys) && (dss = get_discrete_subsystems(sys)) !== nothing - affects, clocks, svs = ModelingToolkit.generate_discrete_affect(sys, dss...) + affects, clocks, svs = ModelingToolkit.generate_discrete_affect( + sys, dss...; eval_expression, eval_module) discrete_cbs = map(affects, clocks, svs) do affect, clock, sv if clock isa Clock PeriodicCallback(DiscreteSaveAffect(affect, sv), clock.dt; @@ -1577,8 +1587,8 @@ function InitializationProblem{iip, specialize}(sys::AbstractODESystem, u0map = merge(todict(guesses), todict(u0map)) if neqs == nunknown - NonlinearProblem(isys, u0map, parammap) + NonlinearProblem(isys, u0map, parammap; kwargs...) else - NonlinearLeastSquaresProblem(isys, u0map, parammap) + NonlinearLeastSquaresProblem(isys, u0map, parammap; kwargs...) end end diff --git a/src/systems/diffeqs/odesystem.jl b/src/systems/diffeqs/odesystem.jl index 619a896810..b0bb9dd9a4 100644 --- a/src/systems/diffeqs/odesystem.jl +++ b/src/systems/diffeqs/odesystem.jl @@ -378,6 +378,8 @@ i.e. there are no cycles. function build_explicit_observed_function(sys, ts; inputs = nothing, expression = false, + eval_expression = false, + eval_module = @__MODULE__, output_type = Array, checkbounds = true, drop_expr = drop_expr, @@ -495,14 +497,17 @@ function build_explicit_observed_function(sys, ts; pre(Let(obsexprs, isscalar ? ts[1] : MakeArray(ts, output_type), false))) |> wrap_array_vars(sys, ts)[1] |> toexpr - oop_fn = expression ? oop_fn : drop_expr(@RuntimeGeneratedFunction(oop_fn)) + oop_fn = expression ? oop_fn : eval_or_rgf(oop_fn; eval_expression, eval_module) if !isscalar iip_fn = build_function(ts, args...; postprocess_fbody = pre, wrap_code = wrap_array_vars(sys, ts) .∘ wrap_assignments(isscalar, obsexprs), - expression = Val{expression})[2] + expression = Val{true})[2] + if !expression + iip_fn = eval_or_rgf(iip_fn; eval_expression, eval_module) + end end if isscalar || !return_inplace return oop_fn diff --git a/src/systems/diffeqs/sdesystem.jl b/src/systems/diffeqs/sdesystem.jl index a81a6694e7..09bea2d152 100644 --- a/src/systems/diffeqs/sdesystem.jl +++ b/src/systems/diffeqs/sdesystem.jl @@ -408,6 +408,7 @@ function DiffEqBase.SDEFunction{iip}(sys::SDESystem, dvs = unknowns(sys), u0 = nothing; version = nothing, tgrad = false, sparse = false, jac = false, Wfact = false, eval_expression = false, + eval_module = @__MODULE__, checkbounds = false, kwargs...) where {iip} if !iscomplete(sys) @@ -416,12 +417,10 @@ function DiffEqBase.SDEFunction{iip}(sys::SDESystem, dvs = unknowns(sys), dvs = scalarize.(dvs) f_gen = generate_function(sys, dvs, ps; expression = Val{true}, kwargs...) - f_oop, f_iip = eval_expression ? eval_module.eval.(f_gen) : - (drop_expr(@RuntimeGeneratedFunction(ex)) for ex in f_gen) + f_oop, f_iip = eval_or_rgf.(f_gen; eval_expression, eval_module) g_gen = generate_diffusion_function(sys, dvs, ps; expression = Val{true}, kwargs...) - g_oop, g_iip = eval_expression ? eval_module.eval.(g_gen) : - (drop_expr(@RuntimeGeneratedFunction(ex)) for ex in g_gen) + g_oop, g_iip = eval_or_rgf.(g_gen; eval_expression, eval_module) f(u, p, t) = f_oop(u, p, t) f(u, p::MTKParameters, t) = f_oop(u, p..., t) @@ -435,8 +434,7 @@ function DiffEqBase.SDEFunction{iip}(sys::SDESystem, dvs = unknowns(sys), if tgrad tgrad_gen = generate_tgrad(sys, dvs, ps; expression = Val{true}, kwargs...) - tgrad_oop, tgrad_iip = eval_expression ? eval_module.eval.(tgrad_gen) : - (drop_expr(@RuntimeGeneratedFunction(ex)) for ex in tgrad_gen) + tgrad_oop, tgrad_iip = eval_or_rgf.(tgrad_gen; eval_expression, eval_module) _tgrad(u, p, t) = tgrad_oop(u, p, t) _tgrad(u, p::MTKParameters, t) = tgrad_oop(u, p..., t) @@ -449,8 +447,7 @@ function DiffEqBase.SDEFunction{iip}(sys::SDESystem, dvs = unknowns(sys), if jac jac_gen = generate_jacobian(sys, dvs, ps; expression = Val{true}, sparse = sparse, kwargs...) - jac_oop, jac_iip = eval_expression ? eval_module.eval.(jac_gen) : - (drop_expr(@RuntimeGeneratedFunction(ex)) for ex in jac_gen) + jac_oop, jac_iip = eval_or_rgf.(jac_gen; eval_expression, eval_module) _jac(u, p, t) = jac_oop(u, p, t) _jac(u, p::MTKParameters, t) = jac_oop(u, p..., t) @@ -463,10 +460,8 @@ function DiffEqBase.SDEFunction{iip}(sys::SDESystem, dvs = unknowns(sys), if Wfact tmp_Wfact, tmp_Wfact_t = generate_factorized_W(sys, dvs, ps, true; expression = Val{true}, kwargs...) - Wfact_oop, Wfact_iip = eval_expression ? eval_module.eval.(tmp_Wfact) : - (drop_expr(@RuntimeGeneratedFunction(ex)) for ex in tmp_Wfact) - Wfact_oop_t, Wfact_iip_t = eval_expression ? eval_module.eval.(tmp_Wfact_t) : - (drop_expr(@RuntimeGeneratedFunction(ex)) for ex in tmp_Wfact_t) + 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(u, p, dtgamma, t) = Wfact_oop(u, p, dtgamma, t) _Wfact(u, p::MTKParameters, dtgamma, t) = Wfact_oop(u, p..., dtgamma, t) @@ -483,7 +478,7 @@ function DiffEqBase.SDEFunction{iip}(sys::SDESystem, dvs = unknowns(sys), M = calculate_massmatrix(sys) _M = (u0 === nothing || M == I) ? M : ArrayInterface.restructure(u0 .* u0', M) - observedfun = ObservedFunctionCache(sys) + observedfun = ObservedFunctionCache(sys; eval_expression, eval_module) SDEFunction{iip}(f, g, sys = sys, @@ -597,7 +592,7 @@ function DiffEqBase.SDEProblem{iip}(sys::SDESystem, u0map = [], tspan = get_tspa end f, u0, p = process_DEProblem(SDEFunction{iip}, sys, u0map, parammap; check_length, kwargs...) - cbs = process_events(sys; callback) + cbs = process_events(sys; callback, kwargs...) sparsenoise === nothing && (sparsenoise = get(kwargs, :sparse, false)) noiseeqs = get_noiseeqs(sys) diff --git a/src/systems/discrete_system/discrete_system.jl b/src/systems/discrete_system/discrete_system.jl index d19c93435d..1f8c1796e2 100644 --- a/src/systems/discrete_system/discrete_system.jl +++ b/src/systems/discrete_system/discrete_system.jl @@ -222,9 +222,9 @@ end function process_DiscreteProblem(constructor, sys::DiscreteSystem, u0map, parammap; linenumbers = true, parallel = SerialForm(), - eval_expression = false, use_union = false, tofloat = !use_union, + eval_expression = false, eval_module = @__MODULE__, kwargs...) iv = get_iv(sys) eqs = equations(sys) @@ -249,7 +249,7 @@ function process_DiscreteProblem(constructor, sys::DiscreteSystem, u0map, paramm end if has_index_cache(sys) && get_index_cache(sys) !== nothing u0, defs = get_u0(sys, trueu0map, parammap) - p = MTKParameters(sys, parammap, trueu0map) + p = MTKParameters(sys, parammap, trueu0map; eval_expression, eval_module) else u0, p, defs = get_u0_p(sys, trueu0map, parammap; tofloat, use_union) end @@ -259,7 +259,8 @@ function process_DiscreteProblem(constructor, sys::DiscreteSystem, u0map, paramm f = constructor(sys, dvs, ps, u0; linenumbers = linenumbers, parallel = parallel, syms = Symbol.(dvs), paramsyms = Symbol.(ps), - eval_expression = eval_expression, kwargs...) + eval_expression = eval_expression, eval_module = eval_module, + kwargs...) return f, u0, p end @@ -317,8 +318,7 @@ function SciMLBase.DiscreteFunction{iip, specialize}( end f_gen = generate_function(sys, dvs, ps; expression = Val{true}, expression_module = eval_module, kwargs...) - f_oop, f_iip = eval_expression ? eval_module.eval.(f_gen) : - (drop_expr(RuntimeGeneratedFunction(eval_module, eval_module, ex)) for ex in f_gen) + f_oop, f_iip = eval_or_rgf.(f_gen; eval_expression, eval_module) f(u, p, t) = f_oop(u, p, t) f(du, u, p, t) = f_iip(du, u, p, t) diff --git a/src/systems/jumps/jumpsystem.jl b/src/systems/jumps/jumpsystem.jl index accc0bc39f..4da6ce710c 100644 --- a/src/systems/jumps/jumpsystem.jl +++ b/src/systems/jumps/jumpsystem.jl @@ -213,15 +213,17 @@ function generate_affect_function(js::JumpSystem, affect, outputidxs) expression = Val{true}, checkvars = false) end -function assemble_vrj(js, vrj, unknowntoid) - _rate = drop_expr(@RuntimeGeneratedFunction(generate_rate_function(js, vrj.rate))) +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(u, p, t) = _rate(u, p, t) rate(u, p::MTKParameters, t) = _rate(u, p..., t) outputvars = (value(affect.lhs) for affect in vrj.affect!) outputidxs = [unknowntoid[var] for var in outputvars] - affect = drop_expr(@RuntimeGeneratedFunction(generate_affect_function(js, vrj.affect!, - outputidxs))) + affect = eval_or_rgf( + generate_affect_function(js, vrj.affect!, + outputidxs); eval_expression, eval_module) VariableRateJump(rate, affect) end @@ -240,15 +242,17 @@ function assemble_vrj_expr(js, vrj, unknowntoid) end end -function assemble_crj(js, crj, unknowntoid) - _rate = drop_expr(@RuntimeGeneratedFunction(generate_rate_function(js, crj.rate))) +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(u, p, t) = _rate(u, p, t) rate(u, p::MTKParameters, t) = _rate(u, p..., t) outputvars = (value(affect.lhs) for affect in crj.affect!) outputidxs = [unknowntoid[var] for var in outputvars] - affect = drop_expr(@RuntimeGeneratedFunction(generate_affect_function(js, crj.affect!, - outputidxs))) + affect = eval_or_rgf( + generate_affect_function(js, crj.affect!, + outputidxs); eval_expression, eval_module) ConstantRateJump(rate, affect) end @@ -325,6 +329,8 @@ function DiffEqBase.DiscreteProblem(sys::JumpSystem, u0map, tspan::Union{Tuple, parammap = DiffEqBase.NullParameters(); checkbounds = false, use_union = true, + eval_expression = false, + eval_module = @__MODULE__, kwargs...) if !iscomplete(sys) error("A completed `JumpSystem` is required. Call `complete` or `structural_simplify` on the system before creating a `DiscreteProblem`") @@ -338,14 +344,14 @@ function DiffEqBase.DiscreteProblem(sys::JumpSystem, u0map, tspan::Union{Tuple, 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) + p = MTKParameters(sys, parammap, u0map; eval_expression, eval_module) else p = varmap_to_vars(parammap, ps; defaults = defs, tofloat = false, use_union) end f = DiffEqBase.DISCRETE_INPLACE_DEFAULT - observedfun = ObservedFunctionCache(sys) + observedfun = ObservedFunctionCache(sys; eval_expression, eval_module) df = DiscreteFunction{true, true}(f; sys = sys, observed = observedfun) DiscreteProblem(df, u0, tspan, p; kwargs...) @@ -417,7 +423,7 @@ sol = solve(jprob, SSAStepper()) ``` """ function JumpProcesses.JumpProblem(js::JumpSystem, prob, aggregator; callback = nothing, - kwargs...) + 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 @@ -430,8 +436,10 @@ function JumpProcesses.JumpProblem(js::JumpSystem, prob, aggregator; callback = 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) for j in eqs.x[2]] - vrjs = VariableRateJump[assemble_vrj(js, j, unknowntoid) for j in eqs.x[3]] + 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]] ((prob isa DiscreteProblem) && !isempty(vrjs)) && error("Use continuous problems such as an ODEProblem or a SDEProblem with VariableRateJumps") jset = JumpSet(Tuple(vrjs), Tuple(crjs), nothing, majs) @@ -450,7 +458,8 @@ function JumpProcesses.JumpProblem(js::JumpSystem, prob, aggregator; callback = end # handle events, making sure to reset aggregators in the generated affect functions - cbs = process_events(js; callback, postprocess_affect_expr! = _reset_aggregator!) + cbs = process_events(js; callback, eval_expression, eval_module, + postprocess_affect_expr! = _reset_aggregator!) JumpProblem(prob, aggregator, jset; dep_graph = jtoj, vartojumps_map = vtoj, jumptovars_map = jtov, scale_rates = false, nocopy = true, diff --git a/src/systems/nonlinear/nonlinearsystem.jl b/src/systems/nonlinear/nonlinearsystem.jl index 00f526028d..db13bb4541 100644 --- a/src/systems/nonlinear/nonlinearsystem.jl +++ b/src/systems/nonlinear/nonlinearsystem.jl @@ -278,14 +278,14 @@ function SciMLBase.NonlinearFunction{iip}(sys::NonlinearSystem, dvs = unknowns(s version = nothing, jac = false, eval_expression = false, + eval_module = @__MODULE__, 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 `NonlinearFunction`") end f_gen = generate_function(sys, dvs, ps; expression = Val{true}, kwargs...) - f_oop, f_iip = eval_expression ? eval_module.eval.(f_gen) : - (drop_expr(@RuntimeGeneratedFunction(ex)) for ex in f_gen) + f_oop, f_iip = eval_or_rgf.(f_gen; eval_expression, eval_module) f(u, p) = f_oop(u, p) f(u, p::MTKParameters) = f_oop(u, p...) f(du, u, p) = f_iip(du, u, p) @@ -295,8 +295,7 @@ function SciMLBase.NonlinearFunction{iip}(sys::NonlinearSystem, dvs = unknowns(s jac_gen = generate_jacobian(sys, dvs, ps; simplify = simplify, sparse = sparse, expression = Val{true}, kwargs...) - jac_oop, jac_iip = eval_expression ? eval_module.eval.(jac_gen) : - (drop_expr(@RuntimeGeneratedFunction(ex)) for ex in jac_gen) + jac_oop, jac_iip = eval_or_rgf.(jac_gen; eval_expression, eval_module) _jac(u, p) = jac_oop(u, p) _jac(u, p::MTKParameters) = jac_oop(u, p...) _jac(J, u, p) = jac_iip(J, u, p) @@ -305,7 +304,7 @@ function SciMLBase.NonlinearFunction{iip}(sys::NonlinearSystem, dvs = unknowns(s _jac = nothing end - observedfun = ObservedFunctionCache(sys) + observedfun = ObservedFunctionCache(sys; eval_expression, eval_module) NonlinearFunction{iip}(f, sys = sys, @@ -376,6 +375,7 @@ function process_NonlinearProblem(constructor, sys::NonlinearSystem, u0map, para simplify = false, linenumbers = true, parallel = SerialForm(), eval_expression = false, + eval_module = @__MODULE__, use_union = false, tofloat = !use_union, kwargs...) @@ -385,7 +385,7 @@ function process_NonlinearProblem(constructor, sys::NonlinearSystem, u0map, para if has_index_cache(sys) && get_index_cache(sys) !== nothing u0, defs = get_u0(sys, u0map, parammap) check_eqs_u0(eqs, dvs, u0; kwargs...) - p = MTKParameters(sys, parammap, u0map) + p = MTKParameters(sys, parammap, u0map; eval_expression, eval_module) else u0, p, defs = get_u0_p(sys, u0map, parammap; tofloat, use_union) check_eqs_u0(eqs, dvs, u0; kwargs...) @@ -393,7 +393,8 @@ function process_NonlinearProblem(constructor, sys::NonlinearSystem, u0map, para f = constructor(sys, dvs, ps, u0; jac = jac, checkbounds = checkbounds, linenumbers = linenumbers, parallel = parallel, simplify = simplify, - sparse = sparse, eval_expression = eval_expression, kwargs...) + sparse = sparse, eval_expression = eval_expression, eval_module = eval_module, + kwargs...) return f, u0, p end diff --git a/src/systems/optimization/optimizationsystem.jl b/src/systems/optimization/optimizationsystem.jl index f017494b13..3a9f8aef74 100644 --- a/src/systems/optimization/optimizationsystem.jl +++ b/src/systems/optimization/optimizationsystem.jl @@ -240,6 +240,7 @@ function DiffEqBase.OptimizationProblem{iip}(sys::OptimizationSystem, u0map, cons_j = false, cons_h = false, cons_sparse = false, checkbounds = false, linenumbers = true, parallel = SerialForm(), + eval_expression = false, eval_module = @__MODULE__, use_union = false, kwargs...) where {iip} if !iscomplete(sys) @@ -280,7 +281,7 @@ function DiffEqBase.OptimizationProblem{iip}(sys::OptimizationSystem, u0map, if parammap isa MTKParameters p = parammap elseif has_index_cache(sys) && get_index_cache(sys) !== nothing - p = MTKParameters(sys, parammap, u0map) + p = MTKParameters(sys, parammap, u0map; eval_expression, eval_module) else p = varmap_to_vars(parammap, ps; defaults = defs, tofloat = false, use_union) end @@ -292,9 +293,12 @@ function DiffEqBase.OptimizationProblem{iip}(sys::OptimizationSystem, u0map, ub = nothing end - f = let _f = generate_function( - sys, checkbounds = checkbounds, linenumbers = linenumbers, - expression = Val{false}) + f = let _f = eval_or_rgf( + generate_function( + sys, checkbounds = checkbounds, linenumbers = linenumbers, + expression = Val{true}); + eval_expression, + eval_module) __f(u, p) = _f(u, p) __f(u, p::MTKParameters) = _f(u, p...) __f @@ -302,10 +306,13 @@ function DiffEqBase.OptimizationProblem{iip}(sys::OptimizationSystem, u0map, obj_expr = subs_constants(objective(sys)) if grad - _grad = let (grad_oop, grad_iip) = generate_gradient( - sys, checkbounds = checkbounds, - linenumbers = linenumbers, - parallel = parallel, expression = Val{false}) + _grad = let (grad_oop, grad_iip) = eval_or_rgf.( + generate_gradient( + sys, checkbounds = checkbounds, + linenumbers = linenumbers, + parallel = parallel, expression = Val{true}); + 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...) @@ -317,10 +324,14 @@ function DiffEqBase.OptimizationProblem{iip}(sys::OptimizationSystem, u0map, end if hess - _hess = let (hess_oop, hess_iip) = generate_hessian(sys, checkbounds = checkbounds, - linenumbers = linenumbers, - sparse = sparse, parallel = parallel, - expression = Val{false}) + _hess = let (hess_oop, hess_iip) = eval_or_rgf.( + generate_hessian( + sys, checkbounds = checkbounds, + linenumbers = linenumbers, + sparse = sparse, parallel = parallel, + expression = Val{true}); + 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...) @@ -337,20 +348,24 @@ function DiffEqBase.OptimizationProblem{iip}(sys::OptimizationSystem, u0map, hess_prototype = nothing end - observedfun = ObservedFunctionCache(sys) + observedfun = ObservedFunctionCache(sys; eval_expression, eval_module) if length(cstr) > 0 @named cons_sys = ConstraintsSystem(cstr, dvs, ps) cons_sys = complete(cons_sys) cons, lcons_, ucons_ = generate_function(cons_sys, checkbounds = checkbounds, linenumbers = linenumbers, - expression = Val{false}) + expression = Val{true}) + cons = eval_or_rgf.(cons; eval_expression, eval_module) if cons_j - _cons_j = let (cons_jac_oop, cons_jac_iip) = generate_jacobian(cons_sys; - checkbounds = checkbounds, - linenumbers = linenumbers, - parallel = parallel, expression = Val{false}, - sparse = cons_sparse) + _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); + 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...) @@ -361,11 +376,14 @@ function DiffEqBase.OptimizationProblem{iip}(sys::OptimizationSystem, u0map, _cons_j = nothing end if cons_h - _cons_h = let (cons_hess_oop, cons_hess_iip) = generate_hessian( - cons_sys, checkbounds = checkbounds, - linenumbers = linenumbers, - sparse = cons_sparse, parallel = parallel, - expression = Val{false}) + _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}); + 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...) @@ -458,6 +476,7 @@ function OptimizationProblemExpr{iip}(sys::OptimizationSystem, u0map, cons_j = false, cons_h = false, checkbounds = false, linenumbers = false, parallel = SerialForm(), + eval_expression = false, eval_module = @__MODULE__, use_union = false, kwargs...) where {iip} if !iscomplete(sys) @@ -496,7 +515,7 @@ function OptimizationProblemExpr{iip}(sys::OptimizationSystem, u0map, 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) + p = MTKParameters(sys, parammap, u0map; eval_expression, eval_module) else p = varmap_to_vars(parammap, ps; defaults = defs, tofloat = false, use_union) end @@ -512,17 +531,23 @@ function OptimizationProblemExpr{iip}(sys::OptimizationSystem, u0map, f = generate_function(sys, checkbounds = checkbounds, linenumbers = linenumbers, expression = Val{true}) if grad - _grad = generate_gradient( - sys, checkbounds = checkbounds, linenumbers = linenumbers, - parallel = parallel, expression = Val{false})[idx] + _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 = generate_hessian(sys, checkbounds = checkbounds, linenumbers = linenumbers, - sparse = sparse, parallel = parallel, - expression = Val{false})[idx] + _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 @@ -546,14 +571,19 @@ function OptimizationProblemExpr{iip}(sys::OptimizationSystem, u0map, @named cons_sys = ConstraintsSystem(cstr, dvs, ps) cons, lcons_, ucons_ = generate_function(cons_sys, checkbounds = checkbounds, linenumbers = linenumbers, - expression = Val{false}) + expression = Val{true}) + cons = eval_or_rgf(cons; eval_expression, eval_module) if cons_j - _cons_j = generate_jacobian(cons_sys; expression = Val{false}, sparse = sparse)[2] + _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 = generate_hessian(cons_sys; expression = Val{false}, sparse = sparse)[2] + _cons_h = eval_or_rgf( + generate_hessian(cons_sys; expression = Val{true}, sparse = sparse)[2]; + eval_expression, eval_module) else _cons_h = nothing end diff --git a/src/systems/parameter_buffer.jl b/src/systems/parameter_buffer.jl index d1f3aea02a..8a0792fdde 100644 --- a/src/systems/parameter_buffer.jl +++ b/src/systems/parameter_buffer.jl @@ -14,7 +14,8 @@ struct MTKParameters{T, D, C, E, N, F, G} end function MTKParameters( - sys::AbstractSystem, p, u0 = Dict(); tofloat = false, use_union = false) + sys::AbstractSystem, p, u0 = Dict(); tofloat = false, use_union = false, + eval_expression = false, eval_module = @__MODULE__) ic = if has_index_cache(sys) && get_index_cache(sys) !== nothing get_index_cache(sys) else @@ -170,12 +171,15 @@ function MTKParameters( dep_exprs.x[i][j] = unwrap(val) end dep_exprs = identity.(dep_exprs) - p = reorder_parameters(ic, full_parameters(sys)) - oop, iip = build_function(dep_exprs, p...) - update_function_iip, update_function_oop = RuntimeGeneratedFunctions.@RuntimeGeneratedFunction(iip), - RuntimeGeneratedFunctions.@RuntimeGeneratedFunction(oop) - update_function_iip(ArrayPartition(dep_buffer), tunable_buffer..., disc_buffer..., - const_buffer..., nonnumeric_buffer..., dep_buffer...) + psyms = reorder_parameters(ic, full_parameters(sys)) + update_fn_exprs = build_function(dep_exprs, psyms..., expression = Val{true}) + + update_function_oop, update_function_iip = eval_or_rgf.( + update_fn_exprs; eval_expression, eval_module) + ap_dep_buffer = ArrayPartition(dep_buffer) + for i in eachindex(dep_exprs) + ap_dep_buffer[i] = fixpoint_sub(dep_exprs[i], p) + end dep_buffer = narrow_buffer_type.(dep_buffer) else update_function_iip = update_function_oop = nothing diff --git a/src/utils.jl b/src/utils.jl index 58eacb38c5..dc239e34c1 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -825,3 +825,11 @@ function restrict_array_to_union(arr) end 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 From f1684ebd2050af1120731b577468f67f7b1e7fcf Mon Sep 17 00:00:00 2001 From: Christopher Rackauckas Date: Mon, 1 Jul 2024 01:38:12 -0400 Subject: [PATCH 226/316] Better error throwing on ill-formed SDESystems Throws a better error for https://github.com/SciML/ModelingToolkit.jl/issues/2829 --- .github/workflows/Downstream.yml | 2 +- src/systems/diffeqs/sdesystem.jl | 4 ++++ test/sdesystem.jl | 12 ++++++++++++ 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/.github/workflows/Downstream.yml b/.github/workflows/Downstream.yml index f5ae4b1df3..7a7556efa3 100644 --- a/.github/workflows/Downstream.yml +++ b/.github/workflows/Downstream.yml @@ -32,7 +32,7 @@ jobs: - {user: SciML, repo: NeuralPDE.jl, group: NNPDE} - {user: SciML, repo: DataDrivenDiffEq.jl, group: Downstream} - {user: SciML, repo: StructuralIdentifiability.jl, group: All} - - {user: SciML, repo: ModelingToolkitStandardLibrary.jl} + - {user: SciML, repo: ModelingToolkitStandardLibrary.jl, group: Core} - {user: SciML, repo: ModelOrderReduction.jl, group: All} - {user: SciML, repo: MethodOfLines.jl, group: Interface} - {user: SciML, repo: MethodOfLines.jl, group: 2D_Diffusion} diff --git a/src/systems/diffeqs/sdesystem.jl b/src/systems/diffeqs/sdesystem.jl index 223f1b3c0a..9e5d4224f3 100644 --- a/src/systems/diffeqs/sdesystem.jl +++ b/src/systems/diffeqs/sdesystem.jl @@ -140,6 +140,10 @@ struct SDESystem <: AbstractODESystem check_variables(dvs, iv) check_parameters(ps, iv) check_equations(deqs, iv) + check_equations(neqs, dvs) + if size(neqs,1) != length(dvs) + throw(ArgumentError("Noise equations ill-formed. Number of rows must match number of states. size(neqs,1) = $(size(neqs,1)) != length(dvs) = $(length(dvs))")) + end check_equations(equations(cevents), iv) end if checks == true || (checks & CheckUnits) > 0 diff --git a/test/sdesystem.jl b/test/sdesystem.jl index b18ab648e7..2678b9fb21 100644 --- a/test/sdesystem.jl +++ b/test/sdesystem.jl @@ -614,3 +614,15 @@ sys2 = complete(sys2) prob = SDEProblem(sys1, sts .=> [1.0, 0.0, 0.0], (0.0, 100.0), ps .=> (10.0, 26.0)) solve(prob, LambaEulerHeun(), seed = 1) + +# Test ill-formed due to more equations than states in noise equations + +@parameters p d +@variables t X(t) +D = Differential(t) +eqs = [D(X) ~ p - d*X] +noise_eqs = [sqrt(p), -sqrt(d*X)] +@test_throws ArgumentError ssys = 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) From 230eb16454835d5661c889842fb741c913604b6d Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Tue, 18 Jun 2024 15:14:44 +0000 Subject: [PATCH 227/316] feat: dump more variable metadata --- src/systems/abstractsystem.jl | 12 +++++++++++- src/variables.jl | 4 +++- test/test_variable_metadata.jl | 18 ++++++++++++++++++ 3 files changed, 32 insertions(+), 2 deletions(-) diff --git a/src/systems/abstractsystem.jl b/src/systems/abstractsystem.jl index 81950b8a26..d9853c8e58 100644 --- a/src/systems/abstractsystem.jl +++ b/src/systems/abstractsystem.jl @@ -2625,12 +2625,22 @@ See also: [`ModelingToolkit.dump_variable_metadata`](@ref), [`ModelingToolkit.du """ function dump_parameters(sys::AbstractSystem) defs = defaults(sys) - map(dump_variable_metadata.(parameters(sys))) do meta + pdeps = parameter_dependencies(sys) + metas = map(dump_variable_metadata.(parameters(sys))) do meta if haskey(defs, meta.var) meta = merge(meta, (; default = defs[meta.var])) end meta end + pdep_metas = map(collect(keys(pdeps))) do sym + val = pdeps[sym] + meta = dump_variable_metadata(sym) + meta = merge(meta, + (; dependency = pdeps[sym], + default = symbolic_evaluate(pdeps[sym], merge(defs, pdeps)))) + return meta + end + return vcat(metas, pdep_metas) end """ diff --git a/src/variables.jl b/src/variables.jl index fb3e321f0c..d9c5f71cab 100644 --- a/src/variables.jl +++ b/src/variables.jl @@ -47,6 +47,7 @@ function dump_variable_metadata(var) if desc == "" desc = nothing end + default = hasdefault(uvar) ? getdefault(uvar) : nothing guess = getguess(uvar) disturbance = isdisturbance(uvar) || nothing tunable = istunable(uvar, isparameter(uvar)) @@ -72,7 +73,8 @@ function dump_variable_metadata(var) disturbance, tunable, dist, - type + type, + default ) return NamedTuple(k => v for (k, v) in pairs(meta) if v !== nothing) diff --git a/test/test_variable_metadata.jl b/test/test_variable_metadata.jl index 22b436301f..c715814d4c 100644 --- a/test/test_variable_metadata.jl +++ b/test/test_variable_metadata.jl @@ -16,6 +16,12 @@ using ModelingToolkit @test hasguess(y) === true @test ModelingToolkit.dump_variable_metadata(y).guess == 0 +# Default +@variables y = 0 +@test ModelingToolkit.getdefault(y) === 0 +@test ModelingToolkit.hasdefault(y) === true +@test ModelingToolkit.dump_variable_metadata(y).default == 0 + # Issue#2653 @variables y[1:3] [guess = ones(3)] @test getguess(y) == ones(3) @@ -124,3 +130,15 @@ sp = Set(p) @named sys = ODESystem([u ~ p], t) @test_nowarn show(stdout, "text/plain", sys) + +# Defaults overridden by system, parameter dependencies +@variables x(t) = 1.0 +@parameters p=2.0 q +@named sys = ODESystem(Equation[], t, [x], [p]; defaults = Dict(x => 2.0, p => 3.0), + parameter_dependencies = [q => 2p]) +x_meta = ModelingToolkit.dump_unknowns(sys)[] +@test x_meta.default == 2.0 +params_meta = ModelingToolkit.dump_parameters(sys) +params_meta = Dict([ModelingToolkit.getname(meta.var) => meta for meta in params_meta]) +@test params_meta[:p].default == 3.0 +@test isequal(params_meta[:q].dependency, 2p) From fc321da33684813a250acbb217d3e7cfc5f55b7c Mon Sep 17 00:00:00 2001 From: Christopher Rackauckas Date: Mon, 1 Jul 2024 01:46:23 -0400 Subject: [PATCH 228/316] format --- src/systems/diffeqs/sdesystem.jl | 2 +- test/sdesystem.jl | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/systems/diffeqs/sdesystem.jl b/src/systems/diffeqs/sdesystem.jl index 9e5d4224f3..7eab3dcb1c 100644 --- a/src/systems/diffeqs/sdesystem.jl +++ b/src/systems/diffeqs/sdesystem.jl @@ -141,7 +141,7 @@ struct SDESystem <: AbstractODESystem check_parameters(ps, iv) check_equations(deqs, iv) check_equations(neqs, dvs) - if size(neqs,1) != length(dvs) + if size(neqs, 1) != length(dvs) throw(ArgumentError("Noise equations ill-formed. Number of rows must match number of states. size(neqs,1) = $(size(neqs,1)) != length(dvs) = $(length(dvs))")) end check_equations(equations(cevents), iv) diff --git a/test/sdesystem.jl b/test/sdesystem.jl index 2678b9fb21..14bfe4198a 100644 --- a/test/sdesystem.jl +++ b/test/sdesystem.jl @@ -620,9 +620,9 @@ solve(prob, LambaEulerHeun(), seed = 1) @parameters p d @variables t X(t) D = Differential(t) -eqs = [D(X) ~ p - d*X] -noise_eqs = [sqrt(p), -sqrt(d*X)] -@test_throws ArgumentError ssys = SDESystem(eqs, noise_eqs, t, [X], [p, d]; name = :ssys) +eqs = [D(X) ~ p - d * X] +noise_eqs = [sqrt(p), -sqrt(d * X)] +@test_throws ArgumentError ssys=SDESystem(eqs, noise_eqs, t, [X], [p, d]; name = :ssys) -noise_eqs = reshape([sqrt(p), -sqrt(d*X)],1,2) +noise_eqs = reshape([sqrt(p), -sqrt(d * X)], 1, 2) ssys = SDESystem(eqs, noise_eqs, t, [X], [p, d]; name = :ssys) From b03e284a4272bc83bfffde214be9203324beb08e Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Wed, 26 Jun 2024 13:29:43 +0530 Subject: [PATCH 229/316] fix: fix MTKParameters creation using defaults of parameters not in the system --- src/systems/parameter_buffer.jl | 4 +--- test/initial_values.jl | 17 +++++++++++++++++ 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/src/systems/parameter_buffer.jl b/src/systems/parameter_buffer.jl index 8a0792fdde..e491344fcd 100644 --- a/src/systems/parameter_buffer.jl +++ b/src/systems/parameter_buffer.jl @@ -32,9 +32,7 @@ function MTKParameters( p = Dict() end p = todict(p) - defs = Dict(default_toterm(unwrap(k)) => v - for (k, v) in defaults(sys) - if unwrap(k) in all_ps || default_toterm(unwrap(k)) in all_ps) + defs = Dict(default_toterm(unwrap(k)) => v for (k, v) in defaults(sys)) if eltype(u0) <: Pair u0 = todict(u0) elseif u0 isa AbstractArray && !isempty(u0) diff --git a/test/initial_values.jl b/test/initial_values.jl index 8f9607089b..3a7b48901d 100644 --- a/test/initial_values.jl +++ b/test/initial_values.jl @@ -74,3 +74,20 @@ target_varmap = Dict(p => ones(3), q => 2ones(3), q[1] => 2.0, q[2] => 2.0, q[3] eqs = [D(D(z)) ~ ones(2, 2)] @mtkbuild sys = ODESystem(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( + [ + x1 ~ B1, + x2 ~ B2 + ], t; defaults = [ + A2 => 1 - A1, + B1 => A1, + B2 => A2 + ]) +prob = ODEProblem(sys, [], (0.0, 1.0), [A1 => 0.3]) +@test prob.ps[B1] == 0.3 +@test prob.ps[B2] == 0.7 From 6ae1c2b476d1372b0b4fe5a5863f634b5c4f5b25 Mon Sep 17 00:00:00 2001 From: Christopher Rackauckas Date: Mon, 1 Jul 2024 04:11:51 -0400 Subject: [PATCH 230/316] fix tests --- test/sdesystem.jl | 3 +-- test/symbolic_events.jl | 16 ++++++++-------- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/test/sdesystem.jl b/test/sdesystem.jl index 14bfe4198a..30f92e039c 100644 --- a/test/sdesystem.jl +++ b/test/sdesystem.jl @@ -619,10 +619,9 @@ solve(prob, LambaEulerHeun(), seed = 1) @parameters p d @variables t X(t) -D = Differential(t) eqs = [D(X) ~ p - d * X] noise_eqs = [sqrt(p), -sqrt(d * X)] -@test_throws ArgumentError ssys=SDESystem(eqs, noise_eqs, t, [X], [p, d]; name = :ssys) +@test_throws ArgumentError 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) diff --git a/test/symbolic_events.jl b/test/symbolic_events.jl index b15769e630..88ea88b8b0 100644 --- a/test/symbolic_events.jl +++ b/test/symbolic_events.jl @@ -458,7 +458,7 @@ let ∂ₜ = D eqs = [∂ₜ(A) ~ -k * A] - @named ssys = SDESystem(eqs, Equation[], t, [A], [k, t1, t2], + @named ssys = SDESystem(eqs, Equation[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] @@ -468,7 +468,7 @@ let cond1a = (t == t1) affect1a = [A ~ A + 1, B ~ A] cb1a = cond1a => affect1a - @named ssys1 = SDESystem(eqs, Equation[], t, [A, B], [k, t1, t2], + @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( @@ -478,11 +478,11 @@ let # same as above - but with set-time event syntax cb1‵ = [1.0] => affect1 # needs to be a Vector for the event to happen only once cb2‵ = [2.0] => affect2 - @named ssys‵ = SDESystem(eqs, Equation[], 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) # mixing discrete affects - @named ssys3 = SDESystem(eqs, Equation[], t, [A], [k, t1, t2], + @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) @@ -492,16 +492,16 @@ let nothing end cb2‵‵ = [2.0] => (affect!, [], [k], [k], nothing) - @named ssys4 = SDESystem(eqs, Equation[], t, [A], [k, t1], + @named ssys4 = SDESystem(eqs, [0.0], t, [A], [k, t1], discrete_events = [cb1, cb2‵‵]) testsol(ssys4, u0, p, tspan; tstops = [1.0], paramtotest = k) # mixing with symbolic condition in the func affect cb2‵‵‵ = (t == t2) => (affect!, [], [k], [k], nothing) - @named ssys5 = SDESystem(eqs, Equation[], t, [A], [k, t1, t2], + @named ssys5 = SDESystem(eqs, [0.0], t, [A], [k, t1, t2], discrete_events = [cb1, cb2‵‵‵]) testsol(ssys5, u0, p, tspan; tstops = [1.0, 2.0], paramtotest = k) - @named ssys6 = SDESystem(eqs, Equation[], t, [A], [k, t1, t2], + @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) @@ -509,7 +509,7 @@ let cond3 = A ~ 0.1 affect3 = [k ~ 0.0] cb3 = cond3 => affect3 - @named ssys7 = SDESystem(eqs, Equation[], t, [A], [k, t1, t2], + @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]) From c421522ecf05c1d68c5edef573d2a9c759e0d5a5 Mon Sep 17 00:00:00 2001 From: Christopher Rackauckas Date: Mon, 1 Jul 2024 06:28:48 -0400 Subject: [PATCH 231/316] Update Project.toml --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 6b7ff33d13..70629f84a1 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.21.0" +version = "9.22.0" [deps] AbstractTrees = "1520ce14-60c1-5f80-bbc7-55ef81b5835c" From 4780f41b78ba8f49d280e13960e40537160a8237 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Mon, 17 Jun 2024 13:23:40 +0530 Subject: [PATCH 232/316] fix: use defaults for operating point in linearization --- src/systems/abstractsystem.jl | 19 ++++++- src/systems/nonlinear/initializesystem.jl | 30 +++++------ test/downstream/linearize.jl | 63 +++++++++++++++++++++++ 3 files changed, 95 insertions(+), 17 deletions(-) diff --git a/src/systems/abstractsystem.jl b/src/systems/abstractsystem.jl index d9853c8e58..2ac84b2a8a 100644 --- a/src/systems/abstractsystem.jl +++ b/src/systems/abstractsystem.jl @@ -1896,6 +1896,7 @@ function linearization_function(sys::AbstractSystem, inputs, zero_dummy_der = false, initialization_solver_alg = TrustRegion(), eval_expression = false, eval_module = @__MODULE__, + warn_initialize_determined = true, kwargs...) inputs isa AbstractVector || (inputs = [inputs]) outputs isa AbstractVector || (outputs = [outputs]) @@ -1909,10 +1910,26 @@ function linearization_function(sys::AbstractSystem, inputs, op = merge(defs, op) end sys = ssys + u0map = Dict(k => v for (k, v) in op if is_variable(ssys, k)) initsys = structural_simplify( generate_initializesystem( - sys, guesses = guesses(sys), algebraic_only = true), + sys, u0map = u0map, guesses = guesses(sys), algebraic_only = true), fully_determined = false) + + # HACK: some unknowns may not be involved in any initialization equations, and are + # thus removed from the system during `structural_simplify`. + # This causes `getu(initsys, unknowns(sys))` to fail, so we add them back as parameters + # for now. + missing_unknowns = setdiff(unknowns(sys), all_symbols(initsys)) + if !isempty(missing_unknowns) + if warn_initialize_determined + @warn "Initialization system is underdetermined. No equations for $(missing_unknowns). Initialization will default to using least squares. To suppress this warning pass warn_initialize_determined = false." + end + new_parameters = [parameters(initsys); missing_unknowns] + @set! initsys.ps = new_parameters + initsys = complete(initsys) + end + if p isa SciMLBase.NullParameters p = Dict() else diff --git a/src/systems/nonlinear/initializesystem.jl b/src/systems/nonlinear/initializesystem.jl index a079548025..e9f70c09e9 100644 --- a/src/systems/nonlinear/initializesystem.jl +++ b/src/systems/nonlinear/initializesystem.jl @@ -70,25 +70,23 @@ function generate_initializesystem(sys::ODESystem; defs = merge(defaults(sys), filtered_u0) guesses = merge(get_guesses(sys), todict(guesses), dd_guess) - if !algebraic_only - for st in full_states - if st ∈ keys(defs) - def = defs[st] + for st in full_states + if st ∈ keys(defs) + def = defs[st] - if def isa Equation - st ∉ keys(guesses) && check_defguess && - error("Invalid setup: unknown $(st) has an initial condition equation with no guess.") - push!(eqs_ics, def) - push!(u0, st => guesses[st]) - else - push!(eqs_ics, st ~ def) - push!(u0, st => def) - end - elseif st ∈ keys(guesses) + if def isa Equation + st ∉ keys(guesses) && check_defguess && + error("Invalid setup: unknown $(st) has an initial condition equation with no guess.") + push!(eqs_ics, def) push!(u0, st => guesses[st]) - elseif check_defguess - error("Invalid setup: unknown $(st) has no default value or initial guess") + else + push!(eqs_ics, st ~ def) + push!(u0, st => def) end + elseif st ∈ keys(guesses) + push!(u0, st => guesses[st]) + elseif check_defguess + error("Invalid setup: unknown $(st) has no default value or initial guess") end end diff --git a/test/downstream/linearize.jl b/test/downstream/linearize.jl index 38df060332..6e9a4a070d 100644 --- a/test/downstream/linearize.jl +++ b/test/downstream/linearize.jl @@ -195,3 +195,66 @@ lsys, ssys = linearize(sat, [u], [y]; op = Dict(u => 2)) @test isempty(lsys.B) @test isempty(lsys.C) @test lsys.D[] == 0 + +# Test case when unknowns in system do not have equations in initialization system +using ModelingToolkit, OrdinaryDiffEq, LinearAlgebra +using ModelingToolkitStandardLibrary.Mechanical.Rotational +using ModelingToolkitStandardLibrary.Blocks: Add, Sine, PID, SecondOrder, Step, RealOutput +using ModelingToolkit: connect + +# Parameters +m1 = 1 +m2 = 1 +k = 1000 # Spring stiffness +c = 10 # Damping coefficient +@named inertia1 = Inertia(; J = m1) +@named inertia2 = Inertia(; J = m2) +@named spring = Spring(; c = k) +@named damper = Damper(; d = c) +@named torque = Torque() + +function SystemModel(u = nothing; name = :model) + 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)] + if u !== nothing + push!(eqs, connect(torque.tau, u.output)) + return ODESystem(eqs, t; + systems = [ + torque, + inertia1, + inertia2, + spring, + damper, + u + ], + name) + end + ODESystem(eqs, t; systems = [torque, inertia1, inertia2, spring, damper], name) +end + +@named r = Step(start_time = 0) +model = SystemModel() +@named pid = PID(k = 100, Ti = 0.5, Td = 1) +@named filt = SecondOrder(d = 0.9, w = 10) +@named sensor = AngleSensor() +@named er = Add(k2 = -1) + +connections = [connect(r.output, :r, filt.input) + connect(filt.output, er.input1) + connect(pid.ctr_output, :u, model.torque.tau) + connect(model.inertia2.flange_b, sensor.flange) + 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], + name = :closed_loop, defaults = [ + model.inertia1.phi => 0.0, + model.inertia2.phi => 0.0, + model.inertia1.w => 0.0, + model.inertia2.w => 0.0, + filt.x => 0.0, + filt.xd => 0.0 + ]) + +@test_nowarn linearize(closed_loop, :r, :y) From aa0558bfa9f76fc605e073ffde9f2fdd78cac1e6 Mon Sep 17 00:00:00 2001 From: Christopher Rackauckas Date: Mon, 1 Jul 2024 08:28:15 -0400 Subject: [PATCH 233/316] fix typo --- test/symbolic_events.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/symbolic_events.jl b/test/symbolic_events.jl index 88ea88b8b0..c9b4946d30 100644 --- a/test/symbolic_events.jl +++ b/test/symbolic_events.jl @@ -458,7 +458,7 @@ let ∂ₜ = D eqs = [∂ₜ(A) ~ -k * A] - @named ssys = SDESystem(eqs, Equation[0.0], t, [A], [k, t1, t2], + @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] From fe6ba2877cbc5ecac3fe0671f6d8c18041bfd52e Mon Sep 17 00:00:00 2001 From: Christopher Rackauckas Date: Mon, 1 Jul 2024 09:08:43 -0400 Subject: [PATCH 234/316] simpler test --- src/systems/diffeqs/sdesystem.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/systems/diffeqs/sdesystem.jl b/src/systems/diffeqs/sdesystem.jl index 7eab3dcb1c..0499af86fb 100644 --- a/src/systems/diffeqs/sdesystem.jl +++ b/src/systems/diffeqs/sdesystem.jl @@ -141,8 +141,8 @@ struct SDESystem <: AbstractODESystem check_parameters(ps, iv) check_equations(deqs, iv) check_equations(neqs, dvs) - if size(neqs, 1) != length(dvs) - throw(ArgumentError("Noise equations ill-formed. Number of rows must match number of states. size(neqs,1) = $(size(neqs,1)) != length(dvs) = $(length(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) end From 70b1a8bde5915b879d20038f77d686e139ea2a07 Mon Sep 17 00:00:00 2001 From: Christopher Rackauckas Date: Mon, 1 Jul 2024 14:52:52 -0400 Subject: [PATCH 235/316] fix ill-formed system --- test/sdesystem.jl | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/sdesystem.jl b/test/sdesystem.jl index 30f92e039c..866dab0f5d 100644 --- a/test/sdesystem.jl +++ b/test/sdesystem.jl @@ -462,6 +462,10 @@ fdif!(du, u0, p, t) eqs_short = [D(x) ~ σ * (y - x), D(y) ~ x * (ρ - z) - y ] + noise_eqs = [ + y - x + x - y + ] sys1 = SDESystem(eqs_short, noiseeqs, t, [x, y, z], [σ, ρ, β], name = :sys1) sys2 = SDESystem(eqs_short, noiseeqs, t, [x, y, z], [σ, ρ, β], name = :sys1) @test_throws ArgumentError SDESystem([sys2.y ~ sys1.z], [], t, [], [], From bbf967a8a52b4559bfbcd1a7237006bcbdd64839 Mon Sep 17 00:00:00 2001 From: Christopher Rackauckas Date: Mon, 1 Jul 2024 15:31:11 -0400 Subject: [PATCH 236/316] format --- test/sdesystem.jl | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/test/sdesystem.jl b/test/sdesystem.jl index 866dab0f5d..90dbf6f2dd 100644 --- a/test/sdesystem.jl +++ b/test/sdesystem.jl @@ -462,10 +462,8 @@ fdif!(du, u0, p, t) eqs_short = [D(x) ~ σ * (y - x), D(y) ~ x * (ρ - z) - y ] - noise_eqs = [ - y - x - x - y - ] + noise_eqs = [y - x + x - y] sys1 = SDESystem(eqs_short, noiseeqs, t, [x, y, z], [σ, ρ, β], name = :sys1) sys2 = SDESystem(eqs_short, noiseeqs, t, [x, y, z], [σ, ρ, β], name = :sys1) @test_throws ArgumentError SDESystem([sys2.y ~ sys1.z], [], t, [], [], From 3e81ea42bc145aa3165f710830bdb766059b144e Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Tue, 18 Jun 2024 15:33:50 +0530 Subject: [PATCH 237/316] fix: check for non-parameter values in operating point --- src/systems/abstractsystem.jl | 4 ++- test/downstream/linearize.jl | 47 +++++++++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+), 1 deletion(-) diff --git a/src/systems/abstractsystem.jl b/src/systems/abstractsystem.jl index 2ac84b2a8a..040977fdb8 100644 --- a/src/systems/abstractsystem.jl +++ b/src/systems/abstractsystem.jl @@ -2000,7 +2000,9 @@ function linearization_function(sys::AbstractSystem, inputs, p = todict(p) newps = deepcopy(sys_ps) for (k, v) in p - setp(sys, k)(newps, v) + if is_parameter(sys, k) + setp(sys, k)(newps, v) + end end p = newps end diff --git a/test/downstream/linearize.jl b/test/downstream/linearize.jl index 6e9a4a070d..17f06ee63c 100644 --- a/test/downstream/linearize.jl +++ b/test/downstream/linearize.jl @@ -258,3 +258,50 @@ closed_loop = ODESystem(connections, t, systems = [model, pid, filt, sensor, r, ]) @test_nowarn linearize(closed_loop, :r, :y) + +# https://discourse.julialang.org/t/mtk-change-in-linearize/115760/3 +@mtkmodel Tank_noi begin + # Model parameters + @parameters begin + ρ = 1, [description = "Liquid density"] + A = 5, [description = "Cross sectional tank area"] + K = 5, [description = "Effluent valve constant"] + h_ς = 3, [description = "Scaling level in valve model"] + end + # Model variables, with initial values needed + @variables begin + m(t) = 1.5 * ρ * A, [description = "Liquid mass"] + md_i(t), [description = "Influent mass flow rate"] + md_e(t), [description = "Effluent mass flow rate"] + V(t), [description = "Liquid volume"] + h(t), [description = "level"] + end + # Providing model equations + @equations begin + D(m) ~ md_i - md_e + m ~ ρ * V + V ~ A * h + md_e ~ K * sqrt(h / h_ς) + end +end + +@named tank_noi = Tank_noi() +@unpack md_i, h, m = tank_noi +m_ss = 2.4000000003229878 +@test_nowarn linearize(tank_noi, [md_i], [h]; op = Dict(m => m_ss, md_i => 2)) + +# Test initialization +@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) + +matrices1, _ = linearize(sys, [u], []; op = Dict(x => 2.0)) +matrices2, _ = linearize(sys, [u], []; op = Dict(y => 2.0)) +@test matrices1 == matrices2 + +# Ensure parameter values passed as `Dict` are respected +linfun, _ = linearization_function(sys, [u], []; op = Dict(x => 2.0)) +matrices = linfun([1.0], Dict(p => 3.0), 1.0) +# this would be 1 if the parameter value isn't respected +@test matrices.f_u[] == 3.0 From 239fe99cdfc5c753f9ad8c928632df45474b8372 Mon Sep 17 00:00:00 2001 From: Christopher Rackauckas Date: Tue, 2 Jul 2024 04:53:15 -0400 Subject: [PATCH 238/316] Update test/sdesystem.jl --- test/sdesystem.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/sdesystem.jl b/test/sdesystem.jl index 90dbf6f2dd..8db14f5aee 100644 --- a/test/sdesystem.jl +++ b/test/sdesystem.jl @@ -466,7 +466,7 @@ fdif!(du, u0, p, t) x - y] sys1 = SDESystem(eqs_short, noiseeqs, t, [x, y, z], [σ, ρ, β], name = :sys1) sys2 = SDESystem(eqs_short, noiseeqs, t, [x, y, z], [σ, ρ, β], name = :sys1) - @test_throws ArgumentError SDESystem([sys2.y ~ sys1.z], [], t, [], [], + @test_throws ArgumentError SDESystem([sys2.y ~ sys1.z], [sys2.y], t, [], [], systems = [sys1, sys2], name = :foo) end From 91a22a7943b08d783ff6c2c40d4227eb92b76af3 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Tue, 2 Jul 2024 15:40:54 +0530 Subject: [PATCH 239/316] fix: fix deepcopy for SteadyStateProblem --- src/systems/abstractsystem.jl | 15 ++++++++++++--- src/systems/diffeqs/abstractodesystem.jl | 24 +----------------------- 2 files changed, 13 insertions(+), 26 deletions(-) diff --git a/src/systems/abstractsystem.jl b/src/systems/abstractsystem.jl index 040977fdb8..3353690b04 100644 --- a/src/systems/abstractsystem.jl +++ b/src/systems/abstractsystem.jl @@ -1223,21 +1223,24 @@ end struct ObservedFunctionCache{S} sys::S dict::Dict{Any, Any} + steady_state::Bool eval_expression::Bool eval_module::Module end -function ObservedFunctionCache(sys; eval_expression = false, eval_module = @__MODULE__) - return ObservedFunctionCache(sys, Dict(), eval_expression, eval_module) +function ObservedFunctionCache( + sys; steady_state = false, eval_expression = false, eval_module = @__MODULE__) + return ObservedFunctionCache(sys, Dict(), steady_state, eval_expression, eval_module) end # This is hit because ensemble problems do a deepcopy function Base.deepcopy_internal(ofc::ObservedFunctionCache, stackdict::IdDict) sys = deepcopy(ofc.sys) dict = deepcopy(ofc.dict) + steady_state = ofc.steady_state eval_expression = ofc.eval_expression eval_module = ofc.eval_module - newofc = ObservedFunctionCache(sys, dict, eval_expression, eval_module) + newofc = ObservedFunctionCache(sys, dict, steady_state, eval_expression, eval_module) stackdict[ofc] = newofc return newofc end @@ -1248,6 +1251,12 @@ function (ofc::ObservedFunctionCache)(obsvar, args...) ofc.sys, obsvar; eval_expression = ofc.eval_expression, eval_module = ofc.eval_module) end + if ofc.steady_state + obs = let fn = obs + fn1(u, p, t = Inf) = fn(u, p, t) + fn1 + end + end if args === () return obs else diff --git a/src/systems/diffeqs/abstractodesystem.jl b/src/systems/diffeqs/abstractodesystem.jl index 74eb4c2809..6c54cf2200 100644 --- a/src/systems/diffeqs/abstractodesystem.jl +++ b/src/systems/diffeqs/abstractodesystem.jl @@ -399,29 +399,7 @@ function DiffEqBase.ODEFunction{iip, specialize}(sys::AbstractODESystem, ArrayInterface.restructure(u0 .* u0', M) end - obs = observed(sys) - observedfun = if steady_state - let sys = sys, dict = Dict() - function generated_observed(obsvar, args...) - obs = get!(dict, value(obsvar)) do - SymbolicIndexingInterface.observed( - sys, obsvar; eval_expression, eval_module) - end - if args === () - return let obs = obs - fn1(u, p, t = Inf) = obs(u, p, t) - fn1 - end - elseif length(args) == 2 - return obs(args..., Inf) - else - return obs(args...) - end - end - end - else - ObservedFunctionCache(sys; eval_expression, eval_module) - end + observedfun = ObservedFunctionCache(sys; steady_state, eval_expression, eval_module) jac_prototype = if sparse uElType = u0 === nothing ? Float64 : eltype(u0) From 97e51fe1d22ed7e700575be3b9530dd0517ed4f3 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Mon, 1 Jul 2024 12:07:34 +0530 Subject: [PATCH 240/316] fix: infer oop form for SDEProblem/SDEFunction with StaticArrays --- src/systems/diffeqs/sdesystem.jl | 39 +++++++++++++++++++++++++++----- test/sdesystem.jl | 15 ++++++++++++ 2 files changed, 48 insertions(+), 6 deletions(-) diff --git a/src/systems/diffeqs/sdesystem.jl b/src/systems/diffeqs/sdesystem.jl index 09bea2d152..7e5bb35724 100644 --- a/src/systems/diffeqs/sdesystem.jl +++ b/src/systems/diffeqs/sdesystem.jl @@ -403,14 +403,14 @@ function Girsanov_transform(sys::SDESystem, u; θ0 = 1.0) checks = false) end -function DiffEqBase.SDEFunction{iip}(sys::SDESystem, dvs = unknowns(sys), +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, eval_module = @__MODULE__, checkbounds = false, - kwargs...) where {iip} + 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 @@ -480,7 +480,7 @@ function DiffEqBase.SDEFunction{iip}(sys::SDESystem, dvs = unknowns(sys), observedfun = ObservedFunctionCache(sys; eval_expression, eval_module) - SDEFunction{iip}(f, g, + SDEFunction{iip, specialize}(f, g, sys = sys, jac = _jac === nothing ? nothing : _jac, tgrad = _tgrad === nothing ? nothing : _tgrad, @@ -505,6 +505,16 @@ 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), @@ -583,14 +593,16 @@ function SDEFunctionExpr(sys::SDESystem, args...; kwargs...) SDEFunctionExpr{true}(sys, args...; kwargs...) end -function DiffEqBase.SDEProblem{iip}(sys::SDESystem, u0map = [], tspan = get_tspan(sys), +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} + 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_DEProblem(SDEFunction{iip}, sys, u0map, parammap; check_length, + f, u0, p = process_DEProblem( + SDEFunction{iip, specialize}, sys, u0map, parammap; check_length, kwargs...) cbs = process_events(sys; callback, kwargs...) sparsenoise === nothing && (sparsenoise = get(kwargs, :sparse, false)) @@ -628,6 +640,21 @@ 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, diff --git a/test/sdesystem.jl b/test/sdesystem.jl index b18ab648e7..9833dfa8f9 100644 --- a/test/sdesystem.jl +++ b/test/sdesystem.jl @@ -614,3 +614,18 @@ sys2 = complete(sys2) prob = SDEProblem(sys1, sts .=> [1.0, 0.0, 0.0], (0.0, 100.0), ps .=> (10.0, 26.0)) solve(prob, LambaEulerHeun(), seed = 1) + +# SDEProblem construction with StaticArrays +# Issue#2814 +@parameters p d +@variables x(tt) +@brownian a +eqs = [D(x) ~ p - d * x + a * sqrt(p)] +@mtkbuild sys = System(eqs, tt) +u0 = @SVector[x => 10.0] +tspan = (0.0, 10.0) +ps = @SVector[p => 5.0, d => 0.5] +sprob = SDEProblem(sys, u0, tspan, ps) +@test !isinplace(sprob) +@test !isinplace(sprob.f) +@test_nowarn solve(sprob, ImplicitEM()) From 54df3cc2b9752cc33c7d21411ccbe5222a8126a3 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Tue, 2 Jul 2024 16:46:16 +0530 Subject: [PATCH 241/316] feat: generate vector noise function for diagonal noise matrix --- src/systems/diffeqs/sdesystem.jl | 3 +++ test/sdesystem.jl | 13 +++++++++++++ 2 files changed, 16 insertions(+) diff --git a/src/systems/diffeqs/sdesystem.jl b/src/systems/diffeqs/sdesystem.jl index 7e5bb35724..412d308c87 100644 --- a/src/systems/diffeqs/sdesystem.jl +++ b/src/systems/diffeqs/sdesystem.jl @@ -231,6 +231,9 @@ function generate_diffusion_function(sys::SDESystem, dvs = unknowns(sys), if isdde eqs = delay_to_function(sys, eqs) end + if eqs isa AbstractMatrix && isdiag(eqs) + eqs = diag(eqs) + end u = map(x -> time_varying_as_func(value(x), sys), dvs) p = if has_index_cache(sys) && get_index_cache(sys) !== nothing reorder_parameters(get_index_cache(sys), ps) diff --git a/test/sdesystem.jl b/test/sdesystem.jl index 9833dfa8f9..b1786aa721 100644 --- a/test/sdesystem.jl +++ b/test/sdesystem.jl @@ -629,3 +629,16 @@ sprob = SDEProblem(sys, u0, tspan, ps) @test !isinplace(sprob) @test !isinplace(sprob.f) @test_nowarn solve(sprob, ImplicitEM()) + +# Ensure diagonal noise generates vector noise function +@variables y(tt) +@brownian b +eqs = [D(x) ~ p - d * x + a * sqrt(p) + D(y) ~ p - d * y + b * sqrt(d)] +@mtkbuild sys = System(eqs, tt) +u0 = @SVector[x => 10.0, y => 20.0] +tspan = (0.0, 10.0) +ps = @SVector[p => 5.0, d => 0.5] +sprob = SDEProblem(sys, u0, tspan, ps) +@test sprob.f.g(sprob.u0, sprob.p, sprob.tspan[1]) isa SVector{2, Float64} +@test_nowarn solve(sprob, ImplicitEM()) From 3ff798bdc772c270d005bcf8bf4061d9c13ff8fb Mon Sep 17 00:00:00 2001 From: Christopher Rackauckas Date: Wed, 3 Jul 2024 08:23:27 -0400 Subject: [PATCH 242/316] Update Project.toml --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 70629f84a1..0bee7dd1dc 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.22.0" +version = "9.23.0" [deps] AbstractTrees = "1520ce14-60c1-5f80-bbc7-55ef81b5835c" From 43e38eedab1f2c2f55163f6af0fb5e40a0bf96bd Mon Sep 17 00:00:00 2001 From: Christopher Rackauckas Date: Thu, 4 Jul 2024 01:27:37 -0400 Subject: [PATCH 243/316] fix test typo --- test/sdesystem.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/sdesystem.jl b/test/sdesystem.jl index 288c2fb3d5..3314b8a89f 100644 --- a/test/sdesystem.jl +++ b/test/sdesystem.jl @@ -464,8 +464,8 @@ fdif!(du, u0, p, t) ] noise_eqs = [y - x x - y] - sys1 = SDESystem(eqs_short, noiseeqs, t, [x, y, z], [σ, ρ, β], name = :sys1) - sys2 = SDESystem(eqs_short, noiseeqs, t, [x, y, z], [σ, ρ, β], name = :sys1) + 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, [], [], systems = [sys1, sys2], name = :foo) end From 622408b5873269c20a5226dbfe6f704a6670a35d Mon Sep 17 00:00:00 2001 From: Christopher Rackauckas Date: Thu, 4 Jul 2024 10:15:14 -0400 Subject: [PATCH 244/316] Update Project.toml --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 0bee7dd1dc..947dee8b1b 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.23.0" +version = "9.24.0" [deps] AbstractTrees = "1520ce14-60c1-5f80-bbc7-55ef81b5835c" From 4f897952d1b870e467db3babf608e87d09c09d44 Mon Sep 17 00:00:00 2001 From: Herman Sletmoen Date: Fri, 5 Jul 2024 16:25:58 +0200 Subject: [PATCH 245/316] Extend initialization equations --- src/systems/abstractsystem.jl | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/systems/abstractsystem.jl b/src/systems/abstractsystem.jl index 3353690b04..7843f6663b 100644 --- a/src/systems/abstractsystem.jl +++ b/src/systems/abstractsystem.jl @@ -2528,16 +2528,18 @@ function extend(sys::AbstractSystem, basesys::AbstractSystem; name::Symbol = nam cevs = union(get_continuous_events(basesys), get_continuous_events(sys)) devs = union(get_discrete_events(basesys), get_discrete_events(sys)) defs = merge(get_defaults(basesys), get_defaults(sys)) # prefer `sys` + ieqs = union(get_initialization_eqs(basesys), get_initialization_eqs(sys)) syss = union(get_systems(basesys), get_systems(sys)) if length(ivs) == 0 T(eqs, sts, ps, observed = obs, defaults = defs, name = name, systems = syss, continuous_events = cevs, discrete_events = devs, gui_metadata = gui_metadata, - parameter_dependencies = dep_ps) + parameter_dependencies = dep_ps, initialization_eqs = ieqs) elseif length(ivs) == 1 T(eqs, ivs[1], sts, ps, observed = obs, defaults = defs, name = name, systems = syss, continuous_events = cevs, discrete_events = devs, - gui_metadata = gui_metadata, parameter_dependencies = dep_ps) + gui_metadata = gui_metadata, parameter_dependencies = dep_ps, + initialization_eqs = ieqs) end end From bea9fad43728322087f5a5907e502a76868df7d5 Mon Sep 17 00:00:00 2001 From: Herman Sletmoen Date: Fri, 5 Jul 2024 16:32:37 +0200 Subject: [PATCH 246/316] Added test --- test/initializationsystem.jl | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/test/initializationsystem.jl b/test/initializationsystem.jl index 5be31da76c..518eed2a5f 100644 --- a/test/initializationsystem.jl +++ b/test/initializationsystem.jl @@ -447,3 +447,11 @@ prob = ODEProblem(pend, [x => 1], (0.0, 1.5), [g => 1], unsimp = generate_initializesystem(pend; u0map = [x => 1], initialization_eqs = [y ~ 1]) sys = structural_simplify(unsimp; fully_determined = false) @test length(equations(sys)) == 3 + +# Extend two systems with initialization equations +# 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]) +sys = extend(sysx, sysy) +@test length(equations(generate_initializesystem(sys))) == 2 \ No newline at end of file From 8933782aa2d236b41168a220c66633cd10be2620 Mon Sep 17 00:00:00 2001 From: Christopher Rackauckas Date: Fri, 5 Jul 2024 15:26:35 -0400 Subject: [PATCH 247/316] Handle modelingtoolkitize for nonlinearleastsquaresproblem Fixes https://github.com/SciML/ModelingToolkit.jl/issues/2669 --- src/systems/nonlinear/modelingtoolkitize.jl | 14 ++++++++++---- test/modelingtoolkitize.jl | 14 ++++++++++++++ 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/src/systems/nonlinear/modelingtoolkitize.jl b/src/systems/nonlinear/modelingtoolkitize.jl index 6212b5fe73..9aabd9ec06 100644 --- a/src/systems/nonlinear/modelingtoolkitize.jl +++ b/src/systems/nonlinear/modelingtoolkitize.jl @@ -4,7 +4,8 @@ $(TYPEDSIGNATURES) Generate `NonlinearSystem`, dependent variables, and parameters from an `NonlinearProblem`. """ function modelingtoolkitize( - prob::NonlinearProblem; u_names = nothing, p_names = nothing, kwargs...) + prob::Union{NonlinearProblem, NonlinearLeastSquaresProblem}; + u_names = nothing, p_names = nothing, kwargs...) p = prob.p has_p = !(p isa Union{DiffEqBase.NullParameters, Nothing}) @@ -37,13 +38,18 @@ function modelingtoolkitize( end if DiffEqBase.isinplace(prob) - rhs = ArrayInterface.restructure(prob.u0, similar(vars, Num)) + if prob isa NonlinearLeastSquaresProblem + rhs = ArrayInterface.restructure(prob.f.resid_prototype, similar(prob.f.resid_prototype, Num)) + else + rhs = ArrayInterface.restructure(prob.u0, similar(vars, Num)) + end prob.f(rhs, vars, params) + eqs = vcat([0.0 ~ rhs[i] for i in 1:length(prob.f.resid_prototype)]...) 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 - out_def = prob.f(prob.u0, prob.p) - eqs = vcat([0.0 ~ rhs[i] for i in 1:length(out_def)]...) sts = vec(collect(vars)) _params = params diff --git a/test/modelingtoolkitize.jl b/test/modelingtoolkitize.jl index 996d333f2f..ac32f874ed 100644 --- a/test/modelingtoolkitize.jl +++ b/test/modelingtoolkitize.jl @@ -473,3 +473,17 @@ sys = modelingtoolkitize(prob) end end end + +## NonlinearLeastSquaresProblem + +function nlls!(du, u, p) + du[1] = 2u[1] - 2 + du[2] = u[1] - 4u[2] + du[3] = 0 +end +u0 = [0.0, 0.0] +prob = NonlinearLeastSquaresProblem( + NonlinearFunction(nlls!, resid_prototype = zeros(3)), u0) +sys = modelingtoolkitize(prob) +@test length(equations(sys)) == 3 +@test length(equations(structural_simplify(sys; fully_determined = false))) == 0 From bb40ca86653f7931b38bedf25fa941ba072692f2 Mon Sep 17 00:00:00 2001 From: Herman Sletmoen Date: Fri, 5 Jul 2024 21:52:23 +0200 Subject: [PATCH 248/316] Format --- test/initializationsystem.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/initializationsystem.jl b/test/initializationsystem.jl index 518eed2a5f..3a85def23c 100644 --- a/test/initializationsystem.jl +++ b/test/initializationsystem.jl @@ -454,4 +454,4 @@ sys = structural_simplify(unsimp; fully_determined = false) @named sysx = ODESystem([D(x) ~ 0], t; initialization_eqs = [x ~ 1]) @named sysy = ODESystem([D(y) ~ 0], t; initialization_eqs = [y ~ 2]) sys = extend(sysx, sysy) -@test length(equations(generate_initializesystem(sys))) == 2 \ No newline at end of file +@test length(equations(generate_initializesystem(sys))) == 2 From 355baeaca7e912634fd8d3e8458e8c20698e01aa Mon Sep 17 00:00:00 2001 From: Herman Sletmoen Date: Fri, 5 Jul 2024 22:15:43 +0200 Subject: [PATCH 249/316] Also forward guesses in extend(), in case initialization equations are nonlinear --- src/systems/abstractsystem.jl | 5 +++-- test/initializationsystem.jl | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/systems/abstractsystem.jl b/src/systems/abstractsystem.jl index 7843f6663b..93da7a0f17 100644 --- a/src/systems/abstractsystem.jl +++ b/src/systems/abstractsystem.jl @@ -2528,18 +2528,19 @@ function extend(sys::AbstractSystem, basesys::AbstractSystem; name::Symbol = nam cevs = union(get_continuous_events(basesys), get_continuous_events(sys)) devs = union(get_discrete_events(basesys), get_discrete_events(sys)) defs = merge(get_defaults(basesys), get_defaults(sys)) # prefer `sys` + guesses = merge(get_guesses(basesys), get_guesses(sys)) # prefer `sys` ieqs = union(get_initialization_eqs(basesys), get_initialization_eqs(sys)) syss = union(get_systems(basesys), get_systems(sys)) if length(ivs) == 0 T(eqs, sts, ps, observed = obs, defaults = defs, name = name, systems = syss, continuous_events = cevs, discrete_events = devs, gui_metadata = gui_metadata, - parameter_dependencies = dep_ps, initialization_eqs = ieqs) + parameter_dependencies = dep_ps, initialization_eqs = ieqs, guesses = guesses) elseif length(ivs) == 1 T(eqs, ivs[1], sts, ps, observed = obs, defaults = defs, name = name, systems = syss, continuous_events = cevs, discrete_events = devs, gui_metadata = gui_metadata, parameter_dependencies = dep_ps, - initialization_eqs = ieqs) + initialization_eqs = ieqs, guesses = guesses) end end diff --git a/test/initializationsystem.jl b/test/initializationsystem.jl index 3a85def23c..f4097ecd97 100644 --- a/test/initializationsystem.jl +++ b/test/initializationsystem.jl @@ -448,10 +448,11 @@ unsimp = generate_initializesystem(pend; u0map = [x => 1], initialization_eqs = sys = structural_simplify(unsimp; fully_determined = false) @test length(equations(sys)) == 3 -# Extend two systems with initialization equations +# 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]) +@named sysy = ODESystem([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 From a3ea11c7ce2e6f0e52803851fc6e0bfbf973af4e Mon Sep 17 00:00:00 2001 From: Christopher Rackauckas Date: Fri, 5 Jul 2024 17:44:59 -0400 Subject: [PATCH 250/316] format --- src/systems/nonlinear/modelingtoolkitize.jl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/systems/nonlinear/modelingtoolkitize.jl b/src/systems/nonlinear/modelingtoolkitize.jl index 9aabd9ec06..67348402c0 100644 --- a/src/systems/nonlinear/modelingtoolkitize.jl +++ b/src/systems/nonlinear/modelingtoolkitize.jl @@ -39,7 +39,8 @@ function modelingtoolkitize( if DiffEqBase.isinplace(prob) if prob isa NonlinearLeastSquaresProblem - rhs = ArrayInterface.restructure(prob.f.resid_prototype, similar(prob.f.resid_prototype, Num)) + rhs = ArrayInterface.restructure( + prob.f.resid_prototype, similar(prob.f.resid_prototype, Num)) else rhs = ArrayInterface.restructure(prob.u0, similar(vars, Num)) end From 2c5575009036360342ef4e38297047715024557d Mon Sep 17 00:00:00 2001 From: Christopher Rackauckas Date: Fri, 5 Jul 2024 19:06:14 -0400 Subject: [PATCH 251/316] handle non nlls --- src/systems/nonlinear/modelingtoolkitize.jl | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/systems/nonlinear/modelingtoolkitize.jl b/src/systems/nonlinear/modelingtoolkitize.jl index 67348402c0..e8bbb39a9f 100644 --- a/src/systems/nonlinear/modelingtoolkitize.jl +++ b/src/systems/nonlinear/modelingtoolkitize.jl @@ -41,11 +41,14 @@ function modelingtoolkitize( 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 - prob.f(rhs, vars, params) - eqs = vcat([0.0 ~ rhs[i] for i in 1:length(prob.f.resid_prototype)]...) + else rhs = prob.f(vars, params) out_def = prob.f(prob.u0, prob.p) From 13c427a4a8598cfd5610b10f362c409a7d9eeb97 Mon Sep 17 00:00:00 2001 From: Christopher Rackauckas Date: Fri, 5 Jul 2024 22:16:39 -0400 Subject: [PATCH 252/316] format --- src/systems/nonlinear/modelingtoolkitize.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/systems/nonlinear/modelingtoolkitize.jl b/src/systems/nonlinear/modelingtoolkitize.jl index e8bbb39a9f..2f12157884 100644 --- a/src/systems/nonlinear/modelingtoolkitize.jl +++ b/src/systems/nonlinear/modelingtoolkitize.jl @@ -41,8 +41,8 @@ function modelingtoolkitize( 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)]...) + 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) From a94b5c2896a197dc5897167bddab034e499542d6 Mon Sep 17 00:00:00 2001 From: Herman Sletmoen Date: Sat, 6 Jul 2024 12:57:12 +0200 Subject: [PATCH 253/316] Extend initialization only for ODE systems --- src/systems/abstractsystem.jl | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/src/systems/abstractsystem.jl b/src/systems/abstractsystem.jl index 93da7a0f17..dc9073ac0c 100644 --- a/src/systems/abstractsystem.jl +++ b/src/systems/abstractsystem.jl @@ -2517,6 +2517,7 @@ function extend(sys::AbstractSystem, basesys::AbstractSystem; name::Symbol = nam end end + # collect fields common to all system types eqs = union(get_eqs(basesys), get_eqs(sys)) sts = union(get_unknowns(basesys), get_unknowns(sys)) ps = union(get_ps(basesys), get_ps(sys)) @@ -2528,20 +2529,20 @@ function extend(sys::AbstractSystem, basesys::AbstractSystem; name::Symbol = nam cevs = union(get_continuous_events(basesys), get_continuous_events(sys)) devs = union(get_discrete_events(basesys), get_discrete_events(sys)) defs = merge(get_defaults(basesys), get_defaults(sys)) # prefer `sys` - guesses = merge(get_guesses(basesys), get_guesses(sys)) # prefer `sys` - ieqs = union(get_initialization_eqs(basesys), get_initialization_eqs(sys)) syss = union(get_systems(basesys), get_systems(sys)) + args = length(ivs) == 0 ? (eqs, sts, ps) : (eqs, ivs[1], sts, ps) + kwargs = (parameter_dependencies = dep_ps, observed = obs, continuous_events = cevs, + discrete_events = devs, defaults = defs, systems = syss, + name = name, gui_metadata = gui_metadata) - if length(ivs) == 0 - T(eqs, sts, ps, observed = obs, defaults = defs, name = name, systems = syss, - continuous_events = cevs, discrete_events = devs, gui_metadata = gui_metadata, - parameter_dependencies = dep_ps, initialization_eqs = ieqs, guesses = guesses) - elseif length(ivs) == 1 - T(eqs, ivs[1], sts, ps, observed = obs, defaults = defs, name = name, - systems = syss, continuous_events = cevs, discrete_events = devs, - gui_metadata = gui_metadata, parameter_dependencies = dep_ps, - initialization_eqs = ieqs, guesses = guesses) + # 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 + + return T(args...; kwargs...) end function Base.:(&)(sys::AbstractSystem, basesys::AbstractSystem; name::Symbol = nameof(sys)) From ee522fe77a09f83fbe2d5f7b697673a622aa8bbe Mon Sep 17 00:00:00 2001 From: Herman Sletmoen Date: Sat, 6 Jul 2024 13:05:15 +0200 Subject: [PATCH 254/316] Format --- 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 dc9073ac0c..6cdcd72855 100644 --- a/src/systems/abstractsystem.jl +++ b/src/systems/abstractsystem.jl @@ -2532,8 +2532,8 @@ function extend(sys::AbstractSystem, basesys::AbstractSystem; name::Symbol = nam syss = union(get_systems(basesys), get_systems(sys)) args = length(ivs) == 0 ? (eqs, sts, ps) : (eqs, ivs[1], sts, ps) kwargs = (parameter_dependencies = dep_ps, observed = obs, continuous_events = cevs, - discrete_events = devs, defaults = defs, systems = syss, - name = name, gui_metadata = gui_metadata) + discrete_events = devs, defaults = defs, systems = syss, + name = name, gui_metadata = gui_metadata) # collect fields specific to some system types if basesys isa ODESystem From 16a32884c622f2c93908ee0b53392b61a13f1b74 Mon Sep 17 00:00:00 2001 From: Herman Sletmoen Date: Mon, 15 Jul 2024 18:48:46 +0200 Subject: [PATCH 255/316] Expand observed equations into lhs => rhs defaults, but flip if lhs is given --- src/systems/diffeqs/abstractodesystem.jl | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/systems/diffeqs/abstractodesystem.jl b/src/systems/diffeqs/abstractodesystem.jl index 6c54cf2200..b03819a633 100644 --- a/src/systems/diffeqs/abstractodesystem.jl +++ b/src/systems/diffeqs/abstractodesystem.jl @@ -725,10 +725,16 @@ function get_u0( defs = mergedefaults(defs, parammap, ps) end - obs = filter!(x -> !(x[1] isa Number), - map(x -> isparameter(x.rhs) ? x.lhs => x.rhs : x.rhs => x.lhs, observed(sys))) - observedmap = isempty(obs) ? Dict() : todict(obs) - defs = mergedefaults(defs, observedmap, u0map, dvs) + # Convert observed equations "lhs ~ rhs" into defaults. + # Use the order "lhs => rhs" by default, but flip it to "rhs => lhs" + # if "lhs" is known by other means (parameter, another default, ...) + # TODO: Is there a better way to determine which equations to flip? + obs = map(x -> x.lhs => x.rhs, observed(sys)) + obs = map(x -> isparameter(x[1]) || x[1] in keys(defs) ? reverse(x) : x, obs) + obs = filter!(x -> !(x[1] isa Number), obs) # exclude e.g. "0 => x^2 + y^2 - 25" + obsmap = isempty(obs) ? Dict() : todict(obs) + + defs = mergedefaults(defs, obsmap, u0map, dvs) if symbolic_u0 u0 = varmap_to_vars( u0map, dvs; defaults = defs, tofloat = false, use_union = false, toterm) From 6e636b2cdd6305a6d24e94294bce720346431c99 Mon Sep 17 00:00:00 2001 From: Herman Sletmoen Date: Mon, 15 Jul 2024 18:56:39 +0200 Subject: [PATCH 256/316] Activate unactivated tests --- test/guess_propagation.jl | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/test/guess_propagation.jl b/test/guess_propagation.jl index 6d71ce6ca1..738e930adc 100644 --- a/test/guess_propagation.jl +++ b/test/guess_propagation.jl @@ -90,21 +90,21 @@ prob = ODEProblem(sys, [], (0.0, 1.0), [x0 => 1.0]) @variables y(t) = x0 @mtkbuild sys = ODESystem([x ~ x0, D(y) ~ x], t) prob = ODEProblem(sys, [], (0.0, 1.0), [x0 => 1.0]) -prob[x] == 1.0 -prob[y] == 1.0 +@test prob[x] == 1.0 +@test prob[y] == 1.0 @parameters x0 @variables x(t) @variables y(t) = x0 @mtkbuild sys = ODESystem([x ~ y, D(y) ~ x], t) prob = ODEProblem(sys, [], (0.0, 1.0), [x0 => 1.0]) -prob[x] == 1.0 -prob[y] == 1.0 +@test prob[x] == 1.0 +@test prob[y] == 1.0 @parameters x0 @variables x(t) = x0 @variables y(t) = x @mtkbuild sys = ODESystem([x ~ y, D(y) ~ x], t) prob = ODEProblem(sys, [], (0.0, 1.0), [x0 => 1.0]) -prob[x] == 1.0 -prob[y] == 1.0 +@test prob[x] == 1.0 +@test prob[y] == 1.0 From 5e279cee3564f5821e92c273cd53c05eca27ca88 Mon Sep 17 00:00:00 2001 From: Herman Sletmoen Date: Mon, 15 Jul 2024 19:04:28 +0200 Subject: [PATCH 257/316] Added test from issue (and a simplified version of it) --- test/odesystem.jl | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/test/odesystem.jl b/test/odesystem.jl index ab28f5cb47..c7da5ba702 100644 --- a/test/odesystem.jl +++ b/test/odesystem.jl @@ -1194,3 +1194,23 @@ end @test_nowarn obsfn(buffer, [1.0], ps..., 3.0) @test buffer ≈ [2.0, 3.0, 4.0] end + +# https://github.com/SciML/ModelingToolkit.jl/issues/2859 +@testset "Initialization with defaults from observed equations (edge case)" begin + @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) + ssys = structural_simplify(sys) + prob = ODEProblem(ssys, [], (0.0, 1.0), []) + @test prob[x] == prob[y] == prob[z] == 1.0 + + @parameters y0 + @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) + ssys = structural_simplify(sys) + prob = ODEProblem(ssys, [], (0.0, 1.0), []) + @test prob[x] == prob[y] == prob[z] == 1.0 +end From 8862b97e86b8f677bb641f2c0ab8a2f8d1172e8e Mon Sep 17 00:00:00 2001 From: Herman Sletmoen Date: Mon, 15 Jul 2024 20:15:15 +0200 Subject: [PATCH 258/316] Remove redundant isparameter(lhs) check --- 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 b03819a633..ab0565eb31 100644 --- a/src/systems/diffeqs/abstractodesystem.jl +++ b/src/systems/diffeqs/abstractodesystem.jl @@ -730,7 +730,7 @@ function get_u0( # if "lhs" is known by other means (parameter, another default, ...) # TODO: Is there a better way to determine which equations to flip? obs = map(x -> x.lhs => x.rhs, observed(sys)) - obs = map(x -> isparameter(x[1]) || x[1] in keys(defs) ? reverse(x) : x, obs) + obs = map(x -> x[1] in keys(defs) ? reverse(x) : x, obs) obs = filter!(x -> !(x[1] isa Number), obs) # exclude e.g. "0 => x^2 + y^2 - 25" obsmap = isempty(obs) ? Dict() : todict(obs) From d95f9664293e50978d77348a6ada8e2a16537f14 Mon Sep 17 00:00:00 2001 From: Herman Sletmoen Date: Tue, 16 Jul 2024 10:47:10 +0200 Subject: [PATCH 259/316] Check that independent variables are defined as @parameters --- src/systems/diffeqs/odesystem.jl | 1 + src/systems/diffeqs/sdesystem.jl | 1 + src/systems/discrete_system/discrete_system.jl | 1 + src/systems/jumps/jumpsystem.jl | 1 + src/utils.jl | 6 ++++++ 5 files changed, 10 insertions(+) diff --git a/src/systems/diffeqs/odesystem.jl b/src/systems/diffeqs/odesystem.jl index b0bb9dd9a4..5bfb465794 100644 --- a/src/systems/diffeqs/odesystem.jl +++ b/src/systems/diffeqs/odesystem.jl @@ -184,6 +184,7 @@ struct ODESystem <: AbstractODESystem discrete_subsystems = nothing, solved_unknowns = nothing, split_idxs = 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) diff --git a/src/systems/diffeqs/sdesystem.jl b/src/systems/diffeqs/sdesystem.jl index bb8e5889e7..df4dac37a6 100644 --- a/src/systems/diffeqs/sdesystem.jl +++ b/src/systems/diffeqs/sdesystem.jl @@ -137,6 +137,7 @@ struct SDESystem <: AbstractODESystem complete = false, index_cache = 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) diff --git a/src/systems/discrete_system/discrete_system.jl b/src/systems/discrete_system/discrete_system.jl index 1f8c1796e2..79c48af134 100644 --- a/src/systems/discrete_system/discrete_system.jl +++ b/src/systems/discrete_system/discrete_system.jl @@ -101,6 +101,7 @@ struct DiscreteSystem <: AbstractTimeDependentSystem complete = false, index_cache = 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) end diff --git a/src/systems/jumps/jumpsystem.jl b/src/systems/jumps/jumpsystem.jl index 4da6ce710c..8e443dc1b9 100644 --- a/src/systems/jumps/jumpsystem.jl +++ b/src/systems/jumps/jumpsystem.jl @@ -118,6 +118,7 @@ struct JumpSystem{U <: ArrayPartition} <: AbstractTimeDependentSystem complete = false, index_cache = nothing; 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) end diff --git a/src/utils.jl b/src/utils.jl index dc239e34c1..a6a17d3788 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -102,6 +102,12 @@ const CheckAll = 1 << 0 const CheckComponents = 1 << 1 const CheckUnits = 1 << 2 +function check_independent_variables(ivs) + for iv in ivs + isparameter(iv) || throw(ArgumentError("Independent variable $iv is not a parameter.")) + end +end + function check_parameters(ps, iv) for p in ps isequal(iv, p) && From ed78c6f6bf099af3fe8bba22e913bbb17dd492c5 Mon Sep 17 00:00:00 2001 From: Herman Sletmoen Date: Tue, 16 Jul 2024 11:04:11 +0200 Subject: [PATCH 260/316] Added test --- test/odesystem.jl | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/test/odesystem.jl b/test/odesystem.jl index ab28f5cb47..819ffa0205 100644 --- a/test/odesystem.jl +++ b/test/odesystem.jl @@ -1194,3 +1194,13 @@ end @test_nowarn obsfn(buffer, [1.0], ps..., 3.0) @test buffer ≈ [2.0, 3.0, 4.0] end + +# https://github.com/SciML/ModelingToolkit.jl/issues/2818 +@testset "Independent variable must be a parameter" + @parameters x + @variables y(x) + @test_nowarn @named sys = ODESystem([y ~ 0], x) + + @variables x y(x) + @test_throws ArgumentError @named sys = ODESystem([y ~ 0], x) +end From 886f8ce5c2f81afb603eec92bddb6ad8aad6deac Mon Sep 17 00:00:00 2001 From: Herman Sletmoen Date: Tue, 16 Jul 2024 11:09:56 +0200 Subject: [PATCH 261/316] Fix syntax error --- test/odesystem.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/odesystem.jl b/test/odesystem.jl index 819ffa0205..26afaf2cde 100644 --- a/test/odesystem.jl +++ b/test/odesystem.jl @@ -1196,7 +1196,7 @@ end end # https://github.com/SciML/ModelingToolkit.jl/issues/2818 -@testset "Independent variable must be a parameter" +@testset "Independent variable must be a parameter" begin @parameters x @variables y(x) @test_nowarn @named sys = ODESystem([y ~ 0], x) From 43cab06e730dba5607d20ab21188fa8254081363 Mon Sep 17 00:00:00 2001 From: Herman Sletmoen Date: Tue, 16 Jul 2024 11:10:23 +0200 Subject: [PATCH 262/316] Change ivar in a test to parameter --- test/hierarchical_initialization_eqs.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/hierarchical_initialization_eqs.jl b/test/hierarchical_initialization_eqs.jl index 077d834db2..82dc3cb566 100644 --- a/test/hierarchical_initialization_eqs.jl +++ b/test/hierarchical_initialization_eqs.jl @@ -1,6 +1,6 @@ using ModelingToolkit, OrdinaryDiffEq -t = only(@variables(t)) +t = only(@parameters(t)) D = Differential(t) """ A simple linear resistor model From 63c2ef3697ada5e1a0f63d3bc818b5dd1463a753 Mon Sep 17 00:00:00 2001 From: Herman Sletmoen Date: Tue, 16 Jul 2024 11:53:41 +0200 Subject: [PATCH 263/316] Format --- src/utils.jl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/utils.jl b/src/utils.jl index a6a17d3788..4eb7c45726 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -104,7 +104,8 @@ const CheckUnits = 1 << 2 function check_independent_variables(ivs) for iv in ivs - isparameter(iv) || throw(ArgumentError("Independent variable $iv is not a parameter.")) + isparameter(iv) || + throw(ArgumentError("Independent variable $iv is not a parameter.")) end end From 31e7c6c9d45f3992bfe230899e04df686af25c6f Mon Sep 17 00:00:00 2001 From: Herman Sletmoen Date: Tue, 16 Jul 2024 12:16:38 +0200 Subject: [PATCH 264/316] Update documentation --- docs/src/tutorials/SampledData.md | 3 ++- src/discretedomain.jl | 6 ++++-- src/systems/abstractsystem.jl | 4 +--- src/systems/alias_elimination.jl | 6 ++++-- src/systems/dependency_graphs.jl | 3 ++- src/systems/diffeqs/odesystem.jl | 4 ++-- src/systems/diffeqs/sdesystem.jl | 8 ++++---- src/systems/jumps/jumpsystem.jl | 3 ++- src/systems/pde/pdesystem.jl | 4 ++-- src/utils.jl | 3 ++- 10 files changed, 25 insertions(+), 19 deletions(-) diff --git a/docs/src/tutorials/SampledData.md b/docs/src/tutorials/SampledData.md index d2d9294bdb..614e8b65c7 100644 --- a/docs/src/tutorials/SampledData.md +++ b/docs/src/tutorials/SampledData.md @@ -97,7 +97,8 @@ H(z) = \dfrac{b_2 z^2 + b_1 z + b_0}{a_2 z^2 + a_1 z + a_0} may thus be modeled as ```julia -@variables t y(t) [description = "Output"] u(t) [description = "Input"] +t = ModelingToolkit.t_nounits +@variables y(t) [description = "Output"] u(t) [description = "Input"] k = ShiftIndex(Clock(t, dt)) eqs = [ a2 * y(k) + a1 * y(k - 1) + a0 * y(k - 2) ~ b2 * u(k) + b1 * u(k - 1) + b0 * u(k - 2) diff --git a/src/discretedomain.jl b/src/discretedomain.jl index 34f628a8b3..cb723e159f 100644 --- a/src/discretedomain.jl +++ b/src/discretedomain.jl @@ -98,7 +98,7 @@ $(FIELDS) ```jldoctest julia> using Symbolics -julia> @variables t; +julia> t = ModelingToolkit.t_nounits julia> Δ = Sample(t, 0.01) (::Sample) (generic function with 2 methods) @@ -166,7 +166,9 @@ The `ShiftIndex` operator allows you to index a signal and obtain a shifted disc # Examples ``` -julia> @variables t x(t); +julia> t = ModelingToolkit.t_nounits; + +julia> @variables x(t); julia> k = ShiftIndex(t, 0.1); diff --git a/src/systems/abstractsystem.jl b/src/systems/abstractsystem.jl index 6cdcd72855..2b8ab05d77 100644 --- a/src/systems/abstractsystem.jl +++ b/src/systems/abstractsystem.jl @@ -2237,11 +2237,10 @@ This example builds the following feedback interconnection and linearizes it fro ```julia using ModelingToolkit -@variables t +using ModelingToolkit: t_nounits as t, D_nounits as D function plant(; name) @variables x(t) = 1 @variables u(t)=0 y(t)=0 - D = Differential(t) eqs = [D(x) ~ -x + u y ~ x] ODESystem(eqs, t; name = name) @@ -2250,7 +2249,6 @@ end function ref_filt(; name) @variables x(t)=0 y(t)=0 @variables u(t)=0 [input = true] - D = Differential(t) eqs = [D(x) ~ -2 * x + u y ~ x] ODESystem(eqs, t, name = name) diff --git a/src/systems/alias_elimination.jl b/src/systems/alias_elimination.jl index 3a2405e6bd..fb4fedc920 100644 --- a/src/systems/alias_elimination.jl +++ b/src/systems/alias_elimination.jl @@ -388,8 +388,10 @@ Use Kahn's algorithm to topologically sort observed equations. Example: ```julia -julia> @variables t x(t) y(t) z(t) k(t) -(t, x(t), y(t), z(t), k(t)) +julia> t = ModelingToolkit.t_nounits + +julia> @variables x(t) y(t) z(t) k(t) +(x(t), y(t), z(t), k(t)) julia> eqs = [ x ~ y + z diff --git a/src/systems/dependency_graphs.jl b/src/systems/dependency_graphs.jl index 08755a57cb..c46cdca831 100644 --- a/src/systems/dependency_graphs.jl +++ b/src/systems/dependency_graphs.jl @@ -15,8 +15,9 @@ Example: ```julia using ModelingToolkit +using ModelingToolkit: t_nounits as t @parameters β γ κ η -@variables t S(t) I(t) R(t) +@variables S(t) I(t) R(t) rate₁ = β * S * I rate₂ = γ * I + t diff --git a/src/systems/diffeqs/odesystem.jl b/src/systems/diffeqs/odesystem.jl index 5bfb465794..baa82d9b6f 100644 --- a/src/systems/diffeqs/odesystem.jl +++ b/src/systems/diffeqs/odesystem.jl @@ -10,10 +10,10 @@ $(FIELDS) ```julia using ModelingToolkit +using ModelingToolkit: t_nounits as t, D_nounits as D @parameters σ ρ β -@variables t x(t) y(t) z(t) -D = Differential(t) +@variables x(t) y(t) z(t) eqs = [D(x) ~ σ*(y-x), D(y) ~ x*(ρ-z)-y, diff --git a/src/systems/diffeqs/sdesystem.jl b/src/systems/diffeqs/sdesystem.jl index df4dac37a6..86237ac51c 100644 --- a/src/systems/diffeqs/sdesystem.jl +++ b/src/systems/diffeqs/sdesystem.jl @@ -10,10 +10,10 @@ $(FIELDS) ```julia using ModelingToolkit +using ModelingToolkit: t_nounits as t, D_nounits as D @parameters σ ρ β -@variables t x(t) y(t) z(t) -D = Differential(t) +@variables x(t) y(t) z(t) eqs = [D(x) ~ σ*(y-x), D(y) ~ x*(ρ-z)-y, @@ -321,10 +321,10 @@ experiments. Springer Science & Business Media. ```julia using ModelingToolkit +using ModelingToolkit: t_nounits as t, D_nounits as D @parameters α β -@variables t x(t) y(t) z(t) -D = Differential(t) +@variables x(t) y(t) z(t) eqs = [D(x) ~ α*x] noiseeqs = [β*x] diff --git a/src/systems/jumps/jumpsystem.jl b/src/systems/jumps/jumpsystem.jl index 8e443dc1b9..cdd5f1b2f8 100644 --- a/src/systems/jumps/jumpsystem.jl +++ b/src/systems/jumps/jumpsystem.jl @@ -34,9 +34,10 @@ $(FIELDS) ```julia using ModelingToolkit, JumpProcesses +using ModelingToolkit: t_nounits as t @parameters β γ -@variables t S(t) I(t) R(t) +@variables S(t) I(t) R(t) rate₁ = β*S*I affect₁ = [S ~ S - 1, I ~ I + 1] rate₂ = γ*I diff --git a/src/systems/pde/pdesystem.jl b/src/systems/pde/pdesystem.jl index e14c59f440..7aa3f29191 100644 --- a/src/systems/pde/pdesystem.jl +++ b/src/systems/pde/pdesystem.jl @@ -11,8 +11,8 @@ $(FIELDS) ```julia using ModelingToolkit -@parameters x -@variables t u(..) +@parameters x t +@variables u(..) Dxx = Differential(x)^2 Dtt = Differential(t)^2 Dt = Differential(t) diff --git a/src/utils.jl b/src/utils.jl index 4eb7c45726..1e4971757f 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -345,7 +345,8 @@ Return a `Set` containing all variables in `x` that appear in Example: ``` -@variables t u(t) y(t) +t = ModelingToolkit.t_nounits +@variables u(t) y(t) D = Differential(t) v = ModelingToolkit.vars(D(y) ~ u) v == Set([D(y), u]) From a62167c64e84d7af6716c65e7a745f1befa9e399 Mon Sep 17 00:00:00 2001 From: Herman Sletmoen Date: Tue, 16 Jul 2024 13:03:01 +0200 Subject: [PATCH 265/316] Update more documentation --- docs/src/basics/Validation.md | 31 ++++++++++++++++++------------- src/utils.jl | 3 ++- 2 files changed, 20 insertions(+), 14 deletions(-) diff --git a/docs/src/basics/Validation.md b/docs/src/basics/Validation.md index c9c662f13b..8107dffed6 100644 --- a/docs/src/basics/Validation.md +++ b/docs/src/basics/Validation.md @@ -8,15 +8,19 @@ Units may be assigned with the following syntax. ```@example validation using ModelingToolkit, DynamicQuantities -@variables t [unit = u"s"] x(t) [unit = u"m"] g(t) w(t) [unit = "Hz"] +@parameters t [unit = u"s"] +@variables x(t) [unit = u"m"] g(t) w(t) [unit = u"Hz"] -@variables(t, [unit = u"s"], x(t), [unit = u"m"], g(t), w(t), [unit = "Hz"]) +@parameters(t, [unit = u"s"]) +@variables(x(t), [unit = u"m"], g(t), w(t), [unit = u"Hz"]) +@parameters begin + t, [unit = u"s"] +end @variables(begin - t, [unit = u"s"], x(t), [unit = u"m"], g(t), - w(t), [unit = "Hz"] + w(t), [unit = u"Hz"] end) # Simultaneously set default value (use plain numbers, not quantities) @@ -46,10 +50,10 @@ Example usage below. Note that `ModelingToolkit` does not force unit conversions ```@example validation using ModelingToolkit, DynamicQuantities -@parameters τ [unit = u"ms"] -@variables t [unit = u"ms"] E(t) [unit = u"kJ"] P(t) [unit = u"MW"] +@parameters t [unit = u"ms"] τ [unit = u"ms"] +@variables E(t) [unit = u"kJ"] P(t) [unit = u"MW"] D = Differential(t) -eqs = eqs = [D(E) ~ P - E / τ, +eqs = [D(E) ~ P - E / τ, 0 ~ P] ModelingToolkit.validate(eqs) ``` @@ -70,10 +74,10 @@ An example of an inconsistent system: at present, `ModelingToolkit` requires tha ```@example validation using ModelingToolkit, DynamicQuantities -@parameters τ [unit = u"ms"] -@variables t [unit = u"ms"] E(t) [unit = u"J"] P(t) [unit = u"MW"] +@parameters t [unit = u"ms"] τ [unit = u"ms"] +@variables E(t) [unit = u"J"] P(t) [unit = u"MW"] D = Differential(t) -eqs = eqs = [D(E) ~ P - E / τ, +eqs = [D(E) ~ P - E / τ, 0 ~ P] ModelingToolkit.validate(eqs) #Returns false while displaying a warning message ``` @@ -115,7 +119,8 @@ In order for a function to work correctly during both validation & execution, th ```julia using ModelingToolkit, DynamicQuantities -@variables t [unit = u"ms"] E(t) [unit = u"J"] P(t) [unit = u"MW"] +@parameters t [unit = u"ms"] +@variables E(t) [unit = u"J"] P(t) [unit = u"MW"] D = Differential(t) eqs = [D(E) ~ P - E / 1u"ms"] ModelingToolkit.validate(eqs) #Returns false while displaying a warning message @@ -129,8 +134,8 @@ Instead, they should be parameterized: ```@example validation3 using ModelingToolkit, DynamicQuantities -@parameters τ [unit = u"ms"] -@variables t [unit = u"ms"] E(t) [unit = u"kJ"] P(t) [unit = u"MW"] +@parameters t [unit = u"ms"] τ [unit = u"ms"] +@variables E(t) [unit = u"kJ"] P(t) [unit = u"MW"] D = Differential(t) eqs = [D(E) ~ P - E / τ] ModelingToolkit.validate(eqs) #Returns true diff --git a/src/utils.jl b/src/utils.jl index 1e4971757f..035999cebb 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -418,7 +418,8 @@ collect_differential_variables(sys) = collect_operator_variables(sys, Differenti Return a `Set` with all applied operators in `x`, example: ``` -@variables t u(t) y(t) +@parameters t +@variables u(t) y(t) D = Differential(t) eq = D(y) ~ u ModelingToolkit.collect_applied_operators(eq, Differential) == Set([D(y)]) From 8b170a2ef0d200a3371f9674a497812bba7913b7 Mon Sep 17 00:00:00 2001 From: Herman Sletmoen Date: Tue, 16 Jul 2024 13:07:12 +0200 Subject: [PATCH 266/316] Update tests --- test/abstractsystem.jl | 3 ++- test/components.jl | 4 ++-- test/constants.jl | 6 ++++-- test/direct.jl | 5 +++-- test/downstream/linearize.jl | 5 +++-- test/nonlinearsystem.jl | 8 ++++---- test/sdesystem.jl | 6 +++--- test/units.jl | 16 ++++++++-------- test/variable_parsing.jl | 3 ++- test/variable_utils.jl | 3 ++- 10 files changed, 33 insertions(+), 26 deletions(-) diff --git a/test/abstractsystem.jl b/test/abstractsystem.jl index 85379bdad6..3f31467750 100644 --- a/test/abstractsystem.jl +++ b/test/abstractsystem.jl @@ -2,7 +2,8 @@ using ModelingToolkit using Test MT = ModelingToolkit -@variables t x +@parameters t +@variables x struct MyNLS <: MT.AbstractSystem name::Any systems::Any diff --git a/test/components.jl b/test/components.jl index d9233558c3..620b911859 100644 --- a/test/components.jl +++ b/test/components.jl @@ -304,7 +304,7 @@ sol = solve(prob, Tsit5()) Hey there, Pin1! """ @connector function Pin1(; name) - @variables t + @parameters t sts = @variables v(t)=1.0 i(t)=1.0 ODESystem(Equation[], t, sts, []; name = name) end @@ -314,7 +314,7 @@ sol = solve(prob, Tsit5()) Hey there, Pin2! """ @component function Pin2(; name) - @variables t + @parameters t sts = @variables v(t)=1.0 i(t)=1.0 ODESystem(Equation[], t, sts, []; name = name) end diff --git a/test/constants.jl b/test/constants.jl index 2427638703..c78879f2e2 100644 --- a/test/constants.jl +++ b/test/constants.jl @@ -6,7 +6,8 @@ UMT = ModelingToolkit.UnitfulUnitCheck @constants a = 1 @test_throws MT.ArgumentError @constants b -@variables t x(t) w(t) +@parameters t +@variables x(t) w(t) D = Differential(t) eqs = [D(x) ~ a] @named sys = ODESystem(eqs, t) @@ -28,7 +29,8 @@ simp = structural_simplify(sys) @constants β=1 [unit = u"m/s"] UMT.get_unit(β) @test MT.isconstant(β) -@variables t [unit = u"s"] x(t) [unit = u"m"] +@parameters t [unit = u"s"] +@variables x(t) [unit = u"m"] D = Differential(t) eqs = [D(x) ~ β] @named sys = ODESystem(eqs, t) diff --git a/test/direct.jl b/test/direct.jl index b7b18f14cc..8302f068a4 100644 --- a/test/direct.jl +++ b/test/direct.jl @@ -59,7 +59,8 @@ reference_jac = sparse(ModelingToolkit.jacobian(du, [x, y, z])) findnz(reference_jac)[[1, 2]] let - @variables t x(t) y(t) z(t) + @parameters t + @variables x(t) y(t) z(t) @test ModelingToolkit.exprs_occur_in([x, y, z], x^2 * y) == [true, true, false] end @@ -196,7 +197,7 @@ test_worldage() let @register_symbolic foo(x) - @variables t + @parameters t D = Differential(t) @test isequal(expand_derivatives(D(foo(t))), D(foo(t))) diff --git a/test/downstream/linearize.jl b/test/downstream/linearize.jl index 17f06ee63c..577b529293 100644 --- a/test/downstream/linearize.jl +++ b/test/downstream/linearize.jl @@ -1,8 +1,9 @@ using ModelingToolkit, Test # r is an input, and y is an output. -@variables t x(t)=0 y(t)=0 u(t)=0 r(t)=0 -@variables t x(t)=0 y(t)=0 u(t)=0 r(t)=0 [input = true] +@parameters t +@variables x(t)=0 y(t)=0 u(t)=0 r(t)=0 +@variables x(t)=0 y(t)=0 u(t)=0 r(t)=0 [input = true] @parameters kp = 1 D = Differential(t) diff --git a/test/nonlinearsystem.jl b/test/nonlinearsystem.jl index 75c94a6d60..7a8f024a2f 100644 --- a/test/nonlinearsystem.jl +++ b/test/nonlinearsystem.jl @@ -115,7 +115,7 @@ lorenz2 = lorenz(:lorenz2) # system promotion using OrdinaryDiffEq -@variables t +@parameters t D = Differential(t) @named subsys = convert_system(ODESystem, lorenz1, t) @named sys = ODESystem([D(subsys.x) ~ subsys.x + subsys.x], t, systems = [subsys]) @@ -178,8 +178,8 @@ end end # observed variable handling -@variables t x(t) RHS(t) -@parameters τ +@parameters t τ +@variables x(t) RHS(t) @named fol = NonlinearSystem([0 ~ (1 - x * h) / τ], [x], [τ]; observed = [RHS ~ (1 - x) / τ]) @test isequal(RHS, @nonamespace fol.RHS) @@ -188,7 +188,7 @@ RHS2 = RHS @test isequal(RHS, RHS2) # issue #1358 -@variables t +@parameters t @variables v1(t) v2(t) i1(t) i2(t) eq = [v1 ~ sin(2pi * t * h) v1 - v2 ~ i1 diff --git a/test/sdesystem.jl b/test/sdesystem.jl index 3314b8a89f..835960b821 100644 --- a/test/sdesystem.jl +++ b/test/sdesystem.jl @@ -458,7 +458,7 @@ fdif!(du, u0, p, t) # issue #819 @testset "Combined system name collisions" begin - @variables t + @parameters t eqs_short = [D(x) ~ σ * (y - x), D(y) ~ x * (ρ - z) - y ] @@ -619,8 +619,8 @@ solve(prob, LambaEulerHeun(), seed = 1) # Test ill-formed due to more equations than states in noise equations -@parameters p d -@variables t X(t) +@parameters t p d +@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) diff --git a/test/units.jl b/test/units.jl index e170540270..770f36dbb6 100644 --- a/test/units.jl +++ b/test/units.jl @@ -2,8 +2,8 @@ using ModelingToolkit, OrdinaryDiffEq, JumpProcesses, Unitful using Test MT = ModelingToolkit UMT = ModelingToolkit.UnitfulUnitCheck -@parameters τ [unit = u"ms"] γ -@variables t [unit = u"ms"] E(t) [unit = u"kJ"] P(t) [unit = u"MW"] +@parameters t [unit = u"ms"] τ [unit = u"ms"] γ +@variables E(t) [unit = u"kJ"] P(t) [unit = u"MW"] D = Differential(t) #This is how equivalent works: @@ -94,8 +94,8 @@ bad_length_eqs = [connect(op, lp)] @test_throws MT.ValidationError ODESystem(bad_eqs, t, [], []; name = :sys) # Array variables -@variables t [unit = u"s"] x(t)[1:3] [unit = u"m"] -@parameters v[1:3]=[1, 2, 3] [unit = u"m/s"] +@parameters t [unit = u"s"] 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 ODESystem(eqs, t, name = :sys) @@ -109,8 +109,8 @@ eqs = [ @named nls = NonlinearSystem(eqs, [x], [a]) # SDE test w/ noise vector -@parameters τ [unit = u"ms"] Q [unit = u"MW"] -@variables t [unit = u"ms"] E(t) [unit = u"kJ"] P(t) [unit = u"MW"] +@parameters t [unit = u"ms"] τ [unit = u"ms"] Q [unit = u"MW"] +@variables E(t) [unit = u"kJ"] P(t) [unit = u"MW"] D = Differential(t) eqs = [D(E) ~ P - E / τ P ~ Q] @@ -130,8 +130,8 @@ noiseeqs = [0.1u"MW" 0.1u"MW" @test !UMT.validate(eqs, noiseeqs) # Non-trivial simplifications -@variables t [unit = u"s"] V(t) [unit = u"m"^3] L(t) [unit = u"m"] -@parameters v [unit = u"m/s"] r [unit = u"m"^3 / u"s"] +@parameters t [unit = u"s"] v [unit = u"m/s"] r [unit = u"m"^3 / u"s"] +@variables V(t) [unit = u"m"^3] L(t) [unit = u"m"] D = Differential(t) eqs = [D(L) ~ v, V ~ L^3] diff --git a/test/variable_parsing.jl b/test/variable_parsing.jl index 14c6ca543e..bfe6d83283 100644 --- a/test/variable_parsing.jl +++ b/test/variable_parsing.jl @@ -115,7 +115,8 @@ a = rename(value(x), :a) @test getmetadata(x, VariableConnectType) == Flow @test getmetadata(x, VariableUnit) == u -@variables t x(t)=1 [connect = Flow, unit = u] +@parameters t +@variables x(t)=1 [connect = Flow, unit = u] @test getmetadata(x, VariableDefaultValue) == 1 @test getmetadata(x, VariableConnectType) == Flow diff --git a/test/variable_utils.jl b/test/variable_utils.jl index 551614038b..200f61954d 100644 --- a/test/variable_utils.jl +++ b/test/variable_utils.jl @@ -18,7 +18,8 @@ new = (((1 / β - 1) + δ) / γ)^(1 / (γ - 1)) # Continuous using ModelingToolkit: isdifferential, vars, collect_differential_variables, collect_ivs -@variables t u(t) y(t) +@parameters t +@variables u(t) y(t) D = Differential(t) eq = D(y) ~ u v = vars(eq) From ded3046c2d34ca432db28e71c124315c600421a6 Mon Sep 17 00:00:00 2001 From: Herman Sletmoen Date: Tue, 16 Jul 2024 13:32:24 +0200 Subject: [PATCH 267/316] Add note in FAQ --- docs/src/basics/FAQ.md | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/docs/src/basics/FAQ.md b/docs/src/basics/FAQ.md index fa73815059..4a82117c30 100644 --- a/docs/src/basics/FAQ.md +++ b/docs/src/basics/FAQ.md @@ -186,7 +186,7 @@ p, replace, alias = SciMLStructures.canonicalize(Tunable(), prob.p) This error can come up after running `structural_simplify` on a system that generates dummy derivatives (i.e. variables with `ˍt`). For example, here even though all the variables are defined with initial values, the `ODEProblem` generation will throw an error that defaults are missing from the variable map. -``` +```julia using ModelingToolkit using ModelingToolkit: t_nounits as t, D_nounits as D @@ -202,7 +202,7 @@ prob = ODEProblem(sys, [], (0,1)) We can solve this problem by using the `missing_variable_defaults()` function -``` +```julia prob = ODEProblem(sys, ModelingToolkit.missing_variable_defaults(sys), (0,1)) ``` @@ -221,7 +221,7 @@ julia> ModelingToolkit.missing_variable_defaults(sys, [1,2,3]) Use the `u0_constructor` keyword argument to map an array to the desired container type. For example: -``` +```julia using ModelingToolkit, StaticArrays using ModelingToolkit: t_nounits as t, D_nounits as D @@ -230,3 +230,17 @@ eqs = [D(x1) ~ 1.1 * x1] @mtkbuild sys = ODESystem(eqs, t) prob = ODEProblem{false}(sys, [], (0,1); u0_constructor = x->SVector(x...)) ``` + +## Using a custom independent variable + +When possible, we recommend `using ModelingToolkit: t_nounits as t, D_nounits as D` as the independent variable and its derivative. +However, if you want to use your own, you can do so: + +```julia +using ModelingToolkit + +@parameters x # independent variables must be created as parameters +D = Differential(x) +@variables y(x) +@named sys = ODESystem([D(y) ~ x], x) +``` From 1371014db69048b65228c865b4371a187c2f713b Mon Sep 17 00:00:00 2001 From: Herman Sletmoen Date: Tue, 16 Jul 2024 13:46:52 +0200 Subject: [PATCH 268/316] Also add test from originally reported issue --- test/odesystem.jl | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/test/odesystem.jl b/test/odesystem.jl index 26afaf2cde..f12f024072 100644 --- a/test/odesystem.jl +++ b/test/odesystem.jl @@ -1196,11 +1196,19 @@ end end # https://github.com/SciML/ModelingToolkit.jl/issues/2818 -@testset "Independent variable must be a parameter" begin +@testset "Custom independent variable" begin @parameters x @variables y(x) @test_nowarn @named sys = ODESystem([y ~ 0], x) @variables x y(x) @test_throws ArgumentError @named sys = ODESystem([y ~ 0], x) + + @parameters T + D = Differential(T) + @variables x(T) + @named sys2 = ODESystem([D(x) ~ 0], T; initialization_eqs = [x ~ T], guesses = [x => 0.0]) + prob2 = ODEProblem(structural_simplify(sys2), [], (1.0, 2.0), []) + sol2 = solve(prob2) + @test all(sol2[x] .== 1.0) end From 1201d4f6ec877e0af033cd208f3e91d4e5df47ea Mon Sep 17 00:00:00 2001 From: Herman Sletmoen Date: Tue, 16 Jul 2024 13:56:35 +0200 Subject: [PATCH 269/316] Format --- test/odesystem.jl | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/test/odesystem.jl b/test/odesystem.jl index f12f024072..7a8048a34f 100644 --- a/test/odesystem.jl +++ b/test/odesystem.jl @@ -1207,7 +1207,10 @@ end @parameters T D = Differential(T) @variables x(T) - @named sys2 = ODESystem([D(x) ~ 0], T; initialization_eqs = [x ~ T], guesses = [x => 0.0]) + eqs = [D(x) ~ 0.0] + initialization_eqs = [x ~ T] + guesses = [x => 0.0] + @named sys2 = ODESystem(eqs, T; initialization_eqs, guesses) prob2 = ODEProblem(structural_simplify(sys2), [], (1.0, 2.0), []) sol2 = solve(prob2) @test all(sol2[x] .== 1.0) From fe7da521168e848bd5c225789585fb3bf641667d Mon Sep 17 00:00:00 2001 From: Herman Sletmoen Date: Tue, 16 Jul 2024 14:03:10 +0200 Subject: [PATCH 270/316] Format --- docs/src/basics/FAQ.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/src/basics/FAQ.md b/docs/src/basics/FAQ.md index 4a82117c30..975a49e006 100644 --- a/docs/src/basics/FAQ.md +++ b/docs/src/basics/FAQ.md @@ -197,13 +197,13 @@ eqs = [x1 + x2 + 1 ~ 0 2 * D(D(x1)) + D(D(x2)) + D(D(x3)) + D(x4) + 4 ~ 0] @named sys = ODESystem(eqs, t) sys = structural_simplify(sys) -prob = ODEProblem(sys, [], (0,1)) +prob = ODEProblem(sys, [], (0, 1)) ``` We can solve this problem by using the `missing_variable_defaults()` function ```julia -prob = ODEProblem(sys, ModelingToolkit.missing_variable_defaults(sys), (0,1)) +prob = ODEProblem(sys, ModelingToolkit.missing_variable_defaults(sys), (0, 1)) ``` This function provides 0 for the default values, which is a safe assumption for dummy derivatives of most models. However, the 2nd argument allows for a different default value or values to be used if needed. @@ -225,10 +225,10 @@ container type. For example: using ModelingToolkit, StaticArrays using ModelingToolkit: t_nounits as t, D_nounits as D -sts = @variables x1(t)=0.0 +sts = @variables x1(t) = 0.0 eqs = [D(x1) ~ 1.1 * x1] @mtkbuild sys = ODESystem(eqs, t) -prob = ODEProblem{false}(sys, [], (0,1); u0_constructor = x->SVector(x...)) +prob = ODEProblem{false}(sys, [], (0, 1); u0_constructor = x->SVector(x...)) ``` ## Using a custom independent variable From 27d5dfe17f46561d37ad3db37387f4ea7c469026 Mon Sep 17 00:00:00 2001 From: Herman Sletmoen Date: Tue, 16 Jul 2024 14:05:20 +0200 Subject: [PATCH 271/316] Format --- docs/src/basics/FAQ.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/basics/FAQ.md b/docs/src/basics/FAQ.md index 975a49e006..bbfd3566c8 100644 --- a/docs/src/basics/FAQ.md +++ b/docs/src/basics/FAQ.md @@ -228,7 +228,7 @@ using ModelingToolkit: t_nounits as t, D_nounits as D sts = @variables x1(t) = 0.0 eqs = [D(x1) ~ 1.1 * x1] @mtkbuild sys = ODESystem(eqs, t) -prob = ODEProblem{false}(sys, [], (0, 1); u0_constructor = x->SVector(x...)) +prob = ODEProblem{false}(sys, [], (0, 1); u0_constructor = x -> SVector(x...)) ``` ## Using a custom independent variable From f736a7857a883f4e46eee2ed0b6d79ee2210a63c Mon Sep 17 00:00:00 2001 From: Herman Sletmoen Date: Tue, 16 Jul 2024 16:36:28 +0200 Subject: [PATCH 272/316] Substitute in observed before calculating jacobian --- src/systems/nonlinear/nonlinearsystem.jl | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/systems/nonlinear/nonlinearsystem.jl b/src/systems/nonlinear/nonlinearsystem.jl index db13bb4541..445f01f10e 100644 --- a/src/systems/nonlinear/nonlinearsystem.jl +++ b/src/systems/nonlinear/nonlinearsystem.jl @@ -193,8 +193,11 @@ function calculate_jacobian(sys::NonlinearSystem; sparse = false, simplify = fal return cache[1] end - rhs = [eq.rhs for eq in equations(sys)] + # observed equations may depend on unknowns, so substitute them in first + obs = map(eq -> eq.lhs => eq.rhs, observed(sys)) + rhs = map(eq -> substitute(eq.rhs, obs), equations(sys)) vals = [dv for dv in unknowns(sys)] + if sparse jac = sparsejacobian(rhs, vals, simplify = simplify) else From 890d8593ad8def87fc9e202cd98bd8961c607aeb Mon Sep 17 00:00:00 2001 From: Herman Sletmoen Date: Tue, 16 Jul 2024 16:52:32 +0200 Subject: [PATCH 273/316] Added test --- test/nonlinearsystem.jl | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/test/nonlinearsystem.jl b/test/nonlinearsystem.jl index 75c94a6d60..099d1726bc 100644 --- a/test/nonlinearsystem.jl +++ b/test/nonlinearsystem.jl @@ -283,3 +283,32 @@ sys = structural_simplify(ns) @test length(equations(sys)) == 0 sys = structural_simplify(ns; conservative = true) @test length(equations(sys)) == 1 + +# https://github.com/SciML/ModelingToolkit.jl/issues/2858 +@testset "Jacobian with observed equations that depend on unknowns" begin + @variables x y z + @parameters σ ρ β + eqs = [ + 0 ~ σ * (y - x) + 0 ~ x * (ρ - z) - y + 0 ~ x * y - β * z + ] + guesses = [x => 1.0, y => 0.0, z => 0.0] + ps = [σ => 10.0, ρ => 26.0, β => 8 / 3] + @mtkbuild ns = NonlinearSystem(eqs) + + @test isequal(calculate_jacobian(ns), [ + (-1-z+ρ)*σ -x*σ + 2x*(-z+ρ) -β-(x^2) + ]) + + # solve without analytical jacobian + prob = NonlinearProblem(ns, guesses, ps) + sol = solve(prob, NewtonRaphson()) + @test sol.retcode == ReturnCode.Success + + # solve with analytical jacobian + prob = NonlinearProblem(ns, guesses, ps, jac = true) + sol = solve(prob, NewtonRaphson()) + @test sol.retcode == ReturnCode.Success +end From 15c44d5295ab5d18230534c6b022bda97e1550b1 Mon Sep 17 00:00:00 2001 From: Herman Sletmoen Date: Tue, 16 Jul 2024 17:06:24 +0200 Subject: [PATCH 274/316] Clean up example a bit --- docs/src/tutorials/nonlinear.md | 25 ++++++++++--------------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/docs/src/tutorials/nonlinear.md b/docs/src/tutorials/nonlinear.md index 2043bd4be1..3ccc1d9861 100644 --- a/docs/src/tutorials/nonlinear.md +++ b/docs/src/tutorials/nonlinear.md @@ -9,24 +9,19 @@ We use (unknown) variables for our nonlinear system. ```@example nonlinear using ModelingToolkit, NonlinearSolve +# Define a nonlinear system @variables x y z @parameters σ ρ β +@mtkbuild ns = NonlinearSystem([ + 0 ~ σ * (y - x) + 0 ~ x * (ρ - z) - y + 0 ~ x * y - β * z +]) -# Define a nonlinear system -eqs = [0 ~ σ * (y - x), - 0 ~ x * (ρ - z) - y, - 0 ~ x * y - β * z] -@mtkbuild ns = NonlinearSystem(eqs, [x, y, z], [σ, ρ, β]) - -guess = [x => 1.0, - y => 0.0, - z => 0.0] - -ps = [σ => 10.0 - ρ => 26.0 - β => 8 / 3] +guesses = [x => 1.0, y => 0.0, z => 0.0] +ps = [σ => 10.0, ρ => 26.0, β => 8 / 3] -prob = NonlinearProblem(ns, guess, ps) +prob = NonlinearProblem(ns, guesses, ps) sol = solve(prob, NewtonRaphson()) ``` @@ -34,6 +29,6 @@ We can similarly ask to generate the `NonlinearProblem` with the analytical Jacobian function: ```@example nonlinear -prob = NonlinearProblem(ns, guess, ps, jac = true) +prob = NonlinearProblem(ns, guesses, ps, jac = true) sol = solve(prob, NewtonRaphson()) ``` From b477f736b30bee9e41ca7b9e95490cf21c190805 Mon Sep 17 00:00:00 2001 From: Herman Sletmoen Date: Tue, 16 Jul 2024 17:12:13 +0200 Subject: [PATCH 275/316] Add two TODOs --- src/systems/nonlinear/nonlinearsystem.jl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/systems/nonlinear/nonlinearsystem.jl b/src/systems/nonlinear/nonlinearsystem.jl index 445f01f10e..7459f3cd8b 100644 --- a/src/systems/nonlinear/nonlinearsystem.jl +++ b/src/systems/nonlinear/nonlinearsystem.jl @@ -194,6 +194,8 @@ function calculate_jacobian(sys::NonlinearSystem; sparse = false, simplify = fal end # observed equations may depend on unknowns, so substitute them in first + # TODO: must do the same fix in e.g. calculate_hessian? + # TODO: rather keep observed derivatives unexpanded, like "Differential(obs)(expr)"? obs = map(eq -> eq.lhs => eq.rhs, observed(sys)) rhs = map(eq -> substitute(eq.rhs, obs), equations(sys)) vals = [dv for dv in unknowns(sys)] From 597a56bca04be92b1002d3b9c8ef03c9209c9a87 Mon Sep 17 00:00:00 2001 From: Herman Sletmoen Date: Tue, 16 Jul 2024 17:31:26 +0200 Subject: [PATCH 276/316] Format --- test/nonlinearsystem.jl | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/test/nonlinearsystem.jl b/test/nonlinearsystem.jl index 099d1726bc..fb4acb618a 100644 --- a/test/nonlinearsystem.jl +++ b/test/nonlinearsystem.jl @@ -288,20 +288,15 @@ sys = structural_simplify(ns; conservative = true) @testset "Jacobian with observed equations that depend on unknowns" begin @variables x y z @parameters σ ρ β - eqs = [ - 0 ~ σ * (y - x) - 0 ~ x * (ρ - z) - y - 0 ~ x * y - β * z - ] + eqs = [0 ~ σ * (y - x) + 0 ~ x * (ρ - z) - y + 0 ~ x * y - β * z] guesses = [x => 1.0, y => 0.0, z => 0.0] ps = [σ => 10.0, ρ => 26.0, β => 8 / 3] @mtkbuild ns = NonlinearSystem(eqs) - @test isequal(calculate_jacobian(ns), [ - (-1-z+ρ)*σ -x*σ - 2x*(-z+ρ) -β-(x^2) - ]) - + @test isequal(calculate_jacobian(ns), [(-1 - z + ρ)*σ -x*σ + 2x*(-z + ρ) -β-(x^2)]) # solve without analytical jacobian prob = NonlinearProblem(ns, guesses, ps) sol = solve(prob, NewtonRaphson()) From dbd2dc5ce96f0a6700c4111b2e179a6e070bc435 Mon Sep 17 00:00:00 2001 From: Herman Sletmoen Date: Tue, 16 Jul 2024 17:34:38 +0200 Subject: [PATCH 277/316] Clean up my own mess --- docs/src/tutorials/nonlinear.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/docs/src/tutorials/nonlinear.md b/docs/src/tutorials/nonlinear.md index 3ccc1d9861..fc525cc988 100644 --- a/docs/src/tutorials/nonlinear.md +++ b/docs/src/tutorials/nonlinear.md @@ -12,11 +12,12 @@ using ModelingToolkit, NonlinearSolve # Define a nonlinear system @variables x y z @parameters σ ρ β -@mtkbuild ns = NonlinearSystem([ - 0 ~ σ * (y - x) - 0 ~ x * (ρ - z) - y - 0 ~ x * y - β * z -]) +eqs = [0 ~ σ * (y - x) + 0 ~ x * (ρ - z) - y + 0 ~ x * y - β * z] +guesses = [x => 1.0, y => 0.0, z => 0.0] +ps = [σ => 10.0, ρ => 26.0, β => 8 / 3] +@mtkbuild ns = NonlinearSystem(eqs) guesses = [x => 1.0, y => 0.0, z => 0.0] ps = [σ => 10.0, ρ => 26.0, β => 8 / 3] From 7d9d1fda64c8ac040dbb5892bb2463a63065f18b Mon Sep 17 00:00:00 2001 From: Herman Sletmoen Date: Tue, 16 Jul 2024 17:56:35 +0200 Subject: [PATCH 278/316] Use fixpoint_sub --- src/systems/nonlinear/nonlinearsystem.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/systems/nonlinear/nonlinearsystem.jl b/src/systems/nonlinear/nonlinearsystem.jl index 7459f3cd8b..e1ad9959af 100644 --- a/src/systems/nonlinear/nonlinearsystem.jl +++ b/src/systems/nonlinear/nonlinearsystem.jl @@ -196,8 +196,8 @@ function calculate_jacobian(sys::NonlinearSystem; sparse = false, simplify = fal # observed equations may depend on unknowns, so substitute them in first # TODO: must do the same fix in e.g. calculate_hessian? # TODO: rather keep observed derivatives unexpanded, like "Differential(obs)(expr)"? - obs = map(eq -> eq.lhs => eq.rhs, observed(sys)) - rhs = map(eq -> substitute(eq.rhs, obs), equations(sys)) + obs = map(eq -> eq.lhs => eq.rhs, observed(sys)) |> todict + rhs = map(eq -> fixpoint_sub(eq.rhs, obs), equations(sys)) vals = [dv for dv in unknowns(sys)] if sparse From d68cc621bacf7cad08f01abaf95964ada55407db Mon Sep 17 00:00:00 2001 From: Herman Sletmoen Date: Tue, 16 Jul 2024 19:23:59 +0200 Subject: [PATCH 279/316] Add test that requires substitution of chained observeds --- test/nonlinearsystem.jl | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/test/nonlinearsystem.jl b/test/nonlinearsystem.jl index fb4acb618a..19ebc12374 100644 --- a/test/nonlinearsystem.jl +++ b/test/nonlinearsystem.jl @@ -306,4 +306,13 @@ sys = structural_simplify(ns; conservative = true) prob = NonlinearProblem(ns, guesses, ps, jac = true) sol = solve(prob, NewtonRaphson()) @test sol.retcode == ReturnCode.Success + + # system that contains a chain of observed variables when simplified + @variables x y z + eqs = [0 ~ x^2 + 2*z + 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 + @test isequal(expand.(calculate_jacobian(ns)), [3//2 + y;;]) + 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 end From d24e326297131d9f4e042038abfed59e90c3543f Mon Sep 17 00:00:00 2001 From: Herman Sletmoen Date: Tue, 16 Jul 2024 19:40:43 +0200 Subject: [PATCH 280/316] Handle case without observed equations --- src/systems/nonlinear/nonlinearsystem.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/systems/nonlinear/nonlinearsystem.jl b/src/systems/nonlinear/nonlinearsystem.jl index e1ad9959af..ba6ee21ef6 100644 --- a/src/systems/nonlinear/nonlinearsystem.jl +++ b/src/systems/nonlinear/nonlinearsystem.jl @@ -196,7 +196,7 @@ function calculate_jacobian(sys::NonlinearSystem; sparse = false, simplify = fal # observed equations may depend on unknowns, so substitute them in first # TODO: must do the same fix in e.g. calculate_hessian? # TODO: rather keep observed derivatives unexpanded, like "Differential(obs)(expr)"? - obs = map(eq -> eq.lhs => eq.rhs, observed(sys)) |> todict + 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)] From a1a17a6785d46a8a029637101246827b47b36e39 Mon Sep 17 00:00:00 2001 From: Herman Sletmoen Date: Tue, 16 Jul 2024 19:45:53 +0200 Subject: [PATCH 281/316] Do the same for the Hessian --- src/systems/nonlinear/nonlinearsystem.jl | 3 ++- test/nonlinearsystem.jl | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/systems/nonlinear/nonlinearsystem.jl b/src/systems/nonlinear/nonlinearsystem.jl index ba6ee21ef6..f980ad5d5d 100644 --- a/src/systems/nonlinear/nonlinearsystem.jl +++ b/src/systems/nonlinear/nonlinearsystem.jl @@ -220,7 +220,8 @@ function generate_jacobian( end function calculate_hessian(sys::NonlinearSystem; sparse = false, simplify = false) - rhs = [eq.rhs for eq in equations(sys)] + 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)] diff --git a/test/nonlinearsystem.jl b/test/nonlinearsystem.jl index 19ebc12374..11ebaaf32b 100644 --- a/test/nonlinearsystem.jl +++ b/test/nonlinearsystem.jl @@ -285,7 +285,7 @@ sys = structural_simplify(ns; conservative = true) @test length(equations(sys)) == 1 # https://github.com/SciML/ModelingToolkit.jl/issues/2858 -@testset "Jacobian with observed equations that depend on unknowns" begin +@testset "Jacobian/Hessian with observed equations that depend on unknowns" begin @variables x y z @parameters σ ρ β eqs = [0 ~ σ * (y - x) @@ -312,6 +312,7 @@ sys = structural_simplify(ns; conservative = true) eqs = [0 ~ x^2 + 2*z + 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 @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 sol = solve(prob, NewtonRaphson()) @test sol[x] ≈ sol[y] ≈ sol[z] ≈ -3 From ce3d5972850abaf907a5557c2fbd45d043ea92ed Mon Sep 17 00:00:00 2001 From: Herman Sletmoen Date: Tue, 16 Jul 2024 19:48:48 +0200 Subject: [PATCH 282/316] Format --- test/nonlinearsystem.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/nonlinearsystem.jl b/test/nonlinearsystem.jl index 11ebaaf32b..7d982ff7da 100644 --- a/test/nonlinearsystem.jl +++ b/test/nonlinearsystem.jl @@ -309,9 +309,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 + 2*z + y, z ~ y, y ~ x] # analytical solution x = y = z = 0 or -3 + 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 - @test isequal(expand.(calculate_jacobian(ns)), [3//2 + 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 sol = solve(prob, NewtonRaphson()) From 3088c80794fd9b03c4f41faa17ff1fb720c82d32 Mon Sep 17 00:00:00 2001 From: Herman Sletmoen Date: Tue, 16 Jul 2024 19:50:33 +0200 Subject: [PATCH 283/316] Removed TODO --- src/systems/nonlinear/nonlinearsystem.jl | 1 - 1 file changed, 1 deletion(-) diff --git a/src/systems/nonlinear/nonlinearsystem.jl b/src/systems/nonlinear/nonlinearsystem.jl index f980ad5d5d..43d4bc8cc5 100644 --- a/src/systems/nonlinear/nonlinearsystem.jl +++ b/src/systems/nonlinear/nonlinearsystem.jl @@ -194,7 +194,6 @@ function calculate_jacobian(sys::NonlinearSystem; sparse = false, simplify = fal end # observed equations may depend on unknowns, so substitute them in first - # TODO: must do the same fix in e.g. calculate_hessian? # 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)) From e0e3612c6ed746b4fcb687adb62bfd16ec851108 Mon Sep 17 00:00:00 2001 From: Herman Sletmoen Date: Tue, 16 Jul 2024 20:05:48 +0200 Subject: [PATCH 284/316] Delegate getdefault(var) to Symbolics, which already errors if var has no default --- src/utils.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils.jl b/src/utils.jl index dc239e34c1..4ee221e14f 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -217,7 +217,7 @@ function iv_from_nested_derivative(x, op = Differential) end hasdefault(v) = hasmetadata(v, Symbolics.VariableDefaultValue) -getdefault(v) = value(getmetadata(v, Symbolics.VariableDefaultValue)) +getdefault(v) = value(Symbolics.getdefaultval(v)) function getdefaulttype(v) def = value(getmetadata(unwrap(v), Symbolics.VariableDefaultValue, nothing)) def === nothing ? Float64 : typeof(def) From 170d7892c4463ab2d43d842ce96682fa08a6eb8f Mon Sep 17 00:00:00 2001 From: Herman Sletmoen Date: Tue, 16 Jul 2024 20:06:18 +0200 Subject: [PATCH 285/316] Add tests --- test/variable_parsing.jl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/variable_parsing.jl b/test/variable_parsing.jl index 14c6ca543e..e256611dc4 100644 --- a/test/variable_parsing.jl +++ b/test/variable_parsing.jl @@ -104,6 +104,7 @@ end y = 2, [connect = Flow] end +@test_throws ErrorException ModelingToolkit.getdefault(x) @test !hasmetadata(x, VariableDefaultValue) @test getmetadata(x, VariableConnectType) == Flow @test getmetadata(x, VariableUnit) == u @@ -111,6 +112,7 @@ end @test getmetadata(y, VariableConnectType) == Flow a = rename(value(x), :a) +@test_throws ErrorException ModelingToolkit.getdefault(x) @test !hasmetadata(x, VariableDefaultValue) @test getmetadata(x, VariableConnectType) == Flow @test getmetadata(x, VariableUnit) == u From a3ad348eec1c2b8dbf12c5fb5d02493f0747b4c5 Mon Sep 17 00:00:00 2001 From: Herman Sletmoen Date: Tue, 16 Jul 2024 20:08:12 +0200 Subject: [PATCH 286/316] Correct test to test what it was intended to test --- test/variable_parsing.jl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/variable_parsing.jl b/test/variable_parsing.jl index e256611dc4..1a5b0ed221 100644 --- a/test/variable_parsing.jl +++ b/test/variable_parsing.jl @@ -112,10 +112,10 @@ end @test getmetadata(y, VariableConnectType) == Flow a = rename(value(x), :a) -@test_throws ErrorException ModelingToolkit.getdefault(x) -@test !hasmetadata(x, VariableDefaultValue) -@test getmetadata(x, VariableConnectType) == Flow -@test getmetadata(x, VariableUnit) == u +@test_throws ErrorException ModelingToolkit.getdefault(a) +@test !hasmetadata(a, VariableDefaultValue) +@test getmetadata(a, VariableConnectType) == Flow +@test getmetadata(a, VariableUnit) == u @variables t x(t)=1 [connect = Flow, unit = u] From 47cdd3b3af7f1b0b6cb0b1a2ab659c8cdb489f8c Mon Sep 17 00:00:00 2001 From: Herman Sletmoen Date: Tue, 16 Jul 2024 21:15:45 +0200 Subject: [PATCH 287/316] Check that equations contain variables --- src/utils.jl | 19 ++++++++++++++++++- test/odesystem.jl | 9 ++++++++- 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/src/utils.jl b/src/utils.jl index 4ee221e14f..47012b012c 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -171,7 +171,7 @@ end """ check_equations(eqs, iv) -Assert that equations are well-formed when building ODE, i.e., only containing a single independent variable. +Assert that ODE equations are well-formed. """ function check_equations(eqs, iv) ivs = collect_ivs(eqs) @@ -183,6 +183,17 @@ function check_equations(eqs, iv) isequal(single_iv, iv) || throw(ArgumentError("Differential w.r.t. variable ($single_iv) other than the independent variable ($iv) are not allowed.")) end + + for eq in eqs + vars, pars = collect_vars(eq, iv) + if isempty(vars) + if isempty(pars) + throw(ArgumentError("Equation $eq contains no variables or parameters.")) + else + throw(ArgumentError("Equation $eq contains only parameters, but relationships between parameters should be specified with defaults or parameter_dependencies.")) + end + end + end end """ Get all the independent variables with respect to which differentials are taken. @@ -439,6 +450,12 @@ function find_derivatives!(vars, expr, f) return vars end +function collect_vars(args...; kwargs...) + unknowns, parameters = [], [] + collect_vars!(unknowns, parameters, args...; kwargs...) + return unknowns, parameters +end + function collect_vars!(unknowns, parameters, expr, iv; op = Differential) if issym(expr) collect_var!(unknowns, parameters, expr, iv) diff --git a/test/odesystem.jl b/test/odesystem.jl index ab28f5cb47..04331008a1 100644 --- a/test/odesystem.jl +++ b/test/odesystem.jl @@ -420,7 +420,14 @@ der = Differential(w) eqs = [ der(u1) ~ t ] -@test_throws ArgumentError ModelingToolkit.ODESystem(eqs, t, vars, pars, name = :foo) +@test_throws ArgumentError ODESystem(eqs, t, vars, pars, name = :foo) + + # equations without variables are forbidden + # https://github.com/SciML/ModelingToolkit.jl/issues/2727 +@parameters p q +@test_throws ArgumentError ODESystem([p ~ q], t; name = :foo) +@test_throws ArgumentError ODESystem([p ~ 1], t; name = :foo) +@test_throws ArgumentError ODESystem([1 ~ 2], t; name = :foo) @variables x(t) @parameters M b k From 836bc2e50498a872f2c45d0db4ee8c39d1f8b7a8 Mon Sep 17 00:00:00 2001 From: Herman Sletmoen Date: Tue, 16 Jul 2024 21:27:32 +0200 Subject: [PATCH 288/316] Revert "Check that equations contain variables" (commited to wrong branch and pushed it...) This reverts commit 47cdd3b3af7f1b0b6cb0b1a2ab659c8cdb489f8c. --- src/utils.jl | 19 +------------------ test/odesystem.jl | 9 +-------- 2 files changed, 2 insertions(+), 26 deletions(-) diff --git a/src/utils.jl b/src/utils.jl index 47012b012c..4ee221e14f 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -171,7 +171,7 @@ end """ check_equations(eqs, iv) -Assert that ODE equations are well-formed. +Assert that equations are well-formed when building ODE, i.e., only containing a single independent variable. """ function check_equations(eqs, iv) ivs = collect_ivs(eqs) @@ -183,17 +183,6 @@ function check_equations(eqs, iv) isequal(single_iv, iv) || throw(ArgumentError("Differential w.r.t. variable ($single_iv) other than the independent variable ($iv) are not allowed.")) end - - for eq in eqs - vars, pars = collect_vars(eq, iv) - if isempty(vars) - if isempty(pars) - throw(ArgumentError("Equation $eq contains no variables or parameters.")) - else - throw(ArgumentError("Equation $eq contains only parameters, but relationships between parameters should be specified with defaults or parameter_dependencies.")) - end - end - end end """ Get all the independent variables with respect to which differentials are taken. @@ -450,12 +439,6 @@ function find_derivatives!(vars, expr, f) return vars end -function collect_vars(args...; kwargs...) - unknowns, parameters = [], [] - collect_vars!(unknowns, parameters, args...; kwargs...) - return unknowns, parameters -end - function collect_vars!(unknowns, parameters, expr, iv; op = Differential) if issym(expr) collect_var!(unknowns, parameters, expr, iv) diff --git a/test/odesystem.jl b/test/odesystem.jl index 04331008a1..ab28f5cb47 100644 --- a/test/odesystem.jl +++ b/test/odesystem.jl @@ -420,14 +420,7 @@ der = Differential(w) eqs = [ der(u1) ~ t ] -@test_throws ArgumentError ODESystem(eqs, t, vars, pars, name = :foo) - - # equations without variables are forbidden - # https://github.com/SciML/ModelingToolkit.jl/issues/2727 -@parameters p q -@test_throws ArgumentError ODESystem([p ~ q], t; name = :foo) -@test_throws ArgumentError ODESystem([p ~ 1], t; name = :foo) -@test_throws ArgumentError ODESystem([1 ~ 2], t; name = :foo) +@test_throws ArgumentError ModelingToolkit.ODESystem(eqs, t, vars, pars, name = :foo) @variables x(t) @parameters M b k From a9e0c6b3e2f6bdf40d53d291e462a9f710551a27 Mon Sep 17 00:00:00 2001 From: Herman Sletmoen Date: Tue, 16 Jul 2024 23:36:52 +0200 Subject: [PATCH 289/316] Update old test --- test/model_parsing.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/model_parsing.jl b/test/model_parsing.jl index d8665190c8..7ca5618786 100644 --- a/test/model_parsing.jl +++ b/test/model_parsing.jl @@ -263,7 +263,7 @@ end @test getdefault(model.cval) == 1 @test isequal(getdefault(model.c), model.cval + model.jval) @test getdefault(model.d) == 2 - @test_throws KeyError getdefault(model.e) + @test_throws ErrorException getdefault(model.e) @test getdefault(model.f) == 3 @test getdefault(model.i) == 4 @test all(getdefault.(scalarize(model.b2)) .== [1, 3]) From 9ac9ba423091d42ddf0aa798ca1bd1aa799121dd Mon Sep 17 00:00:00 2001 From: Herman Sletmoen Date: Wed, 17 Jul 2024 11:23:03 +0200 Subject: [PATCH 290/316] Add utility function that unites two things that may be nothing --- src/systems/abstractsystem.jl | 5 +---- src/utils.jl | 11 +++++++++++ 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/systems/abstractsystem.jl b/src/systems/abstractsystem.jl index 6cdcd72855..c9518bc2af 100644 --- a/src/systems/abstractsystem.jl +++ b/src/systems/abstractsystem.jl @@ -2521,10 +2521,7 @@ function extend(sys::AbstractSystem, basesys::AbstractSystem; name::Symbol = nam eqs = union(get_eqs(basesys), get_eqs(sys)) sts = union(get_unknowns(basesys), get_unknowns(sys)) ps = union(get_ps(basesys), get_ps(sys)) - base_deps = parameter_dependencies(basesys) - deps = parameter_dependencies(sys) - dep_ps = isnothing(base_deps) ? deps : - isnothing(deps) ? base_deps : union(base_deps, deps) + dep_ps = union_nothing(parameter_dependencies(basesys), parameter_dependencies(sys)) obs = union(get_observed(basesys), get_observed(sys)) cevs = union(get_continuous_events(basesys), get_continuous_events(sys)) devs = union(get_discrete_events(basesys), get_discrete_events(sys)) diff --git a/src/utils.jl b/src/utils.jl index dc239e34c1..dc6f0a0e6f 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -1,3 +1,14 @@ +""" + union_nothing(x::Union{T1, Nothing}, y::Union{T2, Nothing}) where {T1, T2} + +Unite x and y gracefully when they could be nothing. If neither is nothing, x and y are united normally. If one is nothing, the other is returned unmodified. If both are nothing, nothing is returned. +""" +function union_nothing(x::Union{T1, Nothing}, y::Union{T2, Nothing}) where {T1, T2} + isnothing(x) && return y # y can be nothing or something + isnothing(y) && return x # x can be nothing or something + return union(x, y) # both x and y are something and can be united normally +end + get_iv(D::Differential) = D.x function make_operation(@nospecialize(op), args) From bcc781b42bca4ba83c018357eb7c60f165ab9f3b Mon Sep 17 00:00:00 2001 From: Herman Sletmoen Date: Wed, 17 Jul 2024 11:24:00 +0200 Subject: [PATCH 291/316] Extend metadata, too --- src/systems/abstractsystem.jl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/systems/abstractsystem.jl b/src/systems/abstractsystem.jl index c9518bc2af..2798349ae6 100644 --- a/src/systems/abstractsystem.jl +++ b/src/systems/abstractsystem.jl @@ -2526,10 +2526,11 @@ function extend(sys::AbstractSystem, basesys::AbstractSystem; name::Symbol = nam cevs = union(get_continuous_events(basesys), get_continuous_events(sys)) devs = union(get_discrete_events(basesys), get_discrete_events(sys)) defs = merge(get_defaults(basesys), get_defaults(sys)) # prefer `sys` + meta = union_nothing(get_metadata(basesys), get_metadata(sys)) syss = union(get_systems(basesys), get_systems(sys)) args = length(ivs) == 0 ? (eqs, sts, ps) : (eqs, ivs[1], sts, ps) kwargs = (parameter_dependencies = dep_ps, observed = obs, continuous_events = cevs, - discrete_events = devs, defaults = defs, systems = syss, + discrete_events = devs, defaults = defs, systems = syss, metadata = meta, name = name, gui_metadata = gui_metadata) # collect fields specific to some system types From bac931ddc7ec6c988b55f4036a2c21331d532451 Mon Sep 17 00:00:00 2001 From: Herman Sletmoen Date: Wed, 17 Jul 2024 11:24:12 +0200 Subject: [PATCH 292/316] Test metadata extension --- test/odesystem.jl | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/test/odesystem.jl b/test/odesystem.jl index ab28f5cb47..e8c3414cf5 100644 --- a/test/odesystem.jl +++ b/test/odesystem.jl @@ -1194,3 +1194,17 @@ end @test_nowarn obsfn(buffer, [1.0], ps..., 3.0) @test buffer ≈ [2.0, 3.0, 4.0] end + +# https://github.com/SciML/ModelingToolkit.jl/issues/2502 +@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) + @test ModelingToolkit.get_metadata(extend(A1, B1)) == nothing + @test ModelingToolkit.get_metadata(extend(A1, B2)) == B + @test ModelingToolkit.get_metadata(extend(A2, B1)) == A + @test Set(ModelingToolkit.get_metadata(extend(A2, B2))) == Set(A ∪ B) +end \ No newline at end of file From 3c101cfb3bbac25b700b17d362d75bc627a42ca8 Mon Sep 17 00:00:00 2001 From: Herman Sletmoen Date: Wed, 17 Jul 2024 11:25:46 +0200 Subject: [PATCH 293/316] Format --- test/odesystem.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/odesystem.jl b/test/odesystem.jl index e8c3414cf5..d7b22d30b2 100644 --- a/test/odesystem.jl +++ b/test/odesystem.jl @@ -1207,4 +1207,4 @@ end @test ModelingToolkit.get_metadata(extend(A1, B2)) == B @test ModelingToolkit.get_metadata(extend(A2, B1)) == A @test Set(ModelingToolkit.get_metadata(extend(A2, B2))) == Set(A ∪ B) -end \ No newline at end of file +end From e6be2a7dc30829912f103af150e49bfefbf5690c Mon Sep 17 00:00:00 2001 From: Herman Sletmoen Date: Wed, 17 Jul 2024 12:02:50 +0200 Subject: [PATCH 294/316] Format --- test/odesystem.jl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/odesystem.jl b/test/odesystem.jl index 37a7d08dab..d54baa8a93 100644 --- a/test/odesystem.jl +++ b/test/odesystem.jl @@ -1203,9 +1203,9 @@ end @named B1 = ODESystem(Equation[], t, [], []) @named A2 = ODESystem(Equation[], t, [], []; metadata = A) @named B2 = ODESystem(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 + @test ModelingToolkit.get_metadata(extend(A1, B1)) == nothing + @test ModelingToolkit.get_metadata(extend(A1, B2)) == B + @test ModelingToolkit.get_metadata(extend(A2, B1)) == A @test Set(ModelingToolkit.get_metadata(extend(A2, B2))) == Set(A ∪ B) end From 20bb3c65d47c6a6b2860132d8499f02a8f19e28e Mon Sep 17 00:00:00 2001 From: Herman Sletmoen Date: Wed, 17 Jul 2024 14:05:56 +0200 Subject: [PATCH 295/316] Add @independent_variables macro (just passes through @parameters, for now) --- src/ModelingToolkit.jl | 3 ++- src/independent_variables.jl | 11 +++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) create mode 100644 src/independent_variables.jl diff --git a/src/ModelingToolkit.jl b/src/ModelingToolkit.jl index 5be9e6dbb2..c22bc5a239 100644 --- a/src/ModelingToolkit.jl +++ b/src/ModelingToolkit.jl @@ -127,6 +127,7 @@ using .BipartiteGraphs include("variables.jl") include("parameters.jl") +include("independent_variables.jl") include("constants.jl") include("utils.jl") @@ -262,7 +263,7 @@ export generate_initializesystem export alg_equations, diff_equations, has_alg_equations, has_diff_equations export get_alg_eqs, get_diff_eqs, has_alg_eqs, has_diff_eqs -export @variables, @parameters, @constants, @brownian +export @variables, @parameters, @independent_variables, @constants, @brownian export @named, @nonamespace, @namespace, extend, compose, complete export debug_system diff --git a/src/independent_variables.jl b/src/independent_variables.jl new file mode 100644 index 0000000000..beff5cc2b0 --- /dev/null +++ b/src/independent_variables.jl @@ -0,0 +1,11 @@ +""" + @independent_variables t₁ t₂ ... + +Define one or more independent variables. For example: + + @independent_variables t + @variables x(t) +""" +macro independent_variables(ts...) + :(@parameters $(ts...)) |> esc # TODO: treat independent variables separately from variables and parameters +end From 2e714852ad9cb53f6da415d3fdd17d8f3a3e0c1d Mon Sep 17 00:00:00 2001 From: Herman Sletmoen Date: Wed, 17 Jul 2024 14:10:01 +0200 Subject: [PATCH 296/316] Define t_nounits, t_unitful and t with @independent_variables --- src/ModelingToolkit.jl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/ModelingToolkit.jl b/src/ModelingToolkit.jl index c22bc5a239..06bc5584a1 100644 --- a/src/ModelingToolkit.jl +++ b/src/ModelingToolkit.jl @@ -184,13 +184,13 @@ for S in subtypes(ModelingToolkit.AbstractSystem) end const t_nounits = let - only(@parameters t) + only(@independent_variables t) end const t_unitful = let - only(@parameters t [unit = Unitful.u"s"]) + only(@independent_variables t [unit = Unitful.u"s"]) end const t = let - only(@parameters t [unit = DQ.u"s"]) + only(@independent_variables t [unit = DQ.u"s"]) end const D_nounits = Differential(t_nounits) From 9fd20fdd111d43dc19fb1f1237ceae4bd1ff7988 Mon Sep 17 00:00:00 2001 From: Herman Sletmoen Date: Wed, 17 Jul 2024 14:36:06 +0200 Subject: [PATCH 297/316] Change most @parameters t to use @independent_variables t --- docs/src/basics/Validation.md | 13 ++++++++----- src/systems/diffeqs/basic_transformations.jl | 3 ++- src/systems/diffeqs/modelingtoolkitize.jl | 2 +- src/utils.jl | 2 +- test/abstractsystem.jl | 2 +- test/basic_transformations.jl | 3 ++- test/components.jl | 4 ++-- test/constants.jl | 4 ++-- test/direct.jl | 6 +++--- test/downstream/linearization_dd.jl | 2 +- test/downstream/linearize.jl | 2 +- test/inputoutput.jl | 3 ++- test/linearity.jl | 3 ++- test/nonlinearsystem.jl | 11 ++++++----- test/optimizationsystem.jl | 2 +- test/precompile_test/ODEPrecompileTest.jl | 3 ++- test/sdesystem.jl | 6 ++++-- test/simplify.jl | 5 +++-- test/test_variable_metadata.jl | 4 ++-- test/units.jl | 12 ++++++++---- test/variable_parsing.jl | 6 +++--- test/variable_scope.jl | 5 +++-- test/variable_utils.jl | 2 +- 23 files changed, 61 insertions(+), 44 deletions(-) diff --git a/docs/src/basics/Validation.md b/docs/src/basics/Validation.md index 8107dffed6..79c5d0d214 100644 --- a/docs/src/basics/Validation.md +++ b/docs/src/basics/Validation.md @@ -8,7 +8,7 @@ Units may be assigned with the following syntax. ```@example validation using ModelingToolkit, DynamicQuantities -@parameters t [unit = u"s"] +@independent_variables t [unit = u"s"] @variables x(t) [unit = u"m"] g(t) w(t) [unit = u"Hz"] @parameters(t, [unit = u"s"]) @@ -50,7 +50,8 @@ Example usage below. Note that `ModelingToolkit` does not force unit conversions ```@example validation using ModelingToolkit, DynamicQuantities -@parameters t [unit = u"ms"] τ [unit = u"ms"] +@independent_variables t [unit = u"ms"] +@parameters τ [unit = u"ms"] @variables E(t) [unit = u"kJ"] P(t) [unit = u"MW"] D = Differential(t) eqs = [D(E) ~ P - E / τ, @@ -74,7 +75,8 @@ An example of an inconsistent system: at present, `ModelingToolkit` requires tha ```@example validation using ModelingToolkit, DynamicQuantities -@parameters t [unit = u"ms"] τ [unit = u"ms"] +@independent_variables t [unit = u"ms"] +@parameters τ [unit = u"ms"] @variables E(t) [unit = u"J"] P(t) [unit = u"MW"] D = Differential(t) eqs = [D(E) ~ P - E / τ, @@ -119,7 +121,7 @@ In order for a function to work correctly during both validation & execution, th ```julia using ModelingToolkit, DynamicQuantities -@parameters t [unit = u"ms"] +@independent_variables t [unit = u"ms"] @variables E(t) [unit = u"J"] P(t) [unit = u"MW"] D = Differential(t) eqs = [D(E) ~ P - E / 1u"ms"] @@ -134,7 +136,8 @@ Instead, they should be parameterized: ```@example validation3 using ModelingToolkit, DynamicQuantities -@parameters t [unit = u"ms"] τ [unit = u"ms"] +@independent_variables t [unit = u"ms"] +@parameters τ [unit = u"ms"] @variables E(t) [unit = u"kJ"] P(t) [unit = u"MW"] D = Differential(t) eqs = [D(E) ~ P - E / τ] diff --git a/src/systems/diffeqs/basic_transformations.jl b/src/systems/diffeqs/basic_transformations.jl index 61682c806e..e2be889c5e 100644 --- a/src/systems/diffeqs/basic_transformations.jl +++ b/src/systems/diffeqs/basic_transformations.jl @@ -16,7 +16,8 @@ Example: ```julia using ModelingToolkit, OrdinaryDiffEq, Test -@parameters t α β γ δ +@independent_variables t +@parameters α β γ δ @variables x(t) y(t) D = Differential(t) diff --git a/src/systems/diffeqs/modelingtoolkitize.jl b/src/systems/diffeqs/modelingtoolkitize.jl index 34cae293b2..b72a78add9 100644 --- a/src/systems/diffeqs/modelingtoolkitize.jl +++ b/src/systems/diffeqs/modelingtoolkitize.jl @@ -242,7 +242,7 @@ Generate `SDESystem`, 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) - @parameters t + @independent_variables t p = prob.p has_p = !(p isa Union{DiffEqBase.NullParameters, Nothing}) diff --git a/src/utils.jl b/src/utils.jl index 7cb7e9a646..74f50a45ea 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -418,7 +418,7 @@ collect_differential_variables(sys) = collect_operator_variables(sys, Differenti Return a `Set` with all applied operators in `x`, example: ``` -@parameters t +@independent_variables t @variables u(t) y(t) D = Differential(t) eq = D(y) ~ u diff --git a/test/abstractsystem.jl b/test/abstractsystem.jl index 3f31467750..bd5b6fe542 100644 --- a/test/abstractsystem.jl +++ b/test/abstractsystem.jl @@ -2,7 +2,7 @@ using ModelingToolkit using Test MT = ModelingToolkit -@parameters t +@independent_variables t @variables x struct MyNLS <: MT.AbstractSystem name::Any diff --git a/test/basic_transformations.jl b/test/basic_transformations.jl index b174e37f3a..d9b59408e6 100644 --- a/test/basic_transformations.jl +++ b/test/basic_transformations.jl @@ -1,6 +1,7 @@ using ModelingToolkit, OrdinaryDiffEq, Test -@parameters t α β γ δ +@independent_variables t +@parameters α β γ δ @variables x(t) y(t) D = Differential(t) diff --git a/test/components.jl b/test/components.jl index 620b911859..037e089df1 100644 --- a/test/components.jl +++ b/test/components.jl @@ -304,7 +304,7 @@ sol = solve(prob, Tsit5()) Hey there, Pin1! """ @connector function Pin1(; name) - @parameters t + @independent_variables t sts = @variables v(t)=1.0 i(t)=1.0 ODESystem(Equation[], t, sts, []; name = name) end @@ -314,7 +314,7 @@ sol = solve(prob, Tsit5()) Hey there, Pin2! """ @component function Pin2(; name) - @parameters t + @independent_variables t sts = @variables v(t)=1.0 i(t)=1.0 ODESystem(Equation[], t, sts, []; name = name) end diff --git a/test/constants.jl b/test/constants.jl index c78879f2e2..bfdc83bafc 100644 --- a/test/constants.jl +++ b/test/constants.jl @@ -6,7 +6,7 @@ UMT = ModelingToolkit.UnitfulUnitCheck @constants a = 1 @test_throws MT.ArgumentError @constants b -@parameters t +@independent_variables t @variables x(t) w(t) D = Differential(t) eqs = [D(x) ~ a] @@ -29,7 +29,7 @@ simp = structural_simplify(sys) @constants β=1 [unit = u"m/s"] UMT.get_unit(β) @test MT.isconstant(β) -@parameters t [unit = u"s"] +@independent_variables t [unit = u"s"] @variables x(t) [unit = u"m"] D = Differential(t) eqs = [D(x) ~ β] diff --git a/test/direct.jl b/test/direct.jl index 8302f068a4..70e1babe3f 100644 --- a/test/direct.jl +++ b/test/direct.jl @@ -41,7 +41,7 @@ for i in 1:3 @test eval(ModelingToolkit.toexpr.(eqs)[i]) == eval(simpexpr[i]) end -@parameters t σ ρ β +@parameters σ ρ β @variables x y z ∂ = ModelingToolkit.jacobian(eqs, [x, y, z]) for i in 1:3 @@ -59,7 +59,7 @@ reference_jac = sparse(ModelingToolkit.jacobian(du, [x, y, z])) findnz(reference_jac)[[1, 2]] let - @parameters t + @independent_variables t @variables x(t) y(t) z(t) @test ModelingToolkit.exprs_occur_in([x, y, z], x^2 * y) == [true, true, false] end @@ -197,7 +197,7 @@ test_worldage() let @register_symbolic foo(x) - @parameters t + @independent_variables t D = Differential(t) @test isequal(expand_derivatives(D(foo(t))), D(foo(t))) diff --git a/test/downstream/linearization_dd.jl b/test/downstream/linearization_dd.jl index fd29e28bbc..11dc65f619 100644 --- a/test/downstream/linearization_dd.jl +++ b/test/downstream/linearization_dd.jl @@ -12,7 +12,7 @@ using ControlSystemsMTK using ControlSystemsMTK.ControlSystemsBase: sminreal, minreal, poles connect = ModelingToolkit.connect -@parameters t +@independent_variables t D = Differential(t) @named link1 = Link(; m = 0.2, l = 10, I = 1, g = -9.807) diff --git a/test/downstream/linearize.jl b/test/downstream/linearize.jl index 577b529293..c961b188eb 100644 --- a/test/downstream/linearize.jl +++ b/test/downstream/linearize.jl @@ -1,7 +1,7 @@ using ModelingToolkit, Test # r is an input, and y is an output. -@parameters t +@independent_variables t @variables x(t)=0 y(t)=0 u(t)=0 r(t)=0 @variables x(t)=0 y(t)=0 u(t)=0 r(t)=0 [input = true] @parameters kp = 1 diff --git a/test/inputoutput.jl b/test/inputoutput.jl index fde9c68516..2a81a0e315 100644 --- a/test/inputoutput.jl +++ b/test/inputoutput.jl @@ -1,6 +1,7 @@ using ModelingToolkit, OrdinaryDiffEq, Symbolics, Test -@parameters t σ ρ β +@independent_variables t +@parameters σ ρ β @variables x(t) y(t) z(t) F(t) u(t) D = Differential(t) diff --git a/test/linearity.jl b/test/linearity.jl index 0293110c7f..aed9a256d2 100644 --- a/test/linearity.jl +++ b/test/linearity.jl @@ -3,7 +3,8 @@ using DiffEqBase using Test # Define some variables -@parameters t σ ρ β +@independent_variables t +@parameters σ ρ β @variables x(t) y(t) z(t) D = Differential(t) diff --git a/test/nonlinearsystem.jl b/test/nonlinearsystem.jl index 0080bcc990..841a026d0f 100644 --- a/test/nonlinearsystem.jl +++ b/test/nonlinearsystem.jl @@ -9,7 +9,7 @@ using ModelingToolkit: get_default_or_guess, MTKParameters canonequal(a, b) = isequal(simplify(a), simplify(b)) # Define some variables -@parameters t σ ρ β +@parameters σ ρ β @constants h = 1 @variables x y z @@ -115,7 +115,7 @@ lorenz2 = lorenz(:lorenz2) # system promotion using OrdinaryDiffEq -@parameters t +@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]) @@ -126,7 +126,7 @@ 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) -@parameters t σ ρ β +@parameters σ ρ β @variables x y z # Define a nonlinear system @@ -178,7 +178,8 @@ end end # observed variable handling -@parameters t τ +@independent_variables t +@parameters τ @variables x(t) RHS(t) @named fol = NonlinearSystem([0 ~ (1 - x * h) / τ], [x], [τ]; observed = [RHS ~ (1 - x) / τ]) @@ -188,7 +189,7 @@ RHS2 = RHS @test isequal(RHS, RHS2) # issue #1358 -@parameters t +@independent_variables t @variables v1(t) v2(t) i1(t) i2(t) eq = [v1 ~ sin(2pi * t * h) v1 - v2 ~ i1 diff --git a/test/optimizationsystem.jl b/test/optimizationsystem.jl index 5a059fe02f..598f9c3f27 100644 --- a/test/optimizationsystem.jl +++ b/test/optimizationsystem.jl @@ -180,7 +180,7 @@ end end @testset "time dependent var" begin - @parameters t + @independent_variables t @variables x(t) y @parameters a b loss = (a - x)^2 + b * (y - x^2)^2 diff --git a/test/precompile_test/ODEPrecompileTest.jl b/test/precompile_test/ODEPrecompileTest.jl index f1ef601350..81187c4075 100644 --- a/test/precompile_test/ODEPrecompileTest.jl +++ b/test/precompile_test/ODEPrecompileTest.jl @@ -3,7 +3,8 @@ using ModelingToolkit function system(; kwargs...) # Define some variables - @parameters t σ ρ β + @independent_variables t + @parameters σ ρ β @variables x(t) y(t) z(t) D = Differential(t) diff --git a/test/sdesystem.jl b/test/sdesystem.jl index 835960b821..3d4fb6d311 100644 --- a/test/sdesystem.jl +++ b/test/sdesystem.jl @@ -458,7 +458,8 @@ fdif!(du, u0, p, t) # issue #819 @testset "Combined system name collisions" begin - @parameters t + @independent_variables t + D = Differential(t) eqs_short = [D(x) ~ σ * (y - x), D(y) ~ x * (ρ - z) - y ] @@ -619,7 +620,8 @@ solve(prob, LambaEulerHeun(), seed = 1) # Test ill-formed due to more equations than states in noise equations -@parameters t p d +@independent_variables t +@parameters p d @variables X(t) eqs = [D(X) ~ p - d * X] noise_eqs = [sqrt(p), -sqrt(d * X)] diff --git a/test/simplify.jl b/test/simplify.jl index 48bfafc731..4252e3262e 100644 --- a/test/simplify.jl +++ b/test/simplify.jl @@ -2,7 +2,7 @@ using ModelingToolkit using ModelingToolkit: value using Test -@parameters t +@independent_variables t @variables x(t) y(t) z(t) null_op = 0 * t @@ -37,7 +37,8 @@ d3 = Differential(x)(d2) # 699 using SymbolicUtils: substitute -@parameters t a(t) b(t) +@independent_variables t +@parameters a(t) b(t) # back and forth substitution does not work for parameters with dependencies term = value(a) diff --git a/test/test_variable_metadata.jl b/test/test_variable_metadata.jl index c715814d4c..22832fed98 100644 --- a/test/test_variable_metadata.jl +++ b/test/test_variable_metadata.jl @@ -73,7 +73,7 @@ d = FakeNormal() @test !haskey(ModelingToolkit.dump_variable_metadata(y), :dist) ## System interface -@parameters t +@independent_variables t Dₜ = Differential(t) @variables x(t)=0 [bounds = (-10, 10)] u(t)=0 [input = true] y(t)=0 [output = true] @parameters T [bounds = (0, Inf)] @@ -124,7 +124,7 @@ sp = Set(p) @test !hasdescription(u) @test !haskey(ModelingToolkit.dump_variable_metadata(u), :desc) -@parameters t +@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) diff --git a/test/units.jl b/test/units.jl index 770f36dbb6..033a64c0e3 100644 --- a/test/units.jl +++ b/test/units.jl @@ -2,7 +2,8 @@ using ModelingToolkit, OrdinaryDiffEq, JumpProcesses, Unitful using Test MT = ModelingToolkit UMT = ModelingToolkit.UnitfulUnitCheck -@parameters t [unit = u"ms"] τ [unit = u"ms"] γ +@independent_variables t [unit = u"ms"] +@parameters τ [unit = u"ms"] γ @variables E(t) [unit = u"kJ"] P(t) [unit = u"MW"] D = Differential(t) @@ -94,7 +95,8 @@ bad_length_eqs = [connect(op, lp)] @test_throws MT.ValidationError ODESystem(bad_eqs, t, [], []; name = :sys) # Array variables -@parameters t [unit = u"s"] v[1:3]=[1, 2, 3] [unit = u"m/s"] +@independent_variables t [unit = u"s"] +@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 @@ -109,7 +111,8 @@ eqs = [ @named nls = NonlinearSystem(eqs, [x], [a]) # SDE test w/ noise vector -@parameters t [unit = u"ms"] τ [unit = u"ms"] Q [unit = u"MW"] +@independent_variables t [unit = u"ms"] +@parameters τ [unit = u"ms"] Q [unit = u"MW"] @variables E(t) [unit = u"kJ"] P(t) [unit = u"MW"] D = Differential(t) eqs = [D(E) ~ P - E / τ @@ -130,7 +133,8 @@ noiseeqs = [0.1u"MW" 0.1u"MW" @test !UMT.validate(eqs, noiseeqs) # Non-trivial simplifications -@parameters t [unit = u"s"] v [unit = u"m/s"] r [unit = u"m"^3 / u"s"] +@independent_variables t [unit = u"s"] +@parameters v [unit = u"m/s"] r [unit = u"m"^3 / u"s"] @variables V(t) [unit = u"m"^3] L(t) [unit = u"m"] D = Differential(t) eqs = [D(L) ~ v, diff --git a/test/variable_parsing.jl b/test/variable_parsing.jl index 656900abc6..1930b3273d 100644 --- a/test/variable_parsing.jl +++ b/test/variable_parsing.jl @@ -4,7 +4,7 @@ using Test using ModelingToolkit: value, Flow using SymbolicUtils: FnType -@parameters t +@independent_variables t @variables x(t) y(t) # test multi-arg @variables z(t) # test single-arg @@ -60,7 +60,7 @@ end # @test isequal(s1, collect(s)) # @test isequal(σ1, σ) -#@parameters t +#@independent_variables t #@variables x[1:2](t) #x1 = Num[Variable{FnType{Tuple{Any}, Real}}(:x, 1)(t.val), # Variable{FnType{Tuple{Any}, Real}}(:x, 2)(t.val)] @@ -117,7 +117,7 @@ a = rename(value(x), :a) @test getmetadata(a, VariableConnectType) == Flow @test getmetadata(a, VariableUnit) == u -@parameters t +@independent_variables t @variables x(t)=1 [connect = Flow, unit = u] @test getmetadata(x, VariableDefaultValue) == 1 diff --git a/test/variable_scope.jl b/test/variable_scope.jl index 81a248f08f..8c6e358c23 100644 --- a/test/variable_scope.jl +++ b/test/variable_scope.jl @@ -3,7 +3,7 @@ using ModelingToolkit: SymScope using Symbolics: arguments, value using Test -@parameters t +@independent_variables t @variables a b(t) c d e(t) b = ParentScope(b) @@ -52,7 +52,8 @@ end @test renamed([:foo :bar :baz], c) == Symbol("foo₊c") @test renamed([:foo :bar :baz], d) == :d -@parameters t a b c d e f +@independent_variables t +@parameters a b c d e f p = [a ParentScope(b) ParentScope(ParentScope(c)) diff --git a/test/variable_utils.jl b/test/variable_utils.jl index 200f61954d..8f3178f453 100644 --- a/test/variable_utils.jl +++ b/test/variable_utils.jl @@ -18,7 +18,7 @@ new = (((1 / β - 1) + δ) / γ)^(1 / (γ - 1)) # Continuous using ModelingToolkit: isdifferential, vars, collect_differential_variables, collect_ivs -@parameters t +@independent_variables t @variables u(t) y(t) D = Differential(t) eq = D(y) ~ u From 21731deec8002b0a5e043cd551c1a78aecbd5c47 Mon Sep 17 00:00:00 2001 From: Herman Sletmoen Date: Wed, 17 Jul 2024 14:37:25 +0200 Subject: [PATCH 298/316] Use @independent_variables in FAQ --- docs/src/basics/FAQ.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/basics/FAQ.md b/docs/src/basics/FAQ.md index bbfd3566c8..44f97c2b25 100644 --- a/docs/src/basics/FAQ.md +++ b/docs/src/basics/FAQ.md @@ -239,7 +239,7 @@ However, if you want to use your own, you can do so: ```julia using ModelingToolkit -@parameters x # independent variables must be created as parameters +@independent_variables x D = Differential(x) @variables y(x) @named sys = ODESystem([D(y) ~ x], x) From a595821631ac7dce69d4812211f7e0584cbd46a4 Mon Sep 17 00:00:00 2001 From: Herman Sletmoen Date: Wed, 17 Jul 2024 16:31:25 +0200 Subject: [PATCH 299/316] Update test to @independent_variables --- test/odesystem.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/odesystem.jl b/test/odesystem.jl index 37b2f8b5ce..07cdc58277 100644 --- a/test/odesystem.jl +++ b/test/odesystem.jl @@ -1197,7 +1197,7 @@ end # https://github.com/SciML/ModelingToolkit.jl/issues/2818 @testset "Custom independent variable" begin - @parameters x + @independent_variables x @variables y(x) @test_nowarn @named sys = ODESystem([y ~ 0], x) From 2b670348c210ea8ec9d90b38847adc3898d64749 Mon Sep 17 00:00:00 2001 From: Herman Sletmoen Date: Wed, 17 Jul 2024 16:48:15 +0200 Subject: [PATCH 300/316] Issue warning instead of error when independent variable is not a parameter --- src/utils.jl | 2 +- test/odesystem.jl | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/utils.jl b/src/utils.jl index c1ccac05a2..9aa893e321 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -116,7 +116,7 @@ const CheckUnits = 1 << 2 function check_independent_variables(ivs) for iv in ivs isparameter(iv) || - throw(ArgumentError("Independent variable $iv is not a parameter.")) + @warn "Independent variable $iv should be defined with @independent_variables $iv." end end diff --git a/test/odesystem.jl b/test/odesystem.jl index 07cdc58277..03cbed3d9b 100644 --- a/test/odesystem.jl +++ b/test/odesystem.jl @@ -1202,7 +1202,7 @@ end @test_nowarn @named sys = ODESystem([y ~ 0], x) @variables x y(x) - @test_throws ArgumentError @named sys = ODESystem([y ~ 0], x) + @test_logs (:warn,) @named sys = ODESystem([y ~ 0], x) @parameters T D = Differential(T) From 96bf961d47a64701aaf2dc3eefd5def39dfa3670 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Miclu=C8=9Ba-C=C3=A2mpeanu?= Date: Wed, 17 Jul 2024 19:24:18 +0300 Subject: [PATCH 301/316] refactor: avoid substitute warning when not needed --- src/systems/abstractsystem.jl | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/systems/abstractsystem.jl b/src/systems/abstractsystem.jl index 2798349ae6..b701f3c821 100644 --- a/src/systems/abstractsystem.jl +++ b/src/systems/abstractsystem.jl @@ -2600,8 +2600,10 @@ end keytype(::Type{<:Pair{T, V}}) where {T, V} = T function Symbolics.substitute(sys::AbstractSystem, rules::Union{Vector{<:Pair}, Dict}) - if has_continuous_domain(sys) && get_continuous_events(sys) !== nothing || - has_discrete_events(sys) && get_discrete_events(sys) !== nothing + if has_continuous_domain(sys) && get_continuous_events(sys) !== nothing && + !isempty(get_continuous_events(sys)) || + has_discrete_events(sys) && get_discrete_events(sys) !== nothing && + !isempty(get_discrete_events(sys)) @warn "`substitute` only supports performing substitutions in equations. This system has events, which will not be updated." end if keytype(eltype(rules)) <: Symbol From f45a0fcb2d38f3fc8951bc5f1ab68d326965231d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Miclu=C8=9Ba-C=C3=A2mpeanu?= Date: Wed, 17 Jul 2024 19:31:37 +0300 Subject: [PATCH 302/316] test: add test for warnings in `substitute` --- test/odesystem.jl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/odesystem.jl b/test/odesystem.jl index d54baa8a93..68c50d4d7f 100644 --- a/test/odesystem.jl +++ b/test/odesystem.jl @@ -972,7 +972,8 @@ vars_sub2 = @variables s2(t) @named partial_sub = ODESystem(Equation[], t, vars_sub2, []) @named sub = extend(partial_sub, sub) -new_sys2 = complete(substitute(sys2, Dict(:sub => sub))) +# no warnings for systems without events +new_sys2 = @test_nowarn complete(substitute(sys2, Dict(:sub => sub))) Set(unknowns(new_sys2)) == Set([new_sys2.x1, new_sys2.sys1.x1, new_sys2.sys1.sub.s1, new_sys2.sys1.sub.s2, new_sys2.sub.s1, new_sys2.sub.s2]) From 96ad87981f252bc6e839e564b10abd2bfe1f46cb Mon Sep 17 00:00:00 2001 From: Herman Sletmoen Date: Wed, 17 Jul 2024 21:31:17 +0200 Subject: [PATCH 303/316] Add more documentation (based on #1822) --- src/constants.jl | 2 + src/parameters.jl | 2 + .../StructuralTransformations.jl | 2 + .../symbolics_tearing.jl | 8 ++ src/systems/abstractsystem.jl | 74 +++++++++++++++++-- 5 files changed, 81 insertions(+), 7 deletions(-) diff --git a/src/constants.jl b/src/constants.jl index bd2c6508fc..a0a38fd057 100644 --- a/src/constants.jl +++ b/src/constants.jl @@ -27,6 +27,8 @@ toconstant(s::Num) = wrap(toconstant(value(s))) $(SIGNATURES) Define one or more constants. + +See also [`@independent_variables`](@ref), [`@parameters`](@ref) and [`@variables`](@ref). """ macro constants(xs...) Symbolics._parse_vars(:constants, diff --git a/src/parameters.jl b/src/parameters.jl index f330942046..fb6b0b4d6e 100644 --- a/src/parameters.jl +++ b/src/parameters.jl @@ -54,6 +54,8 @@ tovar(s::Num) = Num(tovar(value(s))) $(SIGNATURES) Define one or more known parameters. + +See also [`@independent_variables`](@ref), [`@variables`](@ref) and [`@constants`](@ref). """ macro parameters(xs...) Symbolics._parse_vars(:parameters, diff --git a/src/structural_transformation/StructuralTransformations.jl b/src/structural_transformation/StructuralTransformations.jl index 2682f1f561..5b9c911928 100644 --- a/src/structural_transformation/StructuralTransformations.jl +++ b/src/structural_transformation/StructuralTransformations.jl @@ -50,6 +50,8 @@ using SparseArrays using SimpleNonlinearSolve +using DocStringExtensions + export tearing, partial_state_selection, dae_index_lowering, check_consistency export dummy_derivative export build_torn_function, build_observed_function, ODAEProblem diff --git a/src/structural_transformation/symbolics_tearing.jl b/src/structural_transformation/symbolics_tearing.jl index 6f6e89d86d..6e1bc5d148 100644 --- a/src/structural_transformation/symbolics_tearing.jl +++ b/src/structural_transformation/symbolics_tearing.jl @@ -86,6 +86,14 @@ function tearing_sub(expr, dict, s) s ? simplify(expr) : expr end +""" +$(TYPEDSIGNATURES) + +Like `equations(sys)`, but includes substitutions done by the tearing process. +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) diff --git a/src/systems/abstractsystem.jl b/src/systems/abstractsystem.jl index 2798349ae6..acade88bdc 100644 --- a/src/systems/abstractsystem.jl +++ b/src/systems/abstractsystem.jl @@ -355,6 +355,13 @@ function independent_variables(sys::AbstractMultivariateSystem) return getfield(sys, :ivs) end +""" +$(TYPEDSIGNATURES) + +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 isdefined(sys, :iv) @@ -649,11 +656,29 @@ for prop in [:eqs :split_idxs :parent :index_cache] - fname1 = Symbol(:get_, prop) - fname2 = Symbol(:has_, prop) + fname_get = Symbol(:get_, prop) + fname_has = Symbol(:has_, prop) @eval begin - $fname1(sys::AbstractSystem) = getfield(sys, $(QuoteNode(prop))) - $fname2(sys::AbstractSystem) = isdefined(sys, $(QuoteNode(prop))) + """ + $(TYPEDSIGNATURES) + + Get the internal field `$($(QuoteNode(prop)))` of a system `sys`. + It only includes `$($(QuoteNode(prop)))` local to `sys`; not those of its subsystems, + like `unknowns(sys)`, `parameters(sys)` and `equations(sys)` does. + This is equivalent to, but preferred over `sys.$($(QuoteNode(prop)))`. + + See also [`has_$($(QuoteNode(prop)))`](@ref). + """ + $fname_get(sys::AbstractSystem) = getfield(sys, $(QuoteNode(prop))) + + """ + $(TYPEDSIGNATURES) + + Returns whether the system `sys` has the internal field `$($(QuoteNode(prop)))`. + + See also [`get_$($(QuoteNode(prop)))`](@ref). + """ + $fname_has(sys::AbstractSystem) = isdefined(sys, $(QuoteNode(prop))) end end @@ -1003,6 +1028,14 @@ function namespace_expr( end end _nonum(@nospecialize x) = x isa Num ? x.val : x + +""" +$(TYPEDSIGNATURES) + +Get the unknown variables of the system `sys` and its subsystems. + +See also [`ModelingToolkit.get_unknowns`](@ref). +""" function unknowns(sys::AbstractSystem) sts = get_unknowns(sys) systems = get_systems(sys) @@ -1023,6 +1056,13 @@ function unknowns(sys::AbstractSystem) unique(nonunique_unknowns) end +""" +$(TYPEDSIGNATURES) + +Get the parameters of the system `sys` and its subsystems. + +See also [`@parameters`](@ref) and [`ModelingToolkit.get_ps`](@ref). +""" function parameters(sys::AbstractSystem) ps = get_ps(sys) if ps == SciMLBase.NullParameters() @@ -1131,6 +1171,15 @@ end flatten(sys::AbstractSystem, args...) = sys +""" +$(TYPEDSIGNATURES) + +Get the flattened equations of the system `sys` and its subsystems. +It may include some abbreviations and aliases of observables. +It is often the most useful way to inspect the equations of a system. + +See also [`full_equations`](@ref) and [`ModelingToolkit.get_eqs`](@ref). +""" function equations(sys::AbstractSystem) eqs = get_eqs(sys) systems = get_systems(sys) @@ -1145,6 +1194,13 @@ function equations(sys::AbstractSystem) end end +""" +$(TYPEDSIGNATURES) + +Get the initialization equations of the system `sys` and its subsystems. + +See also [`ModelingToolkit.get_initialization_eqs`](@ref). +""" function initialization_equations(sys::AbstractSystem) eqs = get_initialization_eqs(sys) systems = get_systems(sys) @@ -2500,8 +2556,10 @@ end """ $(TYPEDSIGNATURES) -extend the `basesys` with `sys`, the resulting system would inherit `sys`'s name +Extend the `basesys` with `sys`, the resulting system would inherit `sys`'s name by default. + +See also [`compose`](@ref). """ function extend(sys::AbstractSystem, basesys::AbstractSystem; name::Symbol = nameof(sys), gui_metadata = get_gui_metadata(sys)) @@ -2550,8 +2608,10 @@ end """ $(SIGNATURES) -compose multiple systems together. The resulting system would inherit the first +Compose multiple systems together. The resulting system would inherit the first system's name. + +See also [`extend`](@ref). """ function compose(sys::AbstractSystem, systems::AbstractArray; name = nameof(sys)) nsys = length(systems) @@ -2572,7 +2632,7 @@ end """ missing_variable_defaults(sys::AbstractSystem, default = 0.0; subset = unknowns(sys)) -returns a `Vector{Pair}` of variables set to `default` which are missing from `get_defaults(sys)`. The `default` argument can be a single value or vector to set the missing defaults respectively. +Returns a `Vector{Pair}` of variables set to `default` which are missing from `get_defaults(sys)`. The `default` argument can be a single value or vector to set the missing defaults respectively. """ function missing_variable_defaults( sys::AbstractSystem, default = 0.0; subset = unknowns(sys)) From a1269bef2c9b7694825f9b00f3e52684875855b8 Mon Sep 17 00:00:00 2001 From: Herman Sletmoen Date: Thu, 18 Jul 2024 10:08:32 +0200 Subject: [PATCH 304/316] Export more --- src/ModelingToolkit.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ModelingToolkit.jl b/src/ModelingToolkit.jl index 5be9e6dbb2..b1d0e3ba83 100644 --- a/src/ModelingToolkit.jl +++ b/src/ModelingToolkit.jl @@ -235,8 +235,8 @@ export Differential, expand_derivatives, @derivatives export Equation, ConstrainedEquation export Term, Sym export SymScope, LocalScope, ParentScope, DelayParentScope, GlobalScope -export independent_variable, equations, controls, - observed, full_equations +export independent_variable, equations, controls, observed, full_equations +export initialization_equations, guesses, defaults, parameter_dependencies export structural_simplify, expand_connections, linearize, linearization_function export calculate_jacobian, generate_jacobian, generate_function, generate_custom_function From bb4baaf08ec82eb4f8302143824251c6a4b70106 Mon Sep 17 00:00:00 2001 From: Herman Sletmoen Date: Thu, 18 Jul 2024 10:08:42 +0200 Subject: [PATCH 305/316] Document more --- src/systems/abstractsystem.jl | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/src/systems/abstractsystem.jl b/src/systems/abstractsystem.jl index acade88bdc..f01b658936 100644 --- a/src/systems/abstractsystem.jl +++ b/src/systems/abstractsystem.jl @@ -1093,6 +1093,12 @@ function dependent_parameters(sys::AbstractSystem) end end +""" +$(TYPEDSIGNATURES) +Get the parameter dependencies of the system `sys` and its subsystems. + +See also [`defaults`](@ref) and [`ModelingToolkit.get_parameter_dependencies`](@ref). +""" function parameter_dependencies(sys::AbstractSystem) pdeps = get_parameter_dependencies(sys) if isnothing(pdeps) @@ -1111,6 +1117,13 @@ function full_parameters(sys::AbstractSystem) vcat(parameters(sys), dependent_parameters(sys)) end +""" +$(TYPEDSIGNATURES) + +Get the guesses for variables in the initialization system of the system `sys` and its subsystems. + +See also [`initialization_equations`](@ref) and [`ModelingToolkit.get_guesses`](@ref). +""" function guesses(sys::AbstractSystem) guess = get_guesses(sys) systems = get_systems(sys) @@ -1141,6 +1154,15 @@ end Base.@deprecate default_u0(x) defaults(x) false Base.@deprecate default_p(x) defaults(x) false + +""" +$(TYPEDSIGNATURES) + +Get the default values of the system sys and its subsystems. +If they are not explicitly provided, variables and parameters are initialized to these values. + +See also [`initialization_equations`](@ref), [`parameter_dependencies`](@ref) and [`ModelingToolkit.get_defaults`](@ref). +""" function defaults(sys::AbstractSystem) systems = get_systems(sys) defs = get_defaults(sys) @@ -1199,7 +1221,7 @@ $(TYPEDSIGNATURES) Get the initialization equations of the system `sys` and its subsystems. -See also [`ModelingToolkit.get_initialization_eqs`](@ref). +See also [`guesses`](@ref), [`defaults`](@ref), [`parameter_dependencies`](@ref) and [`ModelingToolkit.get_initialization_eqs`](@ref). """ function initialization_equations(sys::AbstractSystem) eqs = get_initialization_eqs(sys) From 910f7de99b78c3e7425f330affba22bf7b0c8905 Mon Sep 17 00:00:00 2001 From: Herman Sletmoen Date: Fri, 19 Jul 2024 16:44:33 +0200 Subject: [PATCH 306/316] Test a system that should have a guess --- test/initializationsystem.jl | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/test/initializationsystem.jl b/test/initializationsystem.jl index f4097ecd97..3cd30dd7e6 100644 --- a/test/initializationsystem.jl +++ b/test/initializationsystem.jl @@ -456,3 +456,11 @@ sys = structural_simplify(unsimp; fully_determined = false) sys = extend(sysx, sysy) @test length(equations(generate_initializesystem(sys))) == 2 @test length(ModelingToolkit.guesses(sys)) == 1 + +# 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) + ssys = structural_simplify(sys) + @test_throws ArgumentError ODEProblem(ssys, [x => 3], (0, 1), []) # y should have a guess +end From 93d13fbeb3f02b69506ab5329c43528f162909a5 Mon Sep 17 00:00:00 2001 From: Herman Sletmoen Date: Fri, 19 Jul 2024 17:08:44 +0200 Subject: [PATCH 307/316] Disable early return optimization in promote_to_concrete() --- src/utils.jl | 63 ++++++++++++++++++++++++++-------------------------- 1 file changed, 32 insertions(+), 31 deletions(-) diff --git a/src/utils.jl b/src/utils.jl index 9aa893e321..2cc75c7da9 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -663,42 +663,43 @@ function promote_to_concrete(vs; tofloat = true, use_union = true) vs = Any[vs...] end T = eltype(vs) - if Base.isconcretetype(T) && (!tofloat || T === float(T)) # nothing to do - return vs - else - sym_vs = filter(x -> SymbolicUtils.issym(x) || SymbolicUtils.iscall(x), vs) - isempty(sym_vs) || throw_missingvars_in_sys(sym_vs) - - C = nothing - for v in vs - E = typeof(v) - if E <: Number - if tofloat - E = float(E) - end - end - if C === nothing - C = E - end - if use_union - C = Union{C, E} - else - @assert C==E "`promote_to_concrete` can't make type $E uniform with $C" - C = E - end - end - y = similar(vs, C) - for i in eachindex(vs) - if (vs[i] isa Number) & tofloat - y[i] = float(vs[i]) #needed because copyto! can't convert Int to Float automatically - else - y[i] = vs[i] + # return early if there is nothing to do + # TODO: reenable after it was disabled to fix missing errors in https://github.com/SciML/ModelingToolkit.jl/issues/2873 + #Base.isconcretetype(T) && (!tofloat || T === float(T)) && return vs + + sym_vs = filter(x -> SymbolicUtils.issym(x) || SymbolicUtils.iscall(x), vs) + isempty(sym_vs) || throw_missingvars_in_sys(sym_vs) + + C = nothing + for v in vs + E = typeof(v) + if E <: Number + if tofloat + E = float(E) end end + if C === nothing + C = E + end + if use_union + C = Union{C, E} + else + @assert C==E "`promote_to_concrete` can't make type $E uniform with $C" + C = E + end + end - return y + y = similar(vs, C) + for i in eachindex(vs) + if (vs[i] isa Number) & tofloat + y[i] = float(vs[i]) #needed because copyto! can't convert Int to Float automatically + else + y[i] = vs[i] + end end + + return y end struct BitDict <: AbstractDict{Int, Int} From 3e5fb422cfb5c700b3938149e58b84457277e876 Mon Sep 17 00:00:00 2001 From: Christopher Rackauckas Date: Fri, 19 Jul 2024 19:01:33 -0400 Subject: [PATCH 308/316] Some repo cleanup --- downstream/Project.toml | 2 -- format/Project.toml | 2 -- 2 files changed, 4 deletions(-) delete mode 100644 downstream/Project.toml delete mode 100644 format/Project.toml diff --git a/downstream/Project.toml b/downstream/Project.toml deleted file mode 100644 index 536c1c16cd..0000000000 --- a/downstream/Project.toml +++ /dev/null @@ -1,2 +0,0 @@ -[deps] -ModelingToolkit = "961ee093-0014-501f-94e3-6117800e7a78" diff --git a/format/Project.toml b/format/Project.toml deleted file mode 100644 index f3aab8b8bf..0000000000 --- a/format/Project.toml +++ /dev/null @@ -1,2 +0,0 @@ -[deps] -JuliaFormatter = "98e50ef6-434e-11e9-1051-2b60c6c9e899" From e61770376e260b5f717aa940a708e15e13ab7217 Mon Sep 17 00:00:00 2001 From: Christopher Rackauckas Date: Fri, 19 Jul 2024 19:16:44 -0400 Subject: [PATCH 309/316] Allow for setting fully_determined=true in initialization --- src/systems/diffeqs/abstractodesystem.jl | 9 ++++++--- test/initializationsystem.jl | 18 ++++++++++++++++++ 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/src/systems/diffeqs/abstractodesystem.jl b/src/systems/diffeqs/abstractodesystem.jl index ab0565eb31..f4268c506a 100644 --- a/src/systems/diffeqs/abstractodesystem.jl +++ b/src/systems/diffeqs/abstractodesystem.jl @@ -762,6 +762,7 @@ function process_DEProblem(constructor, sys::AbstractODESystem, u0map, parammap; warn_initialize_determined = true, build_initializeprob = true, initialization_eqs = [], + fully_determined = false, kwargs...) eqs = equations(sys) dvs = unknowns(sys) @@ -835,7 +836,7 @@ function process_DEProblem(constructor, sys::AbstractODESystem, u0map, parammap; end initializeprob = ModelingToolkit.InitializationProblem( sys, t, u0map, parammap; guesses, warn_initialize_determined, - initialization_eqs, eval_expression, eval_module) + initialization_eqs, eval_expression, eval_module, fully_determined) initializeprobmap = getu(initializeprob, unknowns(sys)) zerovars = Dict(setdiff(unknowns(sys), keys(defaults(sys))) .=> 0.0) @@ -1478,6 +1479,7 @@ InitializationProblem{iip}(sys::AbstractODESystem, u0map, tspan, simplify = false, linenumbers = true, parallel = SerialForm(), initialization_eqs = [], + fully_determined = false, kwargs...) where {iip} ``` @@ -1527,6 +1529,7 @@ function InitializationProblem{iip, specialize}(sys::AbstractODESystem, check_length = true, warn_initialize_determined = true, initialization_eqs = [], + fully_determined = 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`") @@ -1535,10 +1538,10 @@ function InitializationProblem{iip, specialize}(sys::AbstractODESystem, isys = get_initializesystem(sys; initialization_eqs) elseif isempty(u0map) && get_initializesystem(sys) === nothing isys = structural_simplify( - generate_initializesystem(sys; initialization_eqs); fully_determined = false) + generate_initializesystem(sys; initialization_eqs); fully_determined) else isys = structural_simplify( - generate_initializesystem(sys; u0map, initialization_eqs); fully_determined = false) + generate_initializesystem(sys; u0map, initialization_eqs); fully_determined) end uninit = setdiff(unknowns(sys), [unknowns(isys); getfield.(observed(isys), :lhs)]) diff --git a/test/initializationsystem.jl b/test/initializationsystem.jl index f4097ecd97..5111381121 100644 --- a/test/initializationsystem.jl +++ b/test/initializationsystem.jl @@ -17,6 +17,10 @@ sol = solve(initprob) @test SciMLBase.successful_retcode(sol) @test maximum(abs.(sol[conditions])) < 1e-14 +@test_throws ModelingToolkit.ExtraVariablesSystemException ModelingToolkit.InitializationProblem(pend, 0.0, [], [g => 1]; + guesses = [ModelingToolkit.missing_variable_defaults(pend); x => 1; y => 0.2], + fully_determined = true) + initprob = ModelingToolkit.InitializationProblem(pend, 0.0, [x => 1, y => 0], [g => 1]; guesses = ModelingToolkit.missing_variable_defaults(pend)) @test initprob isa NonlinearProblem @@ -31,6 +35,10 @@ initprob = ModelingToolkit.InitializationProblem( sol = solve(initprob) @test !SciMLBase.successful_retcode(sol) +@test_throws ModelingToolkit.ExtraVariablesSystemException ModelingToolkit.InitializationProblem( + pend, 0.0, [], [g => 1]; guesses = ModelingToolkit.missing_variable_defaults(pend), + fully_determined = true) + prob = ODEProblem(pend, [x => 1, y => 0], (0.0, 1.5), [g => 1], guesses = ModelingToolkit.missing_variable_defaults(pend)) prob.f.initializeprob isa NonlinearProblem @@ -47,6 +55,10 @@ sol = solve(prob.f.initializeprob) sol = solve(prob, Rodas5P()) @test maximum(abs.(sol[conditions][1])) < 1e-14 +@test_throws ModelingToolkit.ExtraVariablesSystemException ODEProblem(pend, [x => 1], (0.0, 1.5), [g => 1], + guesses = ModelingToolkit.missing_variable_defaults(pend), + fully_determined = true) + @connector Port begin p(t) dm(t) = 0, [connect = Flow] @@ -225,6 +237,8 @@ initsol = solve(initprob, reltol = 1e-12, abstol = 1e-12) @test SciMLBase.successful_retcode(initsol) @test maximum(abs.(initsol[conditions])) < 1e-14 +@test_throws ModelingToolkit.ExtraEquationsSystemException ModelingToolkit.InitializationProblem(sys, 0.0, fully_determined = true) + allinit = unknowns(sys) .=> initsol[unknowns(sys)] prob = ODEProblem(sys, allinit, (0, 0.1)) sol = solve(prob, Rodas5P(), initializealg = BrownFullBasicInit()) @@ -232,7 +246,11 @@ sol = solve(prob, Rodas5P(), initializealg = BrownFullBasicInit()) @test sol.retcode == SciMLBase.ReturnCode.Unstable @test maximum(abs.(initsol[conditions][1])) < 1e-14 +prob = ODEProblem(sys, allinit, (0, 0.1)) prob = ODEProblem(sys, [], (0, 0.1), check = false) + +@test_throws ModelingToolkit.ExtraEquationsSystemException ODEProblem(sys, [], (0, 0.1), fully_determined = true) + sol = solve(prob, Rodas5P()) # If initialized incorrectly, then it would be InitialFailure @test sol.retcode == SciMLBase.ReturnCode.Unstable From 5fe46e69b7363d0b897e1c91d15c1a2d207e7e7a Mon Sep 17 00:00:00 2001 From: Christopher Rackauckas Date: Fri, 19 Jul 2024 19:23:04 -0400 Subject: [PATCH 310/316] document the fully_determined behavior --- docs/src/tutorials/initialization.md | 9 ++++++++- src/systems/diffeqs/abstractodesystem.jl | 4 ++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/docs/src/tutorials/initialization.md b/docs/src/tutorials/initialization.md index 8af6a512e2..4886a47a73 100644 --- a/docs/src/tutorials/initialization.md +++ b/docs/src/tutorials/initialization.md @@ -194,6 +194,13 @@ may not be analytically satisfiable!**. In our case here, if you sit down with a long enough you will see that `λ = 0` is required for this equation, but since we chose `λ = 1` we end up with a set of equations that are impossible to satisfy. +!!! note + + If you would prefer to have an error instead of a warning in the context of non-fully + determined systems, pass the keyword argument `fully_determined = true` into the + problem constructor. Additionally, any warning about not being fully determined can + be supressed via passing `warn_initialize_determined = false`. + ## Diving Deeper: Constructing the Initialization System To get a better sense of the initialization system and to help debug it, you can construct @@ -271,7 +278,7 @@ sol = solve(iprob) ``` !!! note - + For more information on solving NonlinearProblems and NonlinearLeastSquaresProblems, check out the [NonlinearSolve.jl tutorials!](https://docs.sciml.ai/NonlinearSolve/stable/tutorials/getting_started/). diff --git a/src/systems/diffeqs/abstractodesystem.jl b/src/systems/diffeqs/abstractodesystem.jl index f4268c506a..11abadad5f 100644 --- a/src/systems/diffeqs/abstractodesystem.jl +++ b/src/systems/diffeqs/abstractodesystem.jl @@ -1556,10 +1556,10 @@ function InitializationProblem{iip, specialize}(sys::AbstractODESystem, nunknown = length(unknowns(isys)) if warn_initialize_determined && neqs > nunknown - @warn "Initialization system is overdetermined. $neqs equations for $nunknown unknowns. Initialization will default to using least squares. To suppress this warning pass warn_initialize_determined = false." + @warn "Initialization system is overdetermined. $neqs equations for $nunknown unknowns. Initialization will default to using least squares. 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. To suppress this warning pass warn_initialize_determined = false." + @warn "Initialization system is underdetermined. $neqs equations for $nunknown unknowns. Initialization will default to using least squares. To suppress this warning pass warn_initialize_determined = false. To make this warning into an error, pass fully_determined = true" end parammap = parammap isa DiffEqBase.NullParameters || isempty(parammap) ? From 7627ebbb34194bece2e57a0d0c4d06bac7385c1c Mon Sep 17 00:00:00 2001 From: Christopher Rackauckas Date: Fri, 19 Jul 2024 19:23:33 -0400 Subject: [PATCH 311/316] format --- docs/src/tutorials/initialization.md | 4 ++-- test/initializationsystem.jl | 12 ++++++++---- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/docs/src/tutorials/initialization.md b/docs/src/tutorials/initialization.md index 4886a47a73..136f671281 100644 --- a/docs/src/tutorials/initialization.md +++ b/docs/src/tutorials/initialization.md @@ -195,7 +195,7 @@ long enough you will see that `λ = 0` is required for this equation, but since `λ = 1` we end up with a set of equations that are impossible to satisfy. !!! note - + If you would prefer to have an error instead of a warning in the context of non-fully determined systems, pass the keyword argument `fully_determined = true` into the problem constructor. Additionally, any warning about not being fully determined can @@ -278,7 +278,7 @@ sol = solve(iprob) ``` !!! note - + For more information on solving NonlinearProblems and NonlinearLeastSquaresProblems, check out the [NonlinearSolve.jl tutorials!](https://docs.sciml.ai/NonlinearSolve/stable/tutorials/getting_started/). diff --git a/test/initializationsystem.jl b/test/initializationsystem.jl index 5111381121..1d1ae9bf77 100644 --- a/test/initializationsystem.jl +++ b/test/initializationsystem.jl @@ -17,7 +17,8 @@ sol = solve(initprob) @test SciMLBase.successful_retcode(sol) @test maximum(abs.(sol[conditions])) < 1e-14 -@test_throws ModelingToolkit.ExtraVariablesSystemException ModelingToolkit.InitializationProblem(pend, 0.0, [], [g => 1]; +@test_throws ModelingToolkit.ExtraVariablesSystemException ModelingToolkit.InitializationProblem( + pend, 0.0, [], [g => 1]; guesses = [ModelingToolkit.missing_variable_defaults(pend); x => 1; y => 0.2], fully_determined = true) @@ -55,7 +56,8 @@ sol = solve(prob.f.initializeprob) sol = solve(prob, Rodas5P()) @test maximum(abs.(sol[conditions][1])) < 1e-14 -@test_throws ModelingToolkit.ExtraVariablesSystemException ODEProblem(pend, [x => 1], (0.0, 1.5), [g => 1], +@test_throws ModelingToolkit.ExtraVariablesSystemException ODEProblem( + pend, [x => 1], (0.0, 1.5), [g => 1], guesses = ModelingToolkit.missing_variable_defaults(pend), fully_determined = true) @@ -237,7 +239,8 @@ initsol = solve(initprob, reltol = 1e-12, abstol = 1e-12) @test SciMLBase.successful_retcode(initsol) @test maximum(abs.(initsol[conditions])) < 1e-14 -@test_throws ModelingToolkit.ExtraEquationsSystemException ModelingToolkit.InitializationProblem(sys, 0.0, fully_determined = true) +@test_throws ModelingToolkit.ExtraEquationsSystemException ModelingToolkit.InitializationProblem( + sys, 0.0, fully_determined = true) allinit = unknowns(sys) .=> initsol[unknowns(sys)] prob = ODEProblem(sys, allinit, (0, 0.1)) @@ -249,7 +252,8 @@ sol = solve(prob, Rodas5P(), initializealg = BrownFullBasicInit()) prob = ODEProblem(sys, allinit, (0, 0.1)) prob = ODEProblem(sys, [], (0, 0.1), check = false) -@test_throws ModelingToolkit.ExtraEquationsSystemException ODEProblem(sys, [], (0, 0.1), fully_determined = true) +@test_throws ModelingToolkit.ExtraEquationsSystemException ODEProblem( + sys, [], (0, 0.1), fully_determined = true) sol = solve(prob, Rodas5P()) # If initialized incorrectly, then it would be InitialFailure From 704f08531c06bac2e4a701fa58105c599fc8e486 Mon Sep 17 00:00:00 2001 From: Christopher Rackauckas Date: Fri, 19 Jul 2024 19:59:19 -0400 Subject: [PATCH 312/316] spell check --- docs/src/tutorials/initialization.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/src/tutorials/initialization.md b/docs/src/tutorials/initialization.md index 136f671281..8852fc7ac0 100644 --- a/docs/src/tutorials/initialization.md +++ b/docs/src/tutorials/initialization.md @@ -195,11 +195,11 @@ long enough you will see that `λ = 0` is required for this equation, but since `λ = 1` we end up with a set of equations that are impossible to satisfy. !!! note - + If you would prefer to have an error instead of a warning in the context of non-fully determined systems, pass the keyword argument `fully_determined = true` into the problem constructor. Additionally, any warning about not being fully determined can - be supressed via passing `warn_initialize_determined = false`. + be suppressed via passing `warn_initialize_determined = false`. ## Diving Deeper: Constructing the Initialization System @@ -278,7 +278,7 @@ sol = solve(iprob) ``` !!! note - + For more information on solving NonlinearProblems and NonlinearLeastSquaresProblems, check out the [NonlinearSolve.jl tutorials!](https://docs.sciml.ai/NonlinearSolve/stable/tutorials/getting_started/). From 19e8a899759b5fae1b3f410f7e90d657d1300900 Mon Sep 17 00:00:00 2001 From: Herman Sletmoen Date: Sat, 20 Jul 2024 11:49:58 +0200 Subject: [PATCH 313/316] Disable just the faulty branch of the early return optimization, since DDEs rely on the other branch --- src/utils.jl | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/utils.jl b/src/utils.jl index 2cc75c7da9..9e6abafc4d 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -665,8 +665,7 @@ function promote_to_concrete(vs; tofloat = true, use_union = true) T = eltype(vs) # return early if there is nothing to do - # TODO: reenable after it was disabled to fix missing errors in https://github.com/SciML/ModelingToolkit.jl/issues/2873 - #Base.isconcretetype(T) && (!tofloat || T === float(T)) && return vs + Base.isconcretetype(T) && (!tofloat#= || T === float(T)=#) && return vs # TODO: disabled float(T) to restore missing errors in https://github.com/SciML/ModelingToolkit.jl/issues/2873 sym_vs = filter(x -> SymbolicUtils.issym(x) || SymbolicUtils.iscall(x), vs) isempty(sym_vs) || throw_missingvars_in_sys(sym_vs) From 1b22927d853ed87283f76f1cc8a49147ae2c4d24 Mon Sep 17 00:00:00 2001 From: Herman Sletmoen Date: Sat, 20 Jul 2024 12:33:29 +0200 Subject: [PATCH 314/316] Format --- docs/src/tutorials/initialization.md | 4 ++-- src/utils.jl | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/src/tutorials/initialization.md b/docs/src/tutorials/initialization.md index 8852fc7ac0..f40c28991f 100644 --- a/docs/src/tutorials/initialization.md +++ b/docs/src/tutorials/initialization.md @@ -195,7 +195,7 @@ long enough you will see that `λ = 0` is required for this equation, but since `λ = 1` we end up with a set of equations that are impossible to satisfy. !!! note - + If you would prefer to have an error instead of a warning in the context of non-fully determined systems, pass the keyword argument `fully_determined = true` into the problem constructor. Additionally, any warning about not being fully determined can @@ -278,7 +278,7 @@ sol = solve(iprob) ``` !!! note - + For more information on solving NonlinearProblems and NonlinearLeastSquaresProblems, check out the [NonlinearSolve.jl tutorials!](https://docs.sciml.ai/NonlinearSolve/stable/tutorials/getting_started/). diff --git a/src/utils.jl b/src/utils.jl index 9e6abafc4d..86a28dcae6 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -665,7 +665,8 @@ function promote_to_concrete(vs; tofloat = true, use_union = true) T = eltype(vs) # return early if there is nothing to do - Base.isconcretetype(T) && (!tofloat#= || T === float(T)=#) && return vs # TODO: disabled float(T) to restore missing errors in https://github.com/SciML/ModelingToolkit.jl/issues/2873 + #Base.isconcretetype(T) && (!tofloat || T === float(T)) && return vs # TODO: disabled float(T) to restore missing errors in https://github.com/SciML/ModelingToolkit.jl/issues/2873 + Base.isconcretetype(T) && !tofloat && return vs sym_vs = filter(x -> SymbolicUtils.issym(x) || SymbolicUtils.iscall(x), vs) isempty(sym_vs) || throw_missingvars_in_sys(sym_vs) From 4da71c4811cf1638d8567ba40056d6ca6ea93839 Mon Sep 17 00:00:00 2001 From: Christopher Rackauckas Date: Sat, 20 Jul 2024 08:22:28 -0400 Subject: [PATCH 315/316] update latexify regression tests --- test/latexify/20.tex | 6 +++--- test/latexify/30.tex | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/test/latexify/20.tex b/test/latexify/20.tex index 9de213aafd..b96d9fdc2d 100644 --- a/test/latexify/20.tex +++ b/test/latexify/20.tex @@ -1,5 +1,5 @@ \begin{align} -\frac{\mathrm{d}}{\mathrm{d}t} u(t)_1 =& p_3 \left( - u(t)_1 + u(t)_2 \right) \\ -0 =& - u(t)_2 + \frac{1}{10} \left( p_1 - u(t)_1 \right) p_2 p_3 u(t)_1 \\ -\frac{\mathrm{d}}{\mathrm{d}t} u(t)_3 =& u(t)_2^{\frac{2}{3}} u(t)_1 - p_3 u(t)_3 +\frac{\mathrm{d} u\left( t \right)_{1}}{\mathrm{d}t} =& p_{3} \left( - u\left( t \right)_{1} + u\left( t \right)_{2} \right) \\ +0 =& - u\left( t \right)_{2} + \frac{1}{10} \left( p_{1} - u\left( t \right)_{1} \right) p_{2} p_{3} u\left( t \right)_{1} \\ +\frac{\mathrm{d} u\left( t \right)_{3}}{\mathrm{d}t} =& u\left( t \right)_{2}^{\frac{2}{3}} u\left( t \right)_{1} - p_{3} u\left( t \right)_{3} \end{align} diff --git a/test/latexify/30.tex b/test/latexify/30.tex index b83feeba72..767b8e54f2 100644 --- a/test/latexify/30.tex +++ b/test/latexify/30.tex @@ -1,5 +1,5 @@ \begin{align} -\frac{\mathrm{d}}{\mathrm{d}t} u(t)_1 =& p_3 \left( - u(t)_1 + u(t)_2 \right) \\ -\frac{\mathrm{d}}{\mathrm{d}t} u(t)_2 =& - u(t)_2 + \frac{1}{10} \left( p_1 - u(t)_1 \right) p_2 p_3 u(t)_1 \\ -\frac{\mathrm{d}}{\mathrm{d}t} u(t)_3 =& u(t)_2^{\frac{2}{3}} u(t)_1 - p_3 u(t)_3 +\frac{\mathrm{d} u\left( t \right)_{1}}{\mathrm{d}t} =& p_{3} \left( - u\left( t \right)_{1} + u\left( t \right)_{2} \right) \\ +\frac{\mathrm{d} u\left( t \right)_{2}}{\mathrm{d}t} =& - u\left( t \right)_{2} + \frac{1}{10} \left( p_{1} - u\left( t \right)_{1} \right) p_{2} p_{3} u\left( t \right)_{1} \\ +\frac{\mathrm{d} u\left( t \right)_{3}}{\mathrm{d}t} =& u\left( t \right)_{2}^{\frac{2}{3}} u\left( t \right)_{1} - p_{3} u\left( t \right)_{3} \end{align} From 6cca2ffa9a6474433009235e90e6399a2af49d25 Mon Sep 17 00:00:00 2001 From: Christopher Rackauckas Date: Sat, 20 Jul 2024 08:22:49 -0400 Subject: [PATCH 316/316] version bump --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 947dee8b1b..24632bfaf3 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.24.0" +version = "9.25.0" [deps] AbstractTrees = "1520ce14-60c1-5f80-bbc7-55ef81b5835c"