From 1d9c6eefd7c5c43b7b0375c4531332b8866f4a97 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Mon, 7 Apr 2025 16:07:21 +0530 Subject: [PATCH 001/185] feat: add unified `System` type --- src/systems/system.jl | 303 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 303 insertions(+) create mode 100644 src/systems/system.jl diff --git a/src/systems/system.jl b/src/systems/system.jl new file mode 100644 index 0000000000..e7a59dea0e --- /dev/null +++ b/src/systems/system.jl @@ -0,0 +1,303 @@ +struct System <: AbstractSystem + tag::UInt + eqs::Vector{Equation} + # nothing - no noise + # vector - diagonal noise + # matrix - generic form + # column matrix - scalar noise + noise_eqs::Union{Nothing, AbstractVector, AbstractMatrix} + jumps::Vector{Any} + constraints::Vector{Union{Equation, Inequality}} + costs::Vector{<:BasicSymbolic} + consolidate::Any + unknowns::Vector + ps::Vector + brownians::Vector + iv::Union{Nothing, BasicSymbolic{Real}} + observed::Vector{Equation} + parameter_dependencies::Vector{Equation} + var_to_name::Dict{Symbol, Any} + name::Symbol + description::String + defaults::Dict + guesses::Dict + systems::Vector{System} + initialization_eqs::Vector{Equation} + continuous_events::Vector{SymbolicContinuousCallback} + discrete_events::Vector{SymbolicDiscreteCallback} + connector_type::Any + assertions::Dict{BasicSymbolic, String} + metadata::Any + gui_metadata::Any # ? + is_dde::Bool + tstops::Vector{Any} + tearing_state::Any + namespacing::Bool + complete::Bool + index_cache::Union{Nothing, IndexCache} + ignored_connections::Union{ + Nothing, Tuple{Vector{IgnoredAnalysisPoint}, Vector{IgnoredAnalysisPoint}}} + parent::Union{Nothing, System} + + function System( + tag, eqs, noise_eqs, jumps, constraints, costs, consolidate, unknowns, ps, + brownians, iv, observed, parameter_dependencies, var_to_name, name, description, + defaults, guesses, systems, initialization_eqs, continuous_events, discrete_events, + connector_type, assertions = Dict{BasicSymbolic, String}(), + metadata = nothing, gui_metadata = nothing, + is_dde = false, tstops = [], tearing_state = nothing, namespacing = true, + complete = false, index_cache = nothing, ignored_connections = nothing, + parent = nothing; checks::Union{Bool, Int} = true) + if (checks == true || (checks & CheckComponents) > 0) && iv !== nothing + check_independent_variables([iv]) + check_variables(unknowns, iv) + check_parameters(ps, iv) + check_equations(eqs, iv) + if noise_eqs !== nothing && size(noise_eqs, 1) != length(eqs) + throw(IllFormedNoiseEquationsError(size(noise_eqs, 1), length(eqs))) + end + check_equations(equations(continuous_events), iv) + check_subsystems(systems) + end + if checks == true || (checks & CheckUnits) > 0 + u = __get_unit_type(unknowns, ps, iv) + check_units(u, eqs) + noise_eqs !== nothing && check_units(u, noise_eqs) + isempty(constraints) || check_units(u, constraints) + end + new(tag, eqs, noise_eqs, jumps, constraints, costs, + consolidate, unknowns, ps, brownians, iv, + observed, parameter_dependencies, var_to_name, name, description, defaults, + guesses, systems, initialization_eqs, continuous_events, discrete_events, + connector_type, assertions, metadata, gui_metadata, is_dde, + tstops, tearing_state, namespacing, complete, index_cache, ignored_connections, + parent) + end +end + +function System(eqs, iv, dvs, ps, brownians = []; + constraints = Union{Equation, Inequality}[], noise_eqs = nothing, jumps = [], + costs = [], consolidate = nothing, + observed = Equation[], parameter_dependencies = Equation[], defaults = Dict(), + guesses = Dict(), systems = System[], initialization_eqs = Equation[], + continuous_events = SymbolicContinuousCallback[], discrete_events = SymbolicDiscreteCallback[], + connector_type = nothing, assertions = Dict{BasicSymbolic, String}(), + metadata = nothing, gui_metadata = nothing, is_dde = nothing, tstops = [], + tearing_state = nothing, ignored_connections = nothing, parent = nothing, + description = "", name = nothing, discover_from_metadata = true, checks = true) + name === nothing && throw(NoNameError()) + + iv = unwrap(iv) + ps = unwrap.(ps) + dvs = unwrap.(dvs) + filter!(!Base.Fix2(isdelay, iv), dvs) + brownians = unwrap.(brownians) + + if !(eqs isa AbstractArray) + eqs = [eqs] + end + + if noise_eqs !== nothing + noise_eqs = unwrap.(noise_eqs) + end + + parameter_dependencies, ps = process_parameter_dependencies(parameter_dependencies, ps) + defaults = anydict(defaults) + guesses = anydict(guesses) + var_to_name = anydict() + + let defaults = discover_from_metadata ? defaults : Dict(), + guesses = discover_from_metadata ? guesses : Dict() + + process_variables!(var_to_name, defaults, guesses, dvs) + process_variables!(var_to_name, defaults, guesses, ps) + process_variables!( + var_to_name, defaults, guesses, [eq.lhs for eq in parameter_dependencies]) + process_variables!( + var_to_name, defaults, guesses, [eq.rhs for eq in parameter_dependencies]) + process_variables!(var_to_name, defaults, guesses, [eq.lhs for eq in observed]) + process_variables!(var_to_name, defaults, guesses, [eq.rhs for eq in observed]) + end + filter!(!(isnothing ∘ last), defaults) + filter!(!(isnothing ∘ last), guesses) + defaults = anydict([unwrap(k) => unwrap(v) for (k, v) in defaults]) + guesses = anydict([unwrap(k) => unwrap(v) for (k, v) in guesses]) + + sysnames = nameof.(systems) + unique_sysnames = Set(sysnames) + if length(unique_sysnames) != length(sysnames) + throw(NonUniqueSubsystemsError(sysnames, unique_sysnames)) + end + + continuous_events = SymbolicContinuousCallbacks(continuous_events) + discrete_events = SymbolicDiscreteCallbacks(discrete_events) + + if iv === nothing && !isempty(continuous_events) || !isempty(discrete_events) + throw(EventsInTimeIndependentSystemError(continuous_events, discrete_events)) + end + + if is_dde === nothing + is_dde = _check_if_dde(eqs, iv, systems) + end + + assertions = Dict{BasicSymbolic, String}(unwrap(k) => v for (k, v) in assertions) + + System(Threads.atomic_add!(SYSTEM_COUNT, UInt(1)), eqs, noise_eqs, jumps, constraints, + costs, consolidate, dvs, ps, brownians, iv, observed, parameter_dependencies, + var_to_name, name, description, defaults, guesses, systems, initialization_eqs, + continuous_events, discrete_events, connector_type, assertions, metadata, gui_metadata, is_dde, + tstops, tearing_state, true, false, nothing, ignored_connections, parent; checks) +end + +function System(eqs, iv; kwargs...) + iv === nothing && return System(eqs; kwargs...) + diffvars, allunknowns, ps, eqs = process_equations(eqs, iv) + brownians = Set() + for x in allunknowns + x = unwrap(x) + if getvariabletype(x) == BROWNIAN + push!(brownians, x) + end + end + setdiff!(allunknowns, brownians) + + for eq in get(kwargs, :parameter_dependencies, Equation[]) + collect_vars!(allunknowns, ps, eq, iv) + end + + for eq in get(kwargs, :constraints, Equation[]) + collect_vars!(allunknowns, ps, eq, iv) + end + + for ssys in get(kwargs, :systems, System[]) + collect_scoped_vars!(allunknowns, ps, ssys, iv) + end + + costs = get(kwargs, :costs, nothing) + if costs !== nothing + collect_vars!(allunknowns, ps, costs, iv) + end + + for v in allunknowns + isdelay(v, iv) || continue + collect_vars!(allunknowns, ps, arguments(v)[1], iv) + end + + new_ps = gather_array_params(ps) + algevars = setdiff(allunknowns, diffvars) + + noiseeqs = get(kwargs, :noise_eqs, nothing) + if noiseeqs !== nothing + # validate noise equations + noisedvs = OrderedSet() + noiseps = OrderedSet() + collect_vars!(noisedvs, noiseps, noiseeqs, iv) + for dv in noisedvs + dv ∈ allunknowns || + throw(ArgumentError("Variable $dv in noise equations is not an unknown of the system.")) + end + end + + return System(eqs, iv, collect(Iterators.flatten((diffvars, algevars))), + collect(new_ps), brownians; kwargs...) +end + +function System(eqs; kwargs...) + eqs = collect(eqs) + + allunknowns = OrderedSet() + ps = OrderedSet() + for eq in eqs + collect_vars!(allunknowns, ps, eq, nothing) + end + for eq in get(kwargs, :parameter_dependencies, Equation[]) + collect_vars!(allunknowns, ps, eq, nothing) + end + for ssys in get(kwargs, :systems, System[]) + collect_scoped_vars!(allunknowns, ps, ssys, nothing) + end + + new_ps = gather_array_params(ps) + + return System(eqs, nothing, collect(allunknowns), collect(new_ps); kwargs...) +end + +function gather_array_params(ps) + new_ps = OrderedSet() + for p in ps + if iscall(p) && operation(p) === getindex + par = arguments(p)[begin] + if Symbolics.shape(Symbolics.unwrap(par)) !== Symbolics.Unknown() && + all(par[i] in ps for i in eachindex(par)) + push!(new_ps, par) + else + push!(new_ps, p) + end + else + if symbolic_type(p) == ArraySymbolic() && + Symbolics.shape(unwrap(p)) != Symbolics.Unknown() + for i in eachindex(p) + delete!(new_ps, p[i]) + end + end + push!(new_ps, p) + end + end + return new_ps +end + +struct IllFormedNoiseEquationsError <: Exception + noise_eqs_rows::Int + eqs_length::Int +end + +function Base.showerror(io::IO, err::IllFormedNoiseEquationsError) + print(io, """ + Noise equations are ill-formed. The number of rows much must number of drift \ + equations. `size(neqs, 1) == $(err.noise_eqs_rows) != length(eqs) == \ + $(err.eqs_length)`. + """) +end + +function NoNameError() + ArgumentError(""" + The `name` keyword must be provided. Please consider using the `@named` macro. + """) +end + +struct NonUniqueSubsystemsError <: Exception + names::Vector{Symbol} + uniques::Set{Symbol} +end + +function Base.showerror(io::IO, err::NonUniqueSubsystemsError) + dupes = Set{Symbol}() + for n in err.names + if !(n in err.uniques) + push!(dupes, n) + end + delete!(err.uniques, n) + end + println(io, "System names must be unique. The following system names were duplicated:") + for n in dupes + println(io, " ", n) + end +end + +struct EventsInTimeIndependentSystemError <: Exception + cevents::Vector + devents::Vector +end + +function Base.showerror(io::IO, err::EventsInTimeIndependentSystemError) + println(io, """ + Events are not supported in time-indepent systems. Provide an independent variable to \ + make the system time-dependent or remove the events. + + The following continuous events were provided: + $(err.cevents) + + The following discrete events were provided: + $(err.devents) + """) +end From f28a525a19ce3995b1544e6be31dd0467c5e88c8 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Sun, 13 Apr 2025 16:33:11 +0530 Subject: [PATCH 002/185] feat: add `is_discrete_system` --- src/systems/system.jl | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/systems/system.jl b/src/systems/system.jl index e7a59dea0e..84b58d311c 100644 --- a/src/systems/system.jl +++ b/src/systems/system.jl @@ -246,6 +246,17 @@ function gather_array_params(ps) return new_ps end +""" + $(TYPEDSIGNATURES) + +Check if a system is a (possibly implicit) discrete system. Hybrid systems are turned into +callbacks, so checking if any LHS is shifted is sufficient. If a variable is shifted in +the input equations there _will_ be a `Shift` equation in the simplified system. +""" +function is_discrete_system(sys::System) + any(eq -> isoperator(eq.lhs, Shift), equations(sys)) +end + struct IllFormedNoiseEquationsError <: Exception noise_eqs_rows::Int eqs_length::Int From d5bb3a80bdba68cbd9d2b321d74dfe706f289c00 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Sun, 13 Apr 2025 16:33:25 +0530 Subject: [PATCH 003/185] feat: add initial codegen for `System` --- src/systems/codegen.jl | 94 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 94 insertions(+) create mode 100644 src/systems/codegen.jl diff --git a/src/systems/codegen.jl b/src/systems/codegen.jl new file mode 100644 index 0000000000..8537687490 --- /dev/null +++ b/src/systems/codegen.jl @@ -0,0 +1,94 @@ +""" + $(TYPEDSIGNATURES) + +Generate the RHS function for the `equations` of a `System`. + +# Arguments + +# Keyword Arguments + +""" +function generate_rhs(sys::System, dvs = unknowns(sys), + ps = parameters(sys; initial_parameters = true); implicit_dae = false, + scalar = false, kwargs...) + eqs = equations(sys) + obs = observed(sys) + u = dvs + p = reorder_parameters(sys, ps) + t = get_iv(sys) + ddvs = nothing + extra_assignments = Assignment[] + + # used for DAEProblem and ImplicitDiscreteProblem + if implicit_dae + if is_discrete_system(sys) + # ImplicitDiscrete case + D = Shift(t, 1) + rhss = map(eqs) do eq + # Algebraic equations get shifted forward 1, to match with differential + # equations + _iszero(eq.lhs) ? distribute_shift(D(eq.rhs)) : (eq.rhs - eq.lhs) + end + # Handle observables in algebraic equations, since they are shifted + shifted_obs = Equation[distribute_shift(D(eq)) for eq in obs] + obsidxs = observed_equations_used_by(sys, rhss; obs = shifted_obs) + extra_assignments = [Assignment(shifted_obs[i].lhs, shifted_obs[i].rhs) + for i in obsidxs] + else + D = Differential(t) + rhss = [_iszero(eq.lhs) ? eq.rhs : eq.rhs - eq.lhs for eq in eqs] + end + ddvs = map(D, dvs) + else + check_operator_variables(eqs, Differential) + check_lhs(eqs, Differential, Set(dvs)) + rhss = [eq.rhs for eq in eqs] + end + + if !isempty(assertions(sys)) + rhss[end] += unwrap(get_assertions_expr(sys)) + end + + # TODO: add an optional check on the ordering of observed equations + if scalar + rhss = only(rhss) + u = only(u) + end + + args = (u, p...) + p_start = 2 + if t !== nothing + args = (args..., t) + end + if implicit_dae + args = (ddvs, args...) + p_start += 1 + end + + build_function_wrapper(sys, rhss, args...; p_start, extra_assignments, kwargs...) +end + +function calculate_jacobian(sys::System; + sparse = false, simplify = false, dvs = unknowns(sys)) + obs = Dict(eq.lhs => eq.rhs for eq in observed(sys)) + rhs = map(eq -> fixpoint_sub(eq.rhs, obs), equations(sys)) + + if sparse + jac = sparsejacobian(rhs, dvs, simplify) + W_s = W_sparsity(sys) + (Is, Js, Vs) = findnz(W_s) + # Add nonzeros of W as non-structural zeros of the Jacobian (to ensure equal + # results for oop and iip Jacobian) + for (i, j) in zip(Is, Js) + iszero(jac[i, j]) && begin + jac[i, j] = 1 + jac[i, j] = 0 + end + end + else + jac = jacobian(rhs, dvs; simplify) + end + + return jac +end + From d214774e2b6d5ef3ddb9c3b530362aed91458c09 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Sun, 13 Apr 2025 17:51:19 +0530 Subject: [PATCH 004/185] feat: add `is_dde` to `System` --- src/systems/diffeqs/abstractodesystem.jl | 15 --------------- src/systems/system.jl | 23 +++++++++++++++++++++++ 2 files changed, 23 insertions(+), 15 deletions(-) diff --git a/src/systems/diffeqs/abstractodesystem.jl b/src/systems/diffeqs/abstractodesystem.jl index d9e8b05eeb..2aa1a6a37e 100644 --- a/src/systems/diffeqs/abstractodesystem.jl +++ b/src/systems/diffeqs/abstractodesystem.jl @@ -11,21 +11,6 @@ differential equations. """ is_dde(sys::AbstractSystem) = has_is_dde(sys) && get_is_dde(sys) -function _check_if_dde(eqs, iv, subsystems) - is_dde = any(ModelingToolkit.is_dde, subsystems) - if !is_dde - vs = Set() - for eq in eqs - vars!(vs, eq) - is_dde = any(vs) do sym - isdelay(unwrap(sym), iv) - end - is_dde && break - end - end - return is_dde -end - function filter_kwargs(kwargs) kwargs = Dict(kwargs) for key in keys(kwargs) diff --git a/src/systems/system.jl b/src/systems/system.jl index 84b58d311c..91601a29ef 100644 --- a/src/systems/system.jl +++ b/src/systems/system.jl @@ -257,6 +257,29 @@ function is_discrete_system(sys::System) any(eq -> isoperator(eq.lhs, Shift), equations(sys)) end +""" + is_dde(sys::System) + +Return a boolean indicating whether a system represents a set of delay +differential equations. +""" +is_dde(sys::System) = has_is_dde(sys) && get_is_dde(sys) + +function _check_if_dde(eqs, iv, subsystems) + is_dde = any(ModelingToolkit.is_dde, subsystems) + if !is_dde + vs = Set() + for eq in eqs + vars!(vs, eq) + is_dde = any(vs) do sym + isdelay(unwrap(sym), iv) + end + is_dde && break + end + end + return is_dde +end + struct IllFormedNoiseEquationsError <: Exception noise_eqs_rows::Int eqs_length::Int From 6b6f47d915938e9c7a03bd0244bb8dfadb23b742 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Sun, 13 Apr 2025 17:51:42 +0530 Subject: [PATCH 005/185] feat: add more codegen implementation --- src/systems/codegen.jl | 154 +++++++++++++++++++++-- src/systems/diffeqs/abstractodesystem.jl | 10 -- 2 files changed, 145 insertions(+), 19 deletions(-) diff --git a/src/systems/codegen.jl b/src/systems/codegen.jl index 8537687490..ab35e74c4c 100644 --- a/src/systems/codegen.jl +++ b/src/systems/codegen.jl @@ -68,21 +68,38 @@ function generate_rhs(sys::System, dvs = unknowns(sys), build_function_wrapper(sys, rhss, args...; p_start, extra_assignments, kwargs...) end +function calculate_tgrad(sys::System; simplify = false) + # We need to remove explicit time dependence on the unknown because when we + # have `u(t) * t` we want to have the tgrad to be `u(t)` instead of `u'(t) * + # t + u(t)`. + rhs = [detime_dvs(eq.rhs) for eq in full_equations(sys)] + iv = get_iv(sys) + xs = unknowns(sys) + rule = Dict(map((x, xt) -> xt => x, detime_dvs.(xs), xs)) + rhs = substitute.(rhs, Ref(rule)) + tgrad = [expand_derivatives(Differential(iv)(r), simplify) for r in rhs] + reverse_rule = Dict(map((x, xt) -> x => xt, detime_dvs.(xs), xs)) + tgrad = Num.(substitute.(tgrad, Ref(reverse_rule))) + return tgrad +end + function calculate_jacobian(sys::System; sparse = false, simplify = false, dvs = unknowns(sys)) obs = Dict(eq.lhs => eq.rhs for eq in observed(sys)) rhs = map(eq -> fixpoint_sub(eq.rhs, obs), equations(sys)) if sparse - jac = sparsejacobian(rhs, dvs, simplify) - W_s = W_sparsity(sys) - (Is, Js, Vs) = findnz(W_s) - # Add nonzeros of W as non-structural zeros of the Jacobian (to ensure equal - # results for oop and iip Jacobian) - for (i, j) in zip(Is, Js) - iszero(jac[i, j]) && begin - jac[i, j] = 1 - jac[i, j] = 0 + jac = sparsejacobian(rhs, dvs; simplify) + if get_iv(sys) !== nothing + W_s = W_sparsity(sys) + (Is, Js, Vs) = findnz(W_s) + # Add nonzeros of W as non-structural zeros of the Jacobian (to ensure equal + # results for oop and iip Jacobian) + for (i, j) in zip(Is, Js) + iszero(jac[i, j]) && begin + jac[i, j] = 1 + jac[i, j] = 0 + end end end else @@ -92,3 +109,122 @@ function calculate_jacobian(sys::System; return jac end +function generate_jacobian(sys::System, dvs = unknowns(sys), + ps = parameters(sys; initial_parameters = true); + simplify = false, sparse = false, kwargs...) + jac = calculate_jacobian(sys; simplify, sparse, dvs) + p = reorder_parameters(sys, ps) + t = get_iv(sys) + if t !== nothing + wrap_code = sparse ? assert_jac_length_header(sys) : (identity, identity) + end + return build_function_wrapper(sys, jac, dvs, p..., t; wrap_code, kwargs...) +end + +function assert_jac_length_header(sys) + W = W_sparsity(sys) + identity, + function add_header(expr) + Func(expr.args, [], expr.body, + [:(@assert $(SymbolicUtils.Code.toexpr(term(findnz, expr.args[1])))[1:2] == + $(findnz(W)[1:2]))]) + end +end + +function generate_tgrad( + sys::System, dvs = unknowns(sys), ps = parameters( + sys; initial_parameters = true); + simplify = false, kwargs...) + tgrad = calculate_tgrad(sys, simplify = simplify) + p = reorder_parameters(sys, ps) + return build_function_wrapper(sys, tgrad, + dvs, + p..., + get_iv(sys); + kwargs...) +end + +const W_GAMMA = only(@variables ˍ₋gamma) + +function generate_W(sys::System, γ = 1.0, dvs = unknowns(sys), + ps = parameters(sys; initial_parameters = true); + simplify = false, sparse = false, kwargs...) + M = calculate_massmatrix(sys; simplify) + if sparse + M = SparseArrays.sparse(M) + end + J = calculate_jacobian(sys; simplify, sparse, dvs) + W = W_GAMMA * M + J + t = get_iv(sys) + if t !== nothing + wrap_code = sparse ? assert_jac_length_header(sys) : (identity, identity) + end + + p = reorder_parameters(sys, ps) + return build_function_wrapper(sys, W, dvs, p..., W_GAMMA, t; wrap_code, + p_end = 1 + length(p), kwargs...) +end + +function generate_dae_jacobian(sys::System, dvs = unknowns(sys), + ps = parameters(sys; initial_parameters = true); simplify = false, sparse = false, + kwargs...) + jac_u = calculate_jacobian(sys; simplify = simplify, sparse = sparse) + t = get_iv(sys) + derivatives = Differential(t).(unknowns(sys)) + jac_du = calculate_jacobian(sys; simplify = simplify, sparse = sparse, + dvs = derivatives) + dvs = unknowns(sys) + jac = W_GAMMA * jac_du + jac_u + p = reorder_parameters(sys, ps) + return build_function_wrapper(sys, jac, derivatives, dvs, p..., W_GAMMA, t; + p_start = 3, p_end = 2 + length(p), kwargs...) +end + +function calculate_massmatrix(sys::System; simplify = false) + eqs = [eq for eq in equations(sys)] + M = zeros(length(eqs), length(eqs)) + for (i, eq) in enumerate(eqs) + if iscall(eq.lhs) && operation(eq.lhs) isa Differential + st = var_from_nested_derivative(eq.lhs)[1] + j = variable_index(sys, st) + M[i, j] = 1 + else + _iszero(eq.lhs) || + error("Only semi-explicit constant mass matrices are currently supported. Faulty equation: $eq.") + end + end + M = simplify ? simplify.(M) : M + # M should only contain concrete numbers + M == I ? I : M +end + +function jacobian_sparsity(sys::System) + sparsity = torn_system_jacobian_sparsity(sys) + sparsity === nothing || return sparsity + + Symbolics.jacobian_sparsity([eq.rhs for eq in full_equations(sys)], + [dv for dv in unknowns(sys)]) +end + +function jacobian_dae_sparsity(sys::System) + J1 = jacobian_sparsity([eq.rhs for eq in full_equations(sys)], + [dv for dv in unknowns(sys)]) + derivatives = Differential(get_iv(sys)).(unknowns(sys)) + J2 = jacobian_sparsity([eq.rhs for eq in full_equations(sys)], + [dv for dv in derivatives]) + J1 + J2 +end + +function W_sparsity(sys::System) + jac_sparsity = jacobian_sparsity(sys) + (n, n) = size(jac_sparsity) + M = calculate_massmatrix(sys) + M_sparsity = M isa UniformScaling ? sparse(I(n)) : + SparseMatrixCSC{Bool, Int64}((!iszero).(M)) + jac_sparsity .| M_sparsity +end + +function isautonomous(sys::System) + tgrad = calculate_tgrad(sys; simplify = true) + all(iszero, tgrad) +end diff --git a/src/systems/diffeqs/abstractodesystem.jl b/src/systems/diffeqs/abstractodesystem.jl index 2aa1a6a37e..45b5b98b72 100644 --- a/src/systems/diffeqs/abstractodesystem.jl +++ b/src/systems/diffeqs/abstractodesystem.jl @@ -124,16 +124,6 @@ function generate_jacobian(sys::AbstractODESystem, dvs = unknowns(sys), kwargs...) end -function assert_jac_length_header(sys) - W = W_sparsity(sys) - identity, - function add_header(expr) - Func(expr.args, [], expr.body, - [:(@assert $(SymbolicUtils.Code.toexpr(term(findnz, expr.args[1])))[1:2] == - $(findnz(W)[1:2]))]) - end -end - function generate_W(sys::AbstractODESystem, γ = 1.0, dvs = unknowns(sys), ps = parameters(sys; initial_parameters = true); simplify = false, sparse = false, kwargs...) From b91115a4cc119012d77748b5e230cb04275e6cab Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Mon, 14 Apr 2025 23:13:05 +0530 Subject: [PATCH 006/185] refactor: remove old `System` function --- src/systems/systems.jl | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/systems/systems.jl b/src/systems/systems.jl index 52f93afb9b..273953936c 100644 --- a/src/systems/systems.jl +++ b/src/systems/systems.jl @@ -1,8 +1,3 @@ -function System(eqs::AbstractVector{<:Equation}, iv, args...; name = nothing, - kw...) - ODESystem(eqs, iv, args...; name, kw..., checks = false) -end - const REPEATED_SIMPLIFICATION_MESSAGE = "Structural simplification cannot be applied to a completed system. Double simplification is not allowed." struct RepeatedStructuralSimplificationError <: Exception end From 0e494a3139f7367f788a379b424adcd759dd8b1f Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Mon, 14 Apr 2025 23:13:46 +0530 Subject: [PATCH 007/185] refactor: change defaults of `costs` and `consolidate` --- src/systems/system.jl | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/systems/system.jl b/src/systems/system.jl index 91601a29ef..cf97289530 100644 --- a/src/systems/system.jl +++ b/src/systems/system.jl @@ -75,9 +75,13 @@ struct System <: AbstractSystem end end +function default_consolidate(costs, subcosts) + return sum(costs; init = 0.0) + sum(subcosts; init = 0.0) +end + function System(eqs, iv, dvs, ps, brownians = []; constraints = Union{Equation, Inequality}[], noise_eqs = nothing, jumps = [], - costs = [], consolidate = nothing, + costs = BasicSymbolic[], consolidate = default_consolidate, observed = Equation[], parameter_dependencies = Equation[], defaults = Dict(), guesses = Dict(), systems = System[], initialization_eqs = Equation[], continuous_events = SymbolicContinuousCallback[], discrete_events = SymbolicDiscreteCallback[], From 84cd0c636fcd3aa71f84c19d3e9c948f4959035a Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Mon, 14 Apr 2025 23:13:59 +0530 Subject: [PATCH 008/185] feat: implement `is_time_dependent` for `System` --- src/systems/system.jl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/systems/system.jl b/src/systems/system.jl index cf97289530..e7877646ac 100644 --- a/src/systems/system.jl +++ b/src/systems/system.jl @@ -261,6 +261,8 @@ function is_discrete_system(sys::System) any(eq -> isoperator(eq.lhs, Shift), equations(sys)) end +SymbolicIndexingInterface.is_time_dependent(sys::System) = get_iv(sys) !== nothing + """ is_dde(sys::System) From d0e2b88e11380e20d3dc221abf2e2084351caba0 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Mon, 14 Apr 2025 23:14:54 +0530 Subject: [PATCH 009/185] feat: add getters for new `System` fields --- src/systems/abstractsystem.jl | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/systems/abstractsystem.jl b/src/systems/abstractsystem.jl index 1252367d2b..39d0a677b3 100644 --- a/src/systems/abstractsystem.jl +++ b/src/systems/abstractsystem.jl @@ -876,11 +876,14 @@ end for prop in [:eqs :tag - :noiseeqs + :noiseeqs # TODO: remove + :noise_eqs :iv :unknowns :ps :tspan + :brownians + :jumps :name :description :var_to_name From ad09ce5278da56efa65c2836a657067f4147456a Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Mon, 14 Apr 2025 23:15:20 +0530 Subject: [PATCH 010/185] feat: add hierarchical aggregator functions for jumps, brownians and cost --- src/systems/abstractsystem.jl | 51 +++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/src/systems/abstractsystem.jl b/src/systems/abstractsystem.jl index 39d0a677b3..230ef1bb13 100644 --- a/src/systems/abstractsystem.jl +++ b/src/systems/abstractsystem.jl @@ -1301,6 +1301,28 @@ function namespace_equation(eq::Equation, (_lhs ~ _rhs)::Equation end +function namespace_jump(j::ConstantRateJump, sys) + return ConstantRateJump(namespace_expr(j.rate, sys), namespace_expr(j.affect!, sys)) +end + +function namespace_jump(j::VariableRateJump, sys) + return VariableRateJump(namespace_expr(j.rate, sys), namespace_expr(j.affect!, sys)) +end + +function namespace_jump(j::MassActionJump, sys) + return MassActionJump(namespace_expr(j.scaled_rates, sys), + [namespace_expr(k, sys) => namespace_expr(v, sys) for (k, v) in j.reactant_stoch], + [namespace_expr(k, sys) => namespace_expr(v, sys) for (k, v) in j.net_stoch]) +end + +function namespace_jumps(sys::AbstractSystem) + return [namespace_jump(j, sys) for j in get_jumps(sys)] +end + +function namespace_brownians(sys::AbstractSystem) + return [renamespace(sys, b) for b in brownians(sys)] +end + function namespace_assignment(eq::Assignment, sys) _lhs = namespace_expr(eq.lhs, sys) _rhs = namespace_expr(eq.rhs, sys) @@ -1734,6 +1756,35 @@ function equations_toplevel(sys::AbstractSystem) return get_eqs(sys) end +function jumps(sys::AbstractSystem) + js = get_jumps(sys) + systems = get_systems(sys) + if isempty(systems) + return js + end + return [js; reduce(vcat, namespace_jumps.(systems); init = [])] +end + +function brownians(sys::AbstractSystem) + bs = get_brownians(sys) + systems = get_systems(sys) + if isempty(systems) + return bs + end + return [bs; reduce(vcat, namespace_brownians.(systems); init = [])] +end + +function cost(sys::AbstractSystem) + cs = get_costs(sys) + consolidate = get_consolidate(sys) + systems = get_systems(sys) + if isempty(systems) + return consolidate(cs, Float64[]) + end + subcosts = [namespace_expr(cost(subsys), subsys) for subsys in systems] + return consolidate(cs, subcosts) +end + """ $(TYPEDSIGNATURES) From aa7b87f6675bec7e09322ee2723b4490feb6167e Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Mon, 14 Apr 2025 23:16:30 +0530 Subject: [PATCH 011/185] feat: add `check_complete` --- src/systems/system.jl | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/systems/system.jl b/src/systems/system.jl index e7877646ac..557c91eeaa 100644 --- a/src/systems/system.jl +++ b/src/systems/system.jl @@ -286,6 +286,24 @@ function _check_if_dde(eqs, iv, subsystems) return is_dde end +""" + $(TYPEDSIGNATURES) +""" +function check_complete(sys::System, obj) + iscomplete(sys) || throw(SystemNotCompleteError(obj)) +end + +struct SystemNotCompleteError <: Exception + obj::Any +end + +function Base.showerror(io::IO, err::SystemNotCompleteError) + print(io, """ + A completed system is required. Call `complete` or `structural_simplify` on the \ + system before creating a `$(err.obj)`. + """) +end + struct IllFormedNoiseEquationsError <: Exception noise_eqs_rows::Int eqs_length::Int From 5d24923ba628c136ad1f024edd518dbebb935806 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Mon, 14 Apr 2025 23:16:47 +0530 Subject: [PATCH 012/185] feat: add `@fallback_iip_specialize` --- src/systems/problem_utils.jl | 43 ++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/src/systems/problem_utils.jl b/src/systems/problem_utils.jl index 47bf3c678d..a14bc7ee39 100644 --- a/src/systems/problem_utils.jl +++ b/src/systems/problem_utils.jl @@ -1226,6 +1226,49 @@ function SciMLBase.detect_cycles(sys::AbstractSystem, varmap::Dict{Any, Any}, va return !isempty(cycles) end +""" + $(TYPEDSIGNATURES) + +Macro for writing problem/function constructors. Expects a function definition with type +parameters for `iip` and `specialize`. Generates fallbacks with +`specialize = SciMLBase.FullSpecialize` and `iip = true`. +""" +macro fallback_iip_specialize(ex) + @assert Meta.isexpr(ex, :function) + fnname, body = ex.args + @assert Meta.isexpr(fnname, :where) + fnname_call, where_args... = fnname.args + @assert length(where_args) == 2 + iiparg, specarg = where_args + + @assert Meta.isexpr(fnname_call, :call) + fnname_curly, args... = fnname_call.args + args = map(args) do arg + Meta.isexpr(arg, :kw) && return arg.args[1] + return arg + end + + @assert Meta.isexpr(fnname_curly, :curly) + fnname_name, curly_args... = fnname_curly.args + @assert curly_args == where_args + + callexpr_iip = Expr( + :call, Expr(:curly, fnname_name, curly_args[1], SciMLBase.FullSpecialize), args...) + fnname_iip = Expr(:curly, fnname_name, curly_args[1]) + fncall_iip = Expr(:call, fnname_iip, args...) + fnwhere_iip = Expr(:where, fncall_iip, where_args[1]) + fn_iip = Expr(:function, fnwhere_iip, callexpr_iip) + + callexpr_base = Expr(:call, Expr(:curly, fnname_name, true), args...) + fncall_base = Expr(:call, fnname_name, args...) + fn_base = Expr(:function, fncall_base, callexpr_base) + return quote + $fn_base + $fn_iip + Base.@__doc__ $ex + end |> esc +end + ############## # Legacy functions for backward compatibility ############## From bae1201244123db0594733652e2b5e702b4e7135 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Mon, 14 Apr 2025 23:16:58 +0530 Subject: [PATCH 013/185] feat: add `check_compatible_system` --- src/systems/codegen.jl | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/systems/codegen.jl b/src/systems/codegen.jl index ab35e74c4c..53c6711c88 100644 --- a/src/systems/codegen.jl +++ b/src/systems/codegen.jl @@ -228,3 +228,15 @@ function isautonomous(sys::System) tgrad = calculate_tgrad(sys; simplify = true) all(iszero, tgrad) end + +function check_compatible_system end + +struct SystemCompatibilityError <: Exception + msg::String +end + +function Base.showerror(io::IO, err::SystemCompatibilityError) + println(io, err.msg) + println(io) + print(io, "To disable this check, pass `check_compatibility = false`.") +end From eab2273c729ea3ebcd011fb0cc17373ef97b57ed Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Mon, 14 Apr 2025 23:17:20 +0530 Subject: [PATCH 014/185] feat: implement `ODEProblem` and `ODEFunction` for `System` --- src/problems/odeproblem.jl | 149 +++++++++++++++++++++++++++++++++++++ 1 file changed, 149 insertions(+) create mode 100644 src/problems/odeproblem.jl diff --git a/src/problems/odeproblem.jl b/src/problems/odeproblem.jl new file mode 100644 index 0000000000..3e9e49e6b0 --- /dev/null +++ b/src/problems/odeproblem.jl @@ -0,0 +1,149 @@ +@fallback_iip_specialize function SciMLBase.ODEFunction{iip, spec}( + sys::System, _d = nothing, u0 = nothing, p = nothing; tgrad = false, jac = false, + t = nothing, eval_expression = false, eval_module = @__MODULE__, sparse = false, + steady_state = false, checkbounds = false, sparsity = false, analytic = nothing, + simplify = false, cse = true, initialization_data = nothing, + check_compatibility = true, kwargs...) where {iip, spec} + check_complete(sys, ODEFunction) + check_compatibility && check_compatible_system(ODEFunction, sys) + + dvs = unknowns(sys) + ps = parameters(sys) + f_gen = generate_rhs(sys, dvs, ps; expression = Val{true}, + expression_module = eval_module, checkbounds = checkbounds, cse, + kwargs...) + f_oop, f_iip = eval_or_rgf.(f_gen; eval_expression, eval_module) + f = GeneratedFunctionWrapper{(2, 3, is_split(sys))}(f_oop, f_iip) + + if spec === SciMLBase.FunctionWrapperSpecialize && iip + if u0 === nothing || p === nothing || t === nothing + error("u0, p, and t must be specified for FunctionWrapperSpecialize on ODEFunction.") + end + f = SciMLBase.wrapfun_iip(f, (u0, u0, p, t)) + end + + if tgrad + tgrad_gen = generate_tgrad(sys, dvs, ps; + simplify = simplify, + expression = Val{true}, + expression_module = eval_module, cse, + checkbounds = checkbounds, kwargs...) + tgrad_oop, tgrad_iip = eval_or_rgf.(tgrad_gen; eval_expression, eval_module) + _tgrad = GeneratedFunctionWrapper{(2, 3, is_split(sys))}(tgrad_oop, tgrad_iip) + else + _tgrad = nothing + end + + if jac + jac_gen = generate_jacobian(sys, dvs, ps; + simplify = simplify, sparse = sparse, + expression = Val{true}, + expression_module = eval_module, cse, + checkbounds = checkbounds, kwargs...) + jac_oop, jac_iip = eval_or_rgf.(jac_gen; eval_expression, eval_module) + + _jac = GeneratedFunctionWrapper{(2, 3, is_split(sys))}(jac_oop, jac_iip) + else + _jac = nothing + end + + M = calculate_massmatrix(sys) + + _M = if sparse && !(u0 === nothing || M === I) + SparseArrays.sparse(M) + elseif u0 === nothing || M === I + M + else + ArrayInterface.restructure(u0 .* u0', M) + end + + observedfun = ObservedFunctionCache( + sys; steady_state, eval_expression, eval_module, checkbounds, cse) + + if sparse + uElType = u0 === nothing ? Float64 : eltype(u0) + W_prototype = similar(W_sparsity(sys), uElType) + else + W_prototype = nothing + end + + ODEFunction{iip, spec}(f; + sys = sys, + jac = _jac, + tgrad = _tgrad, + mass_matrix = _M, + jac_prototype = W_prototype, + observed = observedfun, + sparsity = sparsity ? W_sparsity(sys) : nothing, + analytic = analytic, + initialization_data) +end + +@fallback_iip_specialize function SciMLBase.ODEProblem{iip, spec}( + sys::System, u0map, tspan, parammap = SciMLBase.NullParameters(); + callback = nothing, check_length = true, eval_expression = false, + eval_module = @__MODULE__, check_compatibility = true, kwargs...) where {iip, spec} + check_complete(sys, ODEProblem) + check_compatibility && check_compatible_system(ODEProblem, sys) + + f, u0, p = process_SciMLProblem(ODEFunction{iip, spec}, sys, u0map, parammap; + t = tspan !== nothing ? tspan[1] : tspan, + check_length, eval_expression, eval_module, kwargs...) + cbs = process_events(sys; callback, eval_expression, eval_module, kwargs...) + + kwargs = filter_kwargs(kwargs) + + kwargs1 = (;) + if cbs !== nothing + kwargs1 = merge(kwargs1, (callback = cbs,)) + end + + tstops = SymbolicTstops(sys; eval_expression, eval_module) + if tstops !== nothing + kwargs1 = merge(kwargs1, (; tstops)) + end + + # Call `remake` so it runs initialization if it is trivial + return remake(ODEProblem{iip}( + f, u0, tspan, p, StandardODEProblem(); kwargs1..., kwargs...)) +end + +function check_compatible_system(T::Union{Type{ODEFunction}, Type{ODEProblem}}, sys::System) + if !is_time_dependent(sys) + throw(SystemCompatibilityError(""" + `$T` requires a time-dependent system. + """)) + end + + cost = get_costs(sys) + if cost isa Vector && !isempty(cost) || + cost isa Union{BasicSymbolic, Real} && !_iszero(cost) + throw(SystemCompatibilityError(""" + `$T` will not optimize solutions of systems that have associated cost \ + functions. Solvers for optimal control problems are forthcoming. In order to \ + bypass this error (e.g. to check the cost of a regular solution), pass \ + `allow_cost = true` into the constructor. + """)) + end + + if !isempty(constraints(sys)) + throw(SystemCompatibilityError(""" + A system with constraints cannot be used to construct an `$T`. Consider a \ + `BVProblem` instead. + """)) + end + + if !isempty(jumps(sys)) + throw(SystemCompatibilityError(""" + A system with jumps cannot be used to construct an `$T`. Consider a \ + `JumpProblem` instead. + """)) + end + + if get_noise_eqs(sys) !== nothing + throw(SystemCompatibilityError(""" + A system with jumps cannot be used to construct an `$T`. Consider an \ + `SDEProblem` instead. + """)) + end +end From fd305d5fa788fcfe74b7c8febec7dfb2ae17caff Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Mon, 14 Apr 2025 23:53:12 +0530 Subject: [PATCH 015/185] feat: implement `generate_initializesystem` for `System` --- src/systems/nonlinear/initializesystem.jl | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/systems/nonlinear/initializesystem.jl b/src/systems/nonlinear/initializesystem.jl index 5995272be9..eb4bfeff03 100644 --- a/src/systems/nonlinear/initializesystem.jl +++ b/src/systems/nonlinear/initializesystem.jl @@ -1,9 +1,17 @@ +function generate_initializesystem(sys::AbstractSystem; kwargs...) + if is_time_dependent(sys) + generate_initializesystem_timevarying(sys; kwargs...) + else + generate_initializesystem_timeindependent(sys; kwargs...) + end +end + """ $(TYPEDSIGNATURES) Generate `NonlinearSystem` which initializes a problem from specified initial conditions of an `AbstractTimeDependentSystem`. """ -function generate_initializesystem(sys::AbstractTimeDependentSystem; +function generate_initializesystem_timevarying(sys::AbstractSystem; u0map = Dict(), pmap = Dict(), initialization_eqs = [], @@ -160,7 +168,7 @@ $(TYPEDSIGNATURES) Generate `NonlinearSystem` which initializes a problem from specified initial conditions of an `AbstractTimeDependentSystem`. """ -function generate_initializesystem(sys::AbstractTimeIndependentSystem; +function generate_initializesystem_timeindependent(sys::AbstractSystem; u0map = Dict(), pmap = Dict(), initialization_eqs = [], From 1d9700cffa1804651834320da6d45dae9f41887b Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Mon, 14 Apr 2025 23:55:02 +0530 Subject: [PATCH 016/185] refactor: don't warn about system supertype for `System` --- src/systems/abstractsystem.jl | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/systems/abstractsystem.jl b/src/systems/abstractsystem.jl index 230ef1bb13..4815eab645 100644 --- a/src/systems/abstractsystem.jl +++ b/src/systems/abstractsystem.jl @@ -244,7 +244,9 @@ Get the independent variable(s) of the system `sys`. See also [`@independent_variables`](@ref) and [`ModelingToolkit.get_iv`](@ref). """ function independent_variables(sys::AbstractSystem) - @warn "Please declare ($(typeof(sys))) as a subtype of `AbstractTimeDependentSystem`, `AbstractTimeIndependentSystem` or `AbstractMultivariateSystem`." + if !(sys isa System) + @warn "Please declare ($(typeof(sys))) as a subtype of `AbstractTimeDependentSystem`, `AbstractTimeIndependentSystem` or `AbstractMultivariateSystem`." + end if isdefined(sys, :iv) return [getfield(sys, :iv)] elseif isdefined(sys, :ivs) From 133ffb79c541fb597103fe48f951de688311a805 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Tue, 15 Apr 2025 00:50:49 +0530 Subject: [PATCH 017/185] feat: add `flatten(::System)` --- src/systems/system.jl | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/systems/system.jl b/src/systems/system.jl index 557c91eeaa..98272aef6f 100644 --- a/src/systems/system.jl +++ b/src/systems/system.jl @@ -286,6 +286,26 @@ function _check_if_dde(eqs, iv, subsystems) return is_dde end +function flatten(sys::System, noeqs = false) + systems = get_systems(sys) + isempty(systems) && return sys + + return System(noeqs ? Equation[] : equations(sys), get_iv(sys), unknowns(sys), + parameters(sys; initial_parameters = true), brownians(sys); + jumps = jumps(sys), constraints = constraints(sys), costs = cost(sys), + consolidate = default_consolidate, observed = observed(sys), + parameter_dependencies = parameter_dependencies(sys), defaults = defaults(sys), + guesses = guesses(sys), continuous_events = continuous_events(sys), + discrete_events = discrete_events(sys), assertions = assertions(sys), + is_dde = is_dde(sys), tstops = symbolic_tstops(sys), + ignored_connections = ignored_connections(sys), + # without this, any defaults/guesses obtained from metadata that were + # later removed by the user will be re-added. Right now, we just want to + # retain `defaults(sys)` as-is. + discover_from_metadata = false, + description = description(sys), name = nameof(sys)) +end + """ $(TYPEDSIGNATURES) """ From 6c0246e5f2435804e73997c6dee17277e71faf2d Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Wed, 16 Apr 2025 17:37:25 +0530 Subject: [PATCH 018/185] refactor: compile functions in `generate_*` --- src/systems/codegen.jl | 60 +++++++++++++++++++++++++++++++++++------- 1 file changed, 51 insertions(+), 9 deletions(-) diff --git a/src/systems/codegen.jl b/src/systems/codegen.jl index 53c6711c88..ee783c3e73 100644 --- a/src/systems/codegen.jl +++ b/src/systems/codegen.jl @@ -10,7 +10,8 @@ Generate the RHS function for the `equations` of a `System`. """ function generate_rhs(sys::System, dvs = unknowns(sys), ps = parameters(sys; initial_parameters = true); implicit_dae = false, - scalar = false, kwargs...) + scalar = false, expression = Val{true}, eval_expression = false, + eval_module = @__MODULE__, kwargs...) eqs = equations(sys) obs = observed(sys) u = dvs @@ -65,7 +66,20 @@ function generate_rhs(sys::System, dvs = unknowns(sys), p_start += 1 end - build_function_wrapper(sys, rhss, args...; p_start, extra_assignments, kwargs...) + res = build_function_wrapper(sys, rhss, args...; p_start, extra_assignments, + expression = Val{true}, expression_module = eval_module, kwargs...) + if expression == Val{true} + return res + end + + if res isa Tuple + f_oop, f_iip = eval_or_rgf.(res; eval_expression, eval_module) + else + f_oop = eval_or_rgf(res; eval_expression, eval_module) + f_iip = nothing + end + return GeneratedFunctionWrapper{(p_start, length(args) - length(p) + 1, is_split(sys))}( + f_oop, f_iip) end function calculate_tgrad(sys::System; simplify = false) @@ -111,14 +125,21 @@ end function generate_jacobian(sys::System, dvs = unknowns(sys), ps = parameters(sys; initial_parameters = true); - simplify = false, sparse = false, kwargs...) + simplify = false, sparse = false, eval_expression = false, + eval_module = @__MODULE__, expression = Val{true}, kwargs...) jac = calculate_jacobian(sys; simplify, sparse, dvs) p = reorder_parameters(sys, ps) t = get_iv(sys) if t !== nothing wrap_code = sparse ? assert_jac_length_header(sys) : (identity, identity) end - return build_function_wrapper(sys, jac, dvs, p..., t; wrap_code, kwargs...) + res = build_function_wrapper(sys, jac, dvs, p..., t; wrap_code, expression = Val{true}, + expression_module = eval_module, kwargs...) + if expression == Val{true} + return res + end + f_oop, f_iip = eval_or_rgf.(res; eval_expression, eval_module) + return GeneratedFunctionWrapper{(2, 3, is_split(sys))}(f_oop, f_iip) end function assert_jac_length_header(sys) @@ -134,21 +155,31 @@ end function generate_tgrad( sys::System, dvs = unknowns(sys), ps = parameters( sys; initial_parameters = true); - simplify = false, kwargs...) + simplify = false, eval_expression = false, eval_module = @__MODULE__, + expression = Val{true}, kwargs...) tgrad = calculate_tgrad(sys, simplify = simplify) p = reorder_parameters(sys, ps) - return build_function_wrapper(sys, tgrad, + res = build_function_wrapper(sys, tgrad, dvs, p..., get_iv(sys); + expression = Val{true}, + expression_module = eval_module, kwargs...) + + if expression == Val{true} + return res + end + f_oop, f_iip = eval_or_rgf.(res; eval_expression, eval_module) + return GeneratedFunctionWrapper{(2, 3, is_split(sys))}(f_oop, f_iip) end const W_GAMMA = only(@variables ˍ₋gamma) function generate_W(sys::System, γ = 1.0, dvs = unknowns(sys), ps = parameters(sys; initial_parameters = true); - simplify = false, sparse = false, kwargs...) + simplify = false, sparse = false, expression = Val{true}, + eval_expression = false, eval_module = @__MODULE__, kwargs...) M = calculate_massmatrix(sys; simplify) if sparse M = SparseArrays.sparse(M) @@ -161,12 +192,18 @@ function generate_W(sys::System, γ = 1.0, dvs = unknowns(sys), end p = reorder_parameters(sys, ps) - return build_function_wrapper(sys, W, dvs, p..., W_GAMMA, t; wrap_code, + res = build_function_wrapper(sys, W, dvs, p..., W_GAMMA, t; wrap_code, p_end = 1 + length(p), kwargs...) + if expression == Val{true} + return res + end + f_oop, f_iip = eval_or_rgf.(res; eval_expression, eval_module) + return GeneratedFunctionWrapper{(2, 4, is_split(sys))}(f_oop, f_iip) end function generate_dae_jacobian(sys::System, dvs = unknowns(sys), ps = parameters(sys; initial_parameters = true); simplify = false, sparse = false, + expression = Val{true}, eval_expression = false, eval_module = @__MODULE__, kwargs...) jac_u = calculate_jacobian(sys; simplify = simplify, sparse = sparse) t = get_iv(sys) @@ -176,8 +213,13 @@ function generate_dae_jacobian(sys::System, dvs = unknowns(sys), dvs = unknowns(sys) jac = W_GAMMA * jac_du + jac_u p = reorder_parameters(sys, ps) - return build_function_wrapper(sys, jac, derivatives, dvs, p..., W_GAMMA, t; + res = build_function_wrapper(sys, jac, derivatives, dvs, p..., W_GAMMA, t; p_start = 3, p_end = 2 + length(p), kwargs...) + if expression == Val{true} + return res + end + f_oop, f_iip = eval_or_rgf.(res; eval_expression, eval_module) + return GeneratedFunctionWrapper{(3, 5, is_split(sys))}(f_oop, f_iip) end function calculate_massmatrix(sys::System; simplify = false) From 09cbaead1aadb1e0b681927ffded09e308f048d3 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Tue, 15 Apr 2025 15:18:48 +0530 Subject: [PATCH 019/185] refactor: use new compiled `generate_*` functions --- src/problems/odeproblem.jl | 25 ++++++------------------- 1 file changed, 6 insertions(+), 19 deletions(-) diff --git a/src/problems/odeproblem.jl b/src/problems/odeproblem.jl index 3e9e49e6b0..c9a1077d62 100644 --- a/src/problems/odeproblem.jl +++ b/src/problems/odeproblem.jl @@ -9,11 +9,9 @@ dvs = unknowns(sys) ps = parameters(sys) - f_gen = generate_rhs(sys, dvs, ps; expression = Val{true}, - expression_module = eval_module, checkbounds = checkbounds, cse, + f = generate_rhs(sys, dvs, ps; expression = Val{false}, + eval_expression, eval_module, checkbounds = checkbounds, cse, kwargs...) - f_oop, f_iip = eval_or_rgf.(f_gen; eval_expression, eval_module) - f = GeneratedFunctionWrapper{(2, 3, is_split(sys))}(f_oop, f_iip) if spec === SciMLBase.FunctionWrapperSpecialize && iip if u0 === nothing || p === nothing || t === nothing @@ -23,26 +21,15 @@ end if tgrad - tgrad_gen = generate_tgrad(sys, dvs, ps; - simplify = simplify, - expression = Val{true}, - expression_module = eval_module, cse, - checkbounds = checkbounds, kwargs...) - tgrad_oop, tgrad_iip = eval_or_rgf.(tgrad_gen; eval_expression, eval_module) - _tgrad = GeneratedFunctionWrapper{(2, 3, is_split(sys))}(tgrad_oop, tgrad_iip) + _tgrad = generate_tgrad(sys, dvs, ps; expression = Val{false}, + simplify, cse, eval_expression, eval_module, checkbounds, kwargs...) else _tgrad = nothing end if jac - jac_gen = generate_jacobian(sys, dvs, ps; - simplify = simplify, sparse = sparse, - expression = Val{true}, - expression_module = eval_module, cse, - checkbounds = checkbounds, kwargs...) - jac_oop, jac_iip = eval_or_rgf.(jac_gen; eval_expression, eval_module) - - _jac = GeneratedFunctionWrapper{(2, 3, is_split(sys))}(jac_oop, jac_iip) + _jac = generate_jacobian(sys, dvs, ps; expression = Val{false}, + simplify, sparse, cse, eval_expression, eval_module, checkbounds, kwargs...) else _jac = nothing end From d59434a405f721571d49fd52e3f0d77424bd6aa1 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Tue, 15 Apr 2025 15:19:13 +0530 Subject: [PATCH 020/185] refactor: centralize mass matrix and W sparsity handling --- src/problems/odeproblem.jl | 19 ++++--------------- src/systems/codegen.jl | 16 ++++++++++++++++ 2 files changed, 20 insertions(+), 15 deletions(-) diff --git a/src/problems/odeproblem.jl b/src/problems/odeproblem.jl index c9a1077d62..425914dd90 100644 --- a/src/problems/odeproblem.jl +++ b/src/problems/odeproblem.jl @@ -35,24 +35,13 @@ end M = calculate_massmatrix(sys) - - _M = if sparse && !(u0 === nothing || M === I) - SparseArrays.sparse(M) - elseif u0 === nothing || M === I - M - else - ArrayInterface.restructure(u0 .* u0', M) - end + _M = concrete_massmatrix(M; sparse, u0) observedfun = ObservedFunctionCache( sys; steady_state, eval_expression, eval_module, checkbounds, cse) - if sparse - uElType = u0 === nothing ? Float64 : eltype(u0) - W_prototype = similar(W_sparsity(sys), uElType) - else - W_prototype = nothing - end + _W_sparsity = W_sparsity(sys) + W_prototype = calculate_W_prototype(_W_sparsity; u0, sparse) ODEFunction{iip, spec}(f; sys = sys, @@ -61,7 +50,7 @@ mass_matrix = _M, jac_prototype = W_prototype, observed = observedfun, - sparsity = sparsity ? W_sparsity(sys) : nothing, + sparsity = sparsity ? _W_sparsity : nothing, analytic = analytic, initialization_data) end diff --git a/src/systems/codegen.jl b/src/systems/codegen.jl index ee783c3e73..3a2a0ae34e 100644 --- a/src/systems/codegen.jl +++ b/src/systems/codegen.jl @@ -240,6 +240,16 @@ function calculate_massmatrix(sys::System; simplify = false) M == I ? I : M end +function concrete_massmatrix(M; sparse = false, u0 = nothing) + if sparse && !(u0 === nothing || M === I) + SparseArrays.sparse(M) + elseif u0 === nothing || M === I + M + else + ArrayInterface.restructure(u0 .* u0', M) + end +end + function jacobian_sparsity(sys::System) sparsity = torn_system_jacobian_sparsity(sys) sparsity === nothing || return sparsity @@ -266,6 +276,12 @@ function W_sparsity(sys::System) jac_sparsity .| M_sparsity end +function calculate_W_prototype(W_sparsity; u0 = nothing, sparse = false) + sparse || return nothing + uElType = u0 === nothing ? Float64 : eltype(u0) + return similar(W_sparsity, uElType) +end + function isautonomous(sys::System) tgrad = calculate_tgrad(sys; simplify = true) all(iszero, tgrad) From 69762a981edbad7551bee54aa0b040ffb0f3e5be Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Tue, 15 Apr 2025 15:15:31 +0530 Subject: [PATCH 021/185] refactor: centralize problem `kwargs` handling --- src/problems/odeproblem.jl | 20 ++++---------------- src/systems/problem_utils.jl | 20 ++++++++++++++++++++ 2 files changed, 24 insertions(+), 16 deletions(-) diff --git a/src/problems/odeproblem.jl b/src/problems/odeproblem.jl index 425914dd90..c4d2547a3a 100644 --- a/src/problems/odeproblem.jl +++ b/src/problems/odeproblem.jl @@ -63,25 +63,13 @@ end check_compatibility && check_compatible_system(ODEProblem, sys) f, u0, p = process_SciMLProblem(ODEFunction{iip, spec}, sys, u0map, parammap; - t = tspan !== nothing ? tspan[1] : tspan, - check_length, eval_expression, eval_module, kwargs...) - cbs = process_events(sys; callback, eval_expression, eval_module, kwargs...) - - kwargs = filter_kwargs(kwargs) - - kwargs1 = (;) - if cbs !== nothing - kwargs1 = merge(kwargs1, (callback = cbs,)) - end - - tstops = SymbolicTstops(sys; eval_expression, eval_module) - if tstops !== nothing - kwargs1 = merge(kwargs1, (; tstops)) - end + t = tspan !== nothing ? tspan[1] : tspan, check_length, eval_expression, + eval_module, check_compatibility, kwargs...) + kwargs = process_kwargs(sys; callback, eval_expression, eval_module, kwargs...) # Call `remake` so it runs initialization if it is trivial return remake(ODEProblem{iip}( - f, u0, tspan, p, StandardODEProblem(); kwargs1..., kwargs...)) + f, u0, tspan, p, StandardODEProblem(); kwargs...)) end function check_compatible_system(T::Union{Type{ODEFunction}, Type{ODEProblem}}, sys::System) diff --git a/src/systems/problem_utils.jl b/src/systems/problem_utils.jl index a14bc7ee39..cf57aee102 100644 --- a/src/systems/problem_utils.jl +++ b/src/systems/problem_utils.jl @@ -1226,6 +1226,26 @@ function SciMLBase.detect_cycles(sys::AbstractSystem, varmap::Dict{Any, Any}, va return !isempty(cycles) end +function process_kwargs(sys::System; callback = nothing, eval_expression = false, + eval_module = @__MODULE__, kwargs...) + kwargs = filter_kwargs(kwargs) + kwargs1 = (;) + + if is_time_dependent(sys) + cbs = process_events(sys; callback, eval_expression, eval_module, kwargs...) + if cbs !== nothing + kwargs1 = merge(kwargs1, (callback = cbs,)) + end + + tstops = SymbolicTstops(sys; eval_expression, eval_module) + if tstops !== nothing + kwargs1 = merge(kwargs1, (; tstops)) + end + end + + return merge(kwargs1, kwargs) +end + """ $(TYPEDSIGNATURES) From 2f22ef3df932ab4005270d44d9e88823f347a6db Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Tue, 15 Apr 2025 15:17:51 +0530 Subject: [PATCH 022/185] refactor: modularize compatibility checks --- src/problems/compatibility.jl | 81 +++++++++++++++++++++++++++++++++++ src/problems/odeproblem.jl | 43 +++---------------- src/systems/codegen.jl | 12 ------ 3 files changed, 87 insertions(+), 49 deletions(-) create mode 100644 src/problems/compatibility.jl diff --git a/src/problems/compatibility.jl b/src/problems/compatibility.jl new file mode 100644 index 0000000000..1d686220ac --- /dev/null +++ b/src/problems/compatibility.jl @@ -0,0 +1,81 @@ +""" + function check_compatible_system(T::Type, sys::System) + +Check if `sys` can be used to construct a problem/function of type `T`. +""" +function check_compatible_system end + +struct SystemCompatibilityError <: Exception + msg::String +end + +function Base.showerror(io::IO, err::SystemCompatibilityError) + println(io, err.msg) + println(io) + print(io, "To disable this check, pass `check_compatibility = false`.") +end + +function check_time_dependent(sys::System, T) + if !is_time_dependent(sys) + throw(SystemCompatibilityError(""" + `$T` requires a time-dependent system. + """)) + end +end + +function check_is_dde(sys::System) + altT = get_noise_eqs(sys) === nothing ? ODEProblem : SDEProblem + if !is_dde(sys) + throw(SystemCompatibilityError(""" + The system does not have delays. Consider an `$altT` instead. + """)) + end +end + +function check_not_dde(sys::System) + altT = get_noise_eqs(sys) === nothing ? DDEProblem : SDDEProblem + if is_dde(sys) + throw(SystemCompatibilityError(""" + The system has delays. Consider a `$altT` instead. + """)) + end +end + +function check_no_cost(sys::System, T) + cost = ModelingToolkit.cost(sys) + if !_iszero(cost) + throw(SystemCompatibilityError(""" + `$T` will not optimize solutions of systems that have associated cost \ + functions. Solvers for optimal control problems are forthcoming. In order to \ + bypass this error (e.g. to check the cost of a regular solution), pass \ + `allow_cost = true` into the constructor. + """)) + end +end + +function check_no_constraints(sys::System, T) + if !isempty(constraints(sys)) + throw(SystemCompatibilityError(""" + A system with constraints cannot be used to construct a `$T`. + """)) + end +end + +function check_no_jumps(sys::System, T) + if !isempty(jumps(sys)) + throw(SystemCompatibilityError(""" + A system with jumps cannot be used to construct a `$T`. Consider a \ + `JumpProblem` instead. + """)) + end +end + +function check_no_noise(sys::System, T) + altT = is_dde(sys) ? SDDEProblem : SDEProblem + if get_noise_eqs(sys) !== nothing + throw(SystemCompatibilityError(""" + A system with noise cannot be used to construct a `$T`. Consider an \ + `$altT` instead. + """)) + end +end diff --git a/src/problems/odeproblem.jl b/src/problems/odeproblem.jl index c4d2547a3a..8dcf29aba9 100644 --- a/src/problems/odeproblem.jl +++ b/src/problems/odeproblem.jl @@ -73,41 +73,10 @@ end end function check_compatible_system(T::Union{Type{ODEFunction}, Type{ODEProblem}}, sys::System) - if !is_time_dependent(sys) - throw(SystemCompatibilityError(""" - `$T` requires a time-dependent system. - """)) - end - - cost = get_costs(sys) - if cost isa Vector && !isempty(cost) || - cost isa Union{BasicSymbolic, Real} && !_iszero(cost) - throw(SystemCompatibilityError(""" - `$T` will not optimize solutions of systems that have associated cost \ - functions. Solvers for optimal control problems are forthcoming. In order to \ - bypass this error (e.g. to check the cost of a regular solution), pass \ - `allow_cost = true` into the constructor. - """)) - end - - if !isempty(constraints(sys)) - throw(SystemCompatibilityError(""" - A system with constraints cannot be used to construct an `$T`. Consider a \ - `BVProblem` instead. - """)) - end - - if !isempty(jumps(sys)) - throw(SystemCompatibilityError(""" - A system with jumps cannot be used to construct an `$T`. Consider a \ - `JumpProblem` instead. - """)) - end - - if get_noise_eqs(sys) !== nothing - throw(SystemCompatibilityError(""" - A system with jumps cannot be used to construct an `$T`. Consider an \ - `SDEProblem` instead. - """)) - end + check_time_dependent(sys, T) + check_not_dde(sys, T) + check_no_cost(sys, T) + check_no_constraints(sys, T) + check_no_jumps(sys, T) + check_no_noise(sys, T) end diff --git a/src/systems/codegen.jl b/src/systems/codegen.jl index 3a2a0ae34e..ad608ac1ed 100644 --- a/src/systems/codegen.jl +++ b/src/systems/codegen.jl @@ -286,15 +286,3 @@ function isautonomous(sys::System) tgrad = calculate_tgrad(sys; simplify = true) all(iszero, tgrad) end - -function check_compatible_system end - -struct SystemCompatibilityError <: Exception - msg::String -end - -function Base.showerror(io::IO, err::SystemCompatibilityError) - println(io, err.msg) - println(io) - print(io, "To disable this check, pass `check_compatibility = false`.") -end From 6219b7f2ca74ef2d8d191ab26fe042a55f732b45 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Tue, 15 Apr 2025 15:39:08 +0530 Subject: [PATCH 023/185] refactor: fix and document `delay_to_function`, implement it for `System` --- src/systems/codegen_utils.jl | 74 +++++++++++++++++++++++- src/systems/diffeqs/abstractodesystem.jl | 44 -------------- 2 files changed, 71 insertions(+), 47 deletions(-) diff --git a/src/systems/codegen_utils.jl b/src/systems/codegen_utils.jl index bedfdbcc37..8fba085ec4 100644 --- a/src/systems/codegen_utils.jl +++ b/src/systems/codegen_utils.jl @@ -86,6 +86,74 @@ function array_variable_assignments(args...; argument_name = generated_argument_ return assignments end +""" + $(TYPEDSIGNATURES) + +Check if the variable `var` is a delayed variable, where `iv` is the independent +variable. +""" +function isdelay(var, iv) + iv === nothing && return false + isvariable(var) || return false + isparameter(var) && return false + if iscall(var) && !ModelingToolkit.isoperator(var, Symbolics.Operator) + args = arguments(var) + length(args) == 1 || return false + isequal(args[1], iv) || return true + end + return false +end + +""" +The argument of generated functions corresponding to the history function. +""" +const DDE_HISTORY_FUN = Sym{Symbolics.FnType{Tuple{Any, <:Real}, Vector{Real}}}(:___history___) + +""" + $(TYPEDSIGNATURES) + +Turn delayed unknowns in `eqs` into calls to `DDE_HISTORY_FUNCTION`. + +# Arguments + +- `sys`: The system of DDEs. +- `eqs`: The equations to convert. + +# Keyword Arguments + +- `param_arg`: The name of the variable containing the parameter object. +""" +function delay_to_function( + sys::AbstractSystem, eqs = full_equations(sys); param_arg = MTKPARAMETERS_ARG) + delay_to_function(eqs, + get_iv(sys), + Dict{Any, Int}(operation(s) => i for (i, s) in enumerate(unknowns(sys))), + parameters(sys), + DDE_HISTORY_FUN; param_arg) +end +function delay_to_function(eqs::Vector, iv, sts, ps, h; param_arg = MTKPARAMETERS_ARG) + delay_to_function.(eqs, (iv,), (sts,), (ps,), (h,); param_arg) +end +function delay_to_function(eq::Equation, iv, sts, ps, h; param_arg = MTKPARAMETERS_ARG) + delay_to_function(eq.lhs, iv, sts, ps, h; param_arg) ~ delay_to_function( + eq.rhs, iv, sts, ps, h; param_arg) +end +function delay_to_function(expr, iv, sts, ps, h; param_arg = MTKPARAMETERS_ARG) + if isdelay(expr, iv) + v = operation(expr) + time = arguments(expr)[1] + idx = sts[v] + return term(getindex, h(param_arg, time), idx, type = Real) + elseif iscall(expr) + return maketerm(typeof(expr), + operation(expr), + map(x -> delay_to_function(x, iv, sts, ps, h; param_arg), arguments(expr)), + metadata(expr)) + else + return expr + end +end + """ $(TYPEDSIGNATURES) @@ -138,11 +206,11 @@ function build_function_wrapper(sys::AbstractSystem, expr, args...; p_start = 2, obs = filter(filter_observed, observed(sys)) # turn delayed unknowns into calls to the history function if wrap_delays - history_arg = is_split(sys) ? MTKPARAMETERS_ARG : generated_argument_name(p_start) + param_arg = is_split(sys) ? MTKPARAMETERS_ARG : generated_argument_name(p_start) obs = map(obs) do eq - delay_to_function(sys, eq; history_arg) + delay_to_function(sys, eq; param_arg) end - expr = delay_to_function(sys, expr; history_arg) + expr = delay_to_function(sys, expr; param_arg) # add extra argument args = (args[1:(p_start - 1)]..., DDE_HISTORY_FUN, args[p_start:end]...) p_start += 1 diff --git a/src/systems/diffeqs/abstractodesystem.jl b/src/systems/diffeqs/abstractodesystem.jl index 45b5b98b72..e293fa400a 100644 --- a/src/systems/diffeqs/abstractodesystem.jl +++ b/src/systems/diffeqs/abstractodesystem.jl @@ -200,50 +200,6 @@ function generate_function(sys::AbstractODESystem, dvs = unknowns(sys), end end -function isdelay(var, iv) - iv === nothing && return false - isvariable(var) || return false - isparameter(var) && return false - if iscall(var) && !ModelingToolkit.isoperator(var, Symbolics.Operator) - args = arguments(var) - length(args) == 1 || return false - isequal(args[1], iv) || return true - end - return false -end -const DDE_HISTORY_FUN = Sym{Symbolics.FnType{Tuple{Any, <:Real}, Vector{Real}}}(:___history___) -const DEFAULT_PARAMS_ARG = Sym{Any}(:ˍ₋arg3) -function delay_to_function( - sys::AbstractODESystem, eqs = full_equations(sys); history_arg = DEFAULT_PARAMS_ARG) - delay_to_function(eqs, - get_iv(sys), - Dict{Any, Int}(operation(s) => i for (i, s) in enumerate(unknowns(sys))), - parameters(sys), - DDE_HISTORY_FUN; history_arg) -end -function delay_to_function(eqs::Vector, iv, sts, ps, h; history_arg = DEFAULT_PARAMS_ARG) - delay_to_function.(eqs, (iv,), (sts,), (ps,), (h,); history_arg) -end -function delay_to_function(eq::Equation, iv, sts, ps, h; history_arg = DEFAULT_PARAMS_ARG) - delay_to_function(eq.lhs, iv, sts, ps, h; history_arg) ~ delay_to_function( - eq.rhs, iv, sts, ps, h; history_arg) -end -function delay_to_function(expr, iv, sts, ps, h; history_arg = DEFAULT_PARAMS_ARG) - if isdelay(expr, iv) - v = operation(expr) - time = arguments(expr)[1] - idx = sts[v] - return term(getindex, h(history_arg, time), idx, type = Real) # BIG BIG HACK - elseif iscall(expr) - return maketerm(typeof(expr), - operation(expr), - map(x -> delay_to_function(x, iv, sts, ps, h; history_arg), arguments(expr)), - metadata(expr)) - else - return expr - end -end - function calculate_massmatrix(sys::AbstractODESystem; simplify = false) eqs = [eq for eq in equations(sys)] M = zeros(length(eqs), length(eqs)) From 71599bfa460b8c04a06aefa7d89a529838cc22d8 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Tue, 15 Apr 2025 15:41:55 +0530 Subject: [PATCH 024/185] feat: implement `DDEProblem` and `DDEFunction` for `System` --- src/problems/ddeproblem.jl | 72 ++++++++++++++++++++++++++++++++++++++ src/systems/codegen.jl | 14 ++++++++ 2 files changed, 86 insertions(+) create mode 100644 src/problems/ddeproblem.jl diff --git a/src/problems/ddeproblem.jl b/src/problems/ddeproblem.jl new file mode 100644 index 0000000000..186e531241 --- /dev/null +++ b/src/problems/ddeproblem.jl @@ -0,0 +1,72 @@ +@fallback_iip_specialize function SciMLBase.DDEFunction{iip, spec}( + sys::System, _d = nothing, u0 = nothing, p = nothing; + eval_expression = false, eval_module = @__MODULE__, checkbounds = false, + initialization_data = nothing, cse = true, check_compatibility = true, + sparse = false, simplify = false, analytic = nothing, kwargs...) where { + iip, spec} + check_complete(sys, DDEFunction) + check_compatibility && check_compatible_system(DDEFunction, sys) + + dvs = unknowns(sys) + ps = parameters(sys) + + f = generate_rhs(sys, dvs, ps; expression = Val{false}, + eval_expression, eval_module, checkbounds = checkbounds, cse, + kwargs...) + + if spec === SciMLBase.FunctionWrapperSpecialize && iip + if u0 === nothing || p === nothing || t === nothing + error("u0, p, and t must be specified for FunctionWrapperSpecialize on DDEFunction.") + end + f = SciMLBase.wrapfun_iip(f, (u0, u0, p, t)) + end + + M = calculate_massmatrix(sys) + _M = concrete_massmatrix(M; sparse, u0) + + observedfun = ObservedFunctionCache( + sys; eval_expression, eval_module, checkbounds, cse) + + DDEFunction{iip, spec}(f; + sys = sys, + mass_matrix = _M, + observed = observedfun, + analytic = analytic, + initialization_data) +end + +@fallback_iip_specialize function SciMLBase.DDEProblem{iip, spec}( + sys::System, u0map, tspan, parammap = SciMLBase.NullParameters(); + callback = nothing, check_length = true, cse = true, checkbounds = false, + eval_expression = false, eval_module = @__MODULE__, check_compatibility = true, + u0_constructor = identity, + kwargs...) where {iip, spec} + check_complete(sys, DDEProblem) + check_compatibility && check_compatible_system(DDEProblem, sys) + + f, u0, p = process_SciMLProblem(DDEFunction{iip, spec}, sys, u0map, parammap; + t = tspan !== nothing ? tspan[1] : tspan, check_length, cse, checkbounds, + eval_expression, eval_module, check_compatibility, symbolic_u0 = true, kwargs...) + + h = generate_history( + sys, u0; expression = Val{false}, cse, eval_expression, eval_module, + checkbounds) + u0 = float.(h(p, tspan[1])) + if u0 !== nothing + u0 = u0_constructor(u0) + end + + kwargs = process_kwargs(sys; callback, eval_expression, eval_module, kwargs...) + + # Call `remake` so it runs initialization if it is trivial + return remake(DDEProblem{iip}(f, u0, h, tspan, p; kwargs...)) +end + +function check_compatible_system(T::Union{Type{DDEFunction}, Type{DDEProblem}}, sys::System) + check_time_dependent(sys, T) + check_is_dde(sys) + check_no_cost(sys, T) + check_no_constraints(sys, T) + check_no_jumps(sys, T) + check_no_noise(sys, T) +end diff --git a/src/systems/codegen.jl b/src/systems/codegen.jl index ad608ac1ed..a1bf1e822b 100644 --- a/src/systems/codegen.jl +++ b/src/systems/codegen.jl @@ -222,6 +222,20 @@ function generate_dae_jacobian(sys::System, dvs = unknowns(sys), return GeneratedFunctionWrapper{(3, 5, is_split(sys))}(f_oop, f_iip) end +function generate_history(sys::System, u0; expression = Val{true}, + eval_expression = false, eval_module = @__MODULE__, kwargs...) + p = reorder_parameters(sys) + res = build_function_wrapper(sys, u0, p..., get_iv(sys); expression = Val{true}, + expression_module = eval_module, p_start = 1, p_end = length(p), + similarto = typeof(u0), wrap_delays = false, kwargs...) + + if expression == Val{true} + return res + end + f_oop, f_iip = eval_or_rgf.(res; eval_expression, eval_module) + return GeneratedFunctionWrapper{(1, 2, is_split(sys))}(f_oop, f_iip) +end + function calculate_massmatrix(sys::System; simplify = false) eqs = [eq for eq in equations(sys)] M = zeros(length(eqs), length(eqs)) From 08994d53fc6878b9a0623e24796037a0e9808f3d Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Tue, 15 Apr 2025 17:38:33 +0530 Subject: [PATCH 025/185] feat: implement `DAEProblem` and `DAEFunction` for `System` --- src/problems/daeproblem.jl | 76 ++++++++++++++++++++++++++++++++++++++ src/problems/odeproblem.jl | 6 ++- 2 files changed, 80 insertions(+), 2 deletions(-) create mode 100644 src/problems/daeproblem.jl diff --git a/src/problems/daeproblem.jl b/src/problems/daeproblem.jl new file mode 100644 index 0000000000..d0ea2cf61b --- /dev/null +++ b/src/problems/daeproblem.jl @@ -0,0 +1,76 @@ +@fallback_iip_specialize function SciMLBase.DAEFunction{iip, spec}( + sys::System, _d = nothing, u0 = nothing, p = nothing; tgrad = false, jac = false, + t = nothing, eval_expression = false, eval_module = @__MODULE__, sparse = false, + steady_state = false, checkbounds = false, sparsity = false, analytic = nothing, + simplify = false, cse = true, initialization_data = nothing, + check_compatibility = true, kwargs...) where {iip, spec} + check_complete(sys, DAEFunction) + check_compatibility && check_compatible_system(DAEFunction, sys) + + dvs = unknowns(sys) + ps = parameters(sys) + f = generate_rhs(sys, dvs, ps; expression = Val{false}, implicit_dae = true, + eval_expression, eval_module, checkbounds = checkbounds, cse, + kwargs...) + + if spec === SciMLBase.FunctionWrapperSpecialize && iip + if u0 === nothing || p === nothing || t === nothing + error("u0, p, and t must be specified for FunctionWrapperSpecialize on ODEFunction.") + end + f = SciMLBase.wrapfun_iip(f, (u0, u0, p, t)) + end + + if jac + _jac = generate_dae_jacobian(sys, dvs, ps; expression = Val{false}, + simplify, sparse, cse, eval_expression, eval_module, checkbounds, kwargs...) + else + _jac = nothing + end + + observedfun = ObservedFunctionCache( + sys; steady_state, eval_expression, eval_module, checkbounds, cse) + + jac_prototype = if sparse + uElType = u0 === nothing ? Float64 : eltype(u0) + if jac + J1 = calculate_jacobian(sys, sparse = sparse) + derivatives = Differential(get_iv(sys)).(unknowns(sys)) + J2 = calculate_jacobian(sys; sparse = sparse, dvs = derivatives) + similar(J1 + J2, uElType) + else + similar(jacobian_dae_sparsity(sys), uElType) + end + else + nothing + end + + DAEFunction{iip, spec}(f; + sys = sys, + jac = _jac, + jac_prototype = jac_prototype, + observed = observedfun, + analytic = analytic, + initialization_data) +end + +@fallback_iip_specialize function SciMLBase.DAEProblem{iip, spec}( + sys::System, du0map, u0map, tspan, parammap = SciMLBase.NullParameters(); + callback = nothing, check_length = true, eval_expression = false, + eval_module = @__MODULE__, check_compatibility = true, kwargs...) where {iip, spec} + check_complete(sys, DAEProblem) + check_compatibility && check_compatible_system(DAEProblem, sys) + + f, du0, u0, p = process_SciMLProblem(DAEFunction{iip, spec}, sys, u0map, parammap; + du0map, t = tspan !== nothing ? tspan[1] : tspan, check_length, eval_expression, + eval_module, check_compatibility, implicit_dae = true, kwargs...) + + kwargs = process_kwargs(sys; callback, eval_expression, eval_module, kwargs...) + + diffvars = collect_differential_variables(sys) + sts = unknowns(sys) + differential_vars = map(Base.Fix2(in, diffvars), sts) + + # Call `remake` so it runs initialization if it is trivial + return remake(DAEProblem{iip}( + f, du0, u0, tspan, p; differential_vars, kwargs...)) +end diff --git a/src/problems/odeproblem.jl b/src/problems/odeproblem.jl index 8dcf29aba9..2da4675002 100644 --- a/src/problems/odeproblem.jl +++ b/src/problems/odeproblem.jl @@ -72,9 +72,11 @@ end f, u0, tspan, p, StandardODEProblem(); kwargs...)) end -function check_compatible_system(T::Union{Type{ODEFunction}, Type{ODEProblem}}, sys::System) +function check_compatible_system( + T::Union{Type{ODEFunction}, Type{ODEProblem}, Type{DAEFunction}, Type{DAEProblem}}, + sys::System) check_time_dependent(sys, T) - check_not_dde(sys, T) + check_not_dde(sys) check_no_cost(sys, T) check_no_constraints(sys, T) check_no_jumps(sys, T) From afb0039ebe801602ae68bd7d58f8d5a7718f0519 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Wed, 16 Apr 2025 17:07:15 +0530 Subject: [PATCH 026/185] refactor: remove `generate_factorized_W` --- src/systems/abstractsystem.jl | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/src/systems/abstractsystem.jl b/src/systems/abstractsystem.jl index 4815eab645..b4a687e157 100644 --- a/src/systems/abstractsystem.jl +++ b/src/systems/abstractsystem.jl @@ -113,17 +113,6 @@ the arguments to the internal [`build_function`](@ref) call. """ function generate_jacobian end -""" -```julia -generate_factorized_W(sys::AbstractSystem, dvs = unknowns(sys), ps = parameters(sys), - expression = Val{true}; sparse = false, kwargs...) -``` - -Generates a function for the factorized W matrix of a system. Extra arguments control -the arguments to the internal [`build_function`](@ref) call. -""" -function generate_factorized_W end - """ ```julia generate_hessian(sys::AbstractSystem, dvs = unknowns(sys), ps = parameters(sys), From 6216694a6fe00dfb0195cdaf660dc9c65508fe46 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Wed, 16 Apr 2025 17:07:36 +0530 Subject: [PATCH 027/185] feat: support `SDEProblem` and `SDEFunction` for `System` --- src/problems/compatibility.jl | 10 ++++ src/problems/sdeproblem.jl | 106 ++++++++++++++++++++++++++++++++++ src/systems/codegen.jl | 13 +++++ 3 files changed, 129 insertions(+) create mode 100644 src/problems/sdeproblem.jl diff --git a/src/problems/compatibility.jl b/src/problems/compatibility.jl index 1d686220ac..e53e84de8a 100644 --- a/src/problems/compatibility.jl +++ b/src/problems/compatibility.jl @@ -79,3 +79,13 @@ function check_no_noise(sys::System, T) """)) end end + +function check_has_noise(sys::System, T) + altT = is_dde(sys) ? DDEProblem : ODEProblem + if get_noise_eqs(sys) === nothing + throw(SystemCompatibilityError(""" + A system without noise cannot be used to construct a `$T`. Consider an \ + `$altT` instead. + """)) + end +end diff --git a/src/problems/sdeproblem.jl b/src/problems/sdeproblem.jl new file mode 100644 index 0000000000..2989bc485c --- /dev/null +++ b/src/problems/sdeproblem.jl @@ -0,0 +1,106 @@ +@fallback_iip_specialize function SciMLBase.SDEFunction{iip, spec}( + sys::System, _d = nothing, u0 = nothing, p = nothing; tgrad = false, jac = false, + t = nothing, eval_expression = false, eval_module = @__MODULE__, sparse = false, + steady_state = false, checkbounds = false, sparsity = false, analytic = nothing, + simplify = false, cse = true, initialization_data = nothing, + check_compatibility = true, kwargs...) where {iip, spec} + check_complete(sys, SDEFunction) + check_compatibility && check_compatible_system(SDEFunction, sys) + + dvs = unknowns(sys) + ps = parameters(sys) + f = generate_rhs(sys, dvs, ps; expression = Val{false}, + eval_expression, eval_module, checkbounds = checkbounds, cse, + kwargs...) + g = generate_diffusion_function(sys, dvs, ps; expression = Val{false}, + eval_expression, eval_module, checkbounds, cse, kwargs...) + + if spec === SciMLBase.FunctionWrapperSpecialize && iip + if u0 === nothing || p === nothing || t === nothing + error("u0, p, and t must be specified for FunctionWrapperSpecialize on ODEFunction.") + end + f = SciMLBase.wrapfun_iip(f, (u0, u0, p, t)) + end + + if tgrad + _tgrad = generate_tgrad(sys, dvs, ps; expression = Val{false}, + simplify, cse, eval_expression, eval_module, checkbounds, kwargs...) + else + _tgrad = nothing + end + + if jac + _jac = generate_jacobian(sys, dvs, ps; expression = Val{false}, + simplify, sparse, cse, eval_expression, eval_module, checkbounds, kwargs...) + else + _jac = nothing + end + + M = calculate_massmatrix(sys) + _M = concrete_massmatrix(M; sparse, u0) + + observedfun = ObservedFunctionCache( + sys; steady_state, eval_expression, eval_module, checkbounds, cse) + + _W_sparsity = W_sparsity(sys) + W_prototype = calculate_W_prototype(_W_sparsity; u0, sparse) + + SDEFunction{iip, spec}(f, g; + sys = sys, + jac = _jac, + tgrad = _tgrad, + mass_matrix = _M, + jac_prototype = W_prototype, + observed = observedfun, + sparsity = sparsity ? _W_sparsity : nothing, + analytic = analytic, + initialization_data) +end + +@fallback_iip_specialize function SciMLBase.SDEProblem{iip, spec}( + sys::System, u0map, tspan, parammap = SciMLBase.NullParameters(); + callback = nothing, check_length = true, eval_expression = false, + eval_module = @__MODULE__, check_compatibility = true, sparse = false, + sparsenoise = sparse, kwargs...) where {iip, spec} + check_complete(sys, SDEProblem) + check_compatibility && check_compatible_system(SDEProblem, sys) + + f, u0, p = process_SciMLProblem(SDEFunction{iip, spec}, sys, u0map, parammap; + t = tspan !== nothing ? tspan[1] : tspan, check_length, eval_expression, + eval_module, check_compatibility, sparse, kwargs...) + + noise, noise_rate_prototype = calculate_noise_and_rate_prototype(sys, u0; sparsenoise) + kwargs = process_kwargs(sys; callback, eval_expression, eval_module, kwargs...) + # Call `remake` so it runs initialization if it is trivial + return remake(SDEProblem{iip}(f, u0, tspan, p; noise, noise_rate_prototype, kwargs...)) +end + +function check_compatible_system(T::Union{Type{SDEFunction}, Type{SDEProblem}}, sys::System) + check_time_dependent(sys, T) + check_not_dde(sys) + check_no_cost(sys, T) + check_no_constraints(sys, T) + check_no_jumps(sys, T) + check_has_noise(sys, T) +end + +function calculate_noise_and_rate_prototype(sys::System, u0; sparsenoise = false) + noiseeqs = get_noise_eqs(sys) + if noiseeqs isa AbstractVector + # diagonal noise + noise_rate_prototype = nothing + noise = nothing + elseif size(noiseeqs, 2) == 1 + # scalar noise + noise_rate_prototype = nothing + noise = WienerProcess(0.0, 0.0, 0.0) + elseif sparsenoise + I, J, V = findnz(SparseArrays.sparse(noiseeqs)) + noise_rate_prototype = SparseArrays.sparse(I, J, zero(eltype(u0))) + noise = nothing + else + noise_rate_prototype = zeros(eltype(u0), size(noiseeqs)) + noise = nothing + end + return noise, noise_rate_prototype +end diff --git a/src/systems/codegen.jl b/src/systems/codegen.jl index a1bf1e822b..a4063c8ec0 100644 --- a/src/systems/codegen.jl +++ b/src/systems/codegen.jl @@ -82,6 +82,19 @@ function generate_rhs(sys::System, dvs = unknowns(sys), f_oop, f_iip) end +function generate_diffusion_function(sys::System, dvs = unknowns(sys), + ps = parameters(sys; initial_parameters = true); expression = Val{true}, eval_expression = false, + eval_module = @__MODULE__, kwargs...) + eqs = get_noise_eqs(sys) + p = reorder_parameters(sys, ps) + res = build_function_wrapper(sys, eqs, dvs, p..., get_iv(sys); kwargs...) + if expression == Val{true} + return res + end + f_oop, f_iip = eval_or_rgf.(res; eval_expression, eval_module) + return GeneratedFunctionWrapper{(2, 3, is_split(sys))}(f_oop, f_iip) +end + function calculate_tgrad(sys::System; simplify = false) # We need to remove explicit time dependence on the unknown because when we # have `u(t) * t` we want to have the tgrad to be `u(t)` instead of `u'(t) * From 2186981850316a66b41f1f6fd5f51f49a415b2fe Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Wed, 16 Apr 2025 17:38:55 +0530 Subject: [PATCH 028/185] feat: implement `SDDEProblem`, `SDDEFunction` for `System` --- src/problems/sddeproblem.jl | 77 +++++++++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 src/problems/sddeproblem.jl diff --git a/src/problems/sddeproblem.jl b/src/problems/sddeproblem.jl new file mode 100644 index 0000000000..8bf1cc05d3 --- /dev/null +++ b/src/problems/sddeproblem.jl @@ -0,0 +1,77 @@ +@fallback_iip_specialize function SciMLBase.SDDEFunction{iip, spec}( + sys::System, _d = nothing, u0 = nothing, p = nothing; + eval_expression = false, eval_module = @__MODULE__, checkbounds = false, + initialization_data = nothing, cse = true, check_compatibility = true, + sparse = false, simplify = false, analytic = nothing, kwargs...) where { + iip, spec} + check_complete(sys, SDDEFunction) + check_compatibility && check_compatible_system(SDDEFunction, sys) + + dvs = unknowns(sys) + ps = parameters(sys) + + f = generate_rhs(sys, dvs, ps; expression = Val{false}, + eval_expression, eval_module, checkbounds = checkbounds, cse, + kwargs...) + g = generate_diffusion_function(sys, dvs, ps; expression = Val{false}, + eval_expression, eval_module, checkbounds, cse, kwargs...) + + if spec === SciMLBase.FunctionWrapperSpecialize && iip + if u0 === nothing || p === nothing || t === nothing + error("u0, p, and t must be specified for FunctionWrapperSpecialize on SDDEFunction.") + end + f = SciMLBase.wrapfun_iip(f, (u0, u0, p, t)) + end + + M = calculate_massmatrix(sys) + _M = concrete_massmatrix(M; sparse, u0) + + observedfun = ObservedFunctionCache( + sys; eval_expression, eval_module, checkbounds, cse) + + SDDEFunction{iip, spec}(f, g; + sys = sys, + mass_matrix = _M, + observed = observedfun, + analytic = analytic, + initialization_data) +end + +@fallback_iip_specialize function SciMLBase.SDDEProblem{iip, spec}( + sys::System, u0map, tspan, parammap = SciMLBase.NullParameters(); + callback = nothing, check_length = true, cse = true, checkbounds = false, + eval_expression = false, eval_module = @__MODULE__, check_compatibility = true, + u0_constructor = identity, sparse = false, sparsenoise = sparse, + kwargs...) where {iip, spec} + check_complete(sys, SDDEProblem) + check_compatibility && check_compatible_system(SDDEProblem, sys) + + f, u0, p = process_SciMLProblem(SDDEFunction{iip, spec}, sys, u0map, parammap; + t = tspan !== nothing ? tspan[1] : tspan, check_length, cse, checkbounds, + eval_expression, eval_module, check_compatibility, sparse, symbolic_u0 = true, kwargs...) + + h = generate_history( + sys, u0; expression = Val{false}, cse, eval_expression, eval_module, + checkbounds) + u0 = float.(h(p, tspan[1])) + if u0 !== nothing + u0 = u0_constructor(u0) + end + + noise, noise_rate_prototype = calculate_noise_and_rate_prototype(sys, u0; sparsenoise) + kwargs = process_kwargs(sys; callback, eval_expression, eval_module, kwargs...) + + # Call `remake` so it runs initialization if it is trivial + return remake(SDDEProblem{iip}( + f, f.g, u0, h, tspan, p; noise, noise_rate_prototype, kwargs...)) +end + +function check_compatible_system( + T::Union{Type{SDDEFunction}, Type{SDDEProblem}}, sys::System) + check_time_dependent(sys, T) + check_is_dde(sys) + check_no_cost(sys, T) + check_no_constraints(sys, T) + check_no_jumps(sys, T) + check_has_noise(sys, T) +end From e7d368afc8d326eb63e70bbbdf41972f7dd77235 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Wed, 16 Apr 2025 18:45:35 +0530 Subject: [PATCH 029/185] feat: implement `NonlinearProblem` and `NonlinearFunction` for `System` --- src/problems/compatibility.jl | 8 ++++ src/problems/nonlinearproblem.jl | 78 ++++++++++++++++++++++++++++++++ src/systems/codegen.jl | 4 +- 3 files changed, 89 insertions(+), 1 deletion(-) create mode 100644 src/problems/nonlinearproblem.jl diff --git a/src/problems/compatibility.jl b/src/problems/compatibility.jl index e53e84de8a..897c4e1991 100644 --- a/src/problems/compatibility.jl +++ b/src/problems/compatibility.jl @@ -23,6 +23,14 @@ function check_time_dependent(sys::System, T) end end +function check_time_independent(sys::System, T) + if is_time_dependent(sys) + throw(SystemCompatibilityError(""" + `$T` requires a time-independent system. + """)) + end +end + function check_is_dde(sys::System) altT = get_noise_eqs(sys) === nothing ? ODEProblem : SDEProblem if !is_dde(sys) diff --git a/src/problems/nonlinearproblem.jl b/src/problems/nonlinearproblem.jl new file mode 100644 index 0000000000..6c29d5e404 --- /dev/null +++ b/src/problems/nonlinearproblem.jl @@ -0,0 +1,78 @@ +@fallback_iip_specialize function SciMLBase.NonlinearFunction{iip, spec}( + sys::System, _d = nothing, u0 = nothing, p = nothing; jac = false, + eval_expression = false, eval_module = @__MODULE__, sparse = false, + checkbounds = false, sparsity = false, analytic = nothing, + simplify = false, cse = true, initialization_data = nothing, + check_compatibility = true, kwargs...) where {iip, spec} + check_complete(sys, NonlinearFunction) + check_compatibility && check_compatible_system(NonlinearFunction, sys) + + dvs = unknowns(sys) + ps = parameters(sys) + f = generate_rhs(sys, dvs, ps; expression = Val{false}, + eval_expression, eval_module, checkbounds = checkbounds, cse, + kwargs...) + + if spec === SciMLBase.FunctionWrapperSpecialize && iip + if u0 === nothing || p === nothing + error("u0, and p must be specified for FunctionWrapperSpecialize on NonlinearFunction.") + end + f = SciMLBase.wrapfun_iip(f, (u0, u0, p)) + end + + if jac + _jac = generate_jacobian(sys, dvs, ps; expression = Val{false}, + simplify, sparse, cse, eval_expression, eval_module, checkbounds, kwargs...) + else + _jac = nothing + end + + observedfun = ObservedFunctionCache( + sys; steady_state = false, eval_expression, eval_module, checkbounds, cse) + + if length(dvs) == length(equations(sys)) + resid_prototype = nothing + else + resid_prototype = calculate_resid_prototype(length(equations(sys)), u0, p) + end + + if sparse + jac_prototype = similar(calculate_jacobian(sys; sparse), eltype(u0)) + else + jac_prototype = nothing + end + + NonlinearFunction{iip, spec}(f; + sys = sys, + jac = _jac, + observed = observedfun, + analytic = analytic, + jac_prototype, + resid_prototype, + initialization_data) +end + +@fallback_iip_specialize function SciMLBase.NonlinearProblem{iip, spec}( + sys::System, u0map, parammap = SciMLBase.NullParameters(); + check_length = true, check_compatibility = true, kwargs...) where {iip, spec} + check_complete(sys, NonlinearProblem) + check_compatibility && check_compatible_system(NonlinearProblem, sys) + + f, u0, p = process_SciMLProblem(NonlinearFunction{iip, spec}, sys, u0map, parammap; + check_length, check_compatibility, kwargs...) + + kwargs = process_kwargs(sys; kwargs...) + # Call `remake` so it runs initialization if it is trivial + return remake(NonlinearProblem{iip}( + f, u0, p, StandardNonlinearProblem(); kwargs...)) +end + +function check_compatible_system( + T::Union{Type{NonlinearFunction}, Type{NonlinearProblem}}, sys::System) + check_time_independent(sys, T) + check_not_dde(sys) + check_no_cost(sys, T) + check_no_constraints(sys, T) + check_no_jumps(sys, T) + check_no_noise(sys, T) +end diff --git a/src/systems/codegen.jl b/src/systems/codegen.jl index a4063c8ec0..7bfee0cead 100644 --- a/src/systems/codegen.jl +++ b/src/systems/codegen.jl @@ -143,7 +143,9 @@ function generate_jacobian(sys::System, dvs = unknowns(sys), jac = calculate_jacobian(sys; simplify, sparse, dvs) p = reorder_parameters(sys, ps) t = get_iv(sys) - if t !== nothing + if t === nothing + wrap_code = (identity, identity) + else wrap_code = sparse ? assert_jac_length_header(sys) : (identity, identity) end res = build_function_wrapper(sys, jac, dvs, p..., t; wrap_code, expression = Val{true}, From 19fbb43958501ad1129deccd9b2272366933fc04 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Wed, 16 Apr 2025 19:58:36 +0530 Subject: [PATCH 030/185] fix: fix `remake` for `IntervalNonlinearProblem` --- src/systems/nonlinear/initializesystem.jl | 1 + 1 file changed, 1 insertion(+) diff --git a/src/systems/nonlinear/initializesystem.jl b/src/systems/nonlinear/initializesystem.jl index eb4bfeff03..daa1e616de 100644 --- a/src/systems/nonlinear/initializesystem.jl +++ b/src/systems/nonlinear/initializesystem.jl @@ -616,6 +616,7 @@ end function SciMLBase.late_binding_update_u0_p( prob, sys::AbstractSystem, u0, p, t0, newu0, newp) supports_initialization(sys) || return newu0, newp + prob isa IntervalNonlinearProblem && return newu0, newp initdata = prob.f.initialization_data meta = initdata === nothing ? nothing : initdata.metadata From ccbd6a049bb18dd95135c0f4a69ee734a43baf11 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Wed, 16 Apr 2025 19:58:55 +0530 Subject: [PATCH 031/185] feat: implement `IntervalNonlinearProblem`, `IntervalNonlinearFunction` for `System` --- src/problems/intervalnonlinearproblem.jl | 59 ++++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 src/problems/intervalnonlinearproblem.jl diff --git a/src/problems/intervalnonlinearproblem.jl b/src/problems/intervalnonlinearproblem.jl new file mode 100644 index 0000000000..18bcf80697 --- /dev/null +++ b/src/problems/intervalnonlinearproblem.jl @@ -0,0 +1,59 @@ +function SciMLBase.IntervalNonlinearFunction( + sys::System, _d = nothing, u0 = nothing, p = nothing; + eval_expression = false, eval_module = @__MODULE__, + checkbounds = false, analytic = nothing, + cse = true, initialization_data = nothing, + check_compatibility = true, kwargs...) + check_complete(sys, IntervalNonlinearFunction) + check_compatibility && check_compatible_system(IntervalNonlinearFunction, sys) + + dvs = unknowns(sys) + ps = parameters(sys) + f = generate_rhs(sys, dvs, ps; expression = Val{false}, scalar = true, + eval_expression, eval_module, checkbounds, cse, + kwargs...) + + observedfun = ObservedFunctionCache( + sys; steady_state = false, eval_expression, eval_module, checkbounds, cse) + + IntervalNonlinearFunction{false}(f; + sys = sys, + observed = observedfun, + analytic = analytic, + initialization_data) +end + +function SciMLBase.IntervalNonlinearProblem( + sys::System, uspan::NTuple{2}, parammap = SciMLBase.NullParameters(); + check_compatibility = true, kwargs...) + check_complete(sys, IntervalNonlinearProblem) + check_compatibility && check_compatible_system(IntervalNonlinearProblem, sys) + + u0map = unknowns(sys) .=> uspan[1] + f, u0, p = process_SciMLProblem(IntervalNonlinearFunction, sys, u0map, parammap; + check_compatibility, kwargs...) + + kwargs = process_kwargs(sys; kwargs...) + # Call `remake` so it runs initialization if it is trivial + return remake(IntervalNonlinearProblem(f, uspan, p; kwargs...)) +end + +function check_compatible_system( + T::Union{Type{IntervalNonlinearFunction}, Type{IntervalNonlinearProblem}}, sys::System) + check_time_independent(sys, T) + if !isone(length(unknowns(sys))) + throw(SystemCompatibilityError(""" + `$T` requires a system with a single unknown. Found `$(unknowns(sys))`. + """)) + end + if !isone(length(equations(sys))) + throw(SystemCompatibilityError(""" + `$T` requires a system with a single equation. Found `$(equations(sys))`. + """)) + end + check_not_dde(sys) + check_no_cost(sys, T) + check_no_constraints(sys, T) + check_no_jumps(sys, T) + check_no_noise(sys, T) +end From 9da8b3694870d30612790b5a0e0eb6302e5193dd Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Thu, 17 Apr 2025 11:58:02 +0530 Subject: [PATCH 032/185] feat: implement `DiscreteProblem` and `DiscreteFunction` for `System` --- src/problems/compatibility.jl | 17 +++++++++ src/problems/discreteproblem.jl | 61 +++++++++++++++++++++++++++++++++ src/systems/codegen.jl | 6 ++-- 3 files changed, 82 insertions(+), 2 deletions(-) create mode 100644 src/problems/discreteproblem.jl diff --git a/src/problems/compatibility.jl b/src/problems/compatibility.jl index 897c4e1991..5e375cdaa6 100644 --- a/src/problems/compatibility.jl +++ b/src/problems/compatibility.jl @@ -97,3 +97,20 @@ function check_has_noise(sys::System, T) """)) end end + +function check_is_discrete(sys::System, T) + if !is_discrete_system(sys) + throw(SystemCompatibilityError(""" + `$T` expects a discrete system. Consider an `ODEProblem` instead. If your system \ + is discrete, ensure `structural_simplify` has been run on it. + """)) + end +end + +function check_is_explicit(sys::System, T, altT) + if has_alg_equations(sys) + throw(SystemCompatibilityError(""" + `$T` expects an explicit system. Consider a `$altT` instead. + """)) + end +end diff --git a/src/problems/discreteproblem.jl b/src/problems/discreteproblem.jl new file mode 100644 index 0000000000..69b4f601fe --- /dev/null +++ b/src/problems/discreteproblem.jl @@ -0,0 +1,61 @@ +@fallback_iip_specialize function SciMLBase.DiscreteFunction{iip, spec}( + sys::System, _d = nothing, u0 = nothing, p = nothing; + t = nothing, eval_expression = false, eval_module = @__MODULE__, + checkbounds = false, analytic = nothing, simplify = false, cse = true, + initialization_data = nothing, check_compatibility = true, kwargs...) where { + iip, spec} + check_complete(sys, DiscreteFunction) + check_compatibility && check_compatible_system(DiscreteFunction, sys) + + dvs = unknowns(sys) + ps = parameters(sys) + f = generate_rhs(sys, dvs, ps; expression = Val{false}, + eval_expression, eval_module, checkbounds = checkbounds, cse, + kwargs...) + + if spec === SciMLBase.FunctionWrapperSpecialize && iip + if u0 === nothing || p === nothing || t === nothing + error("u0, p, and t must be specified for FunctionWrapperSpecialize on DiscreteFunction.") + end + f = SciMLBase.wrapfun_iip(f, (u0, u0, p, t)) + end + + observedfun = ObservedFunctionCache( + sys; steady_state = false, eval_expression, eval_module, checkbounds, cse) + + DiscreteFunction{iip, spec}(f; + sys = sys, + observed = observedfun, + analytic = analytic, + initialization_data) +end + +@fallback_iip_specialize function SciMLBase.DiscreteProblem{iip, spec}( + sys::System, u0map, tspan, parammap = SciMLBase.NullParameters(); + check_compatibility = true, kwargs...) where {iip, spec} + check_complete(sys, DiscreteProblem) + check_compatibility && check_compatible_system(DiscreteProblem, sys) + + dvs = unknowns(sys) + u0map = to_varmap(u0map, dvs) + u0map = shift_u0map_forward(sys, u0map, defaults(sys)) + f, u0, p = process_SciMLProblem(DiscreteFunction{iip, spec}, sys, u0map, parammap; + t = tspan !== nothing ? tspan[1] : tspan, check_compatibility, kwargs...) + u0 = f(u0, p, tspan[1]) + + kwargs = process_kwargs(sys; kwargs...) + # Call `remake` so it runs initialization if it is trivial + return remake(DiscreteProblem{iip}(f, u0, tspan, p; kwargs...)) +end + +function check_compatible_system( + T::Union{Type{DiscreteFunction}, Type{DiscreteProblem}}, sys::System) + check_time_dependent(sys, T) + check_not_dde(sys) + check_no_cost(sys, T) + check_no_constraints(sys, T) + check_no_jumps(sys, T) + check_no_noise(sys, T) + check_is_discrete(sys, T) + check_is_explicit(sys, T, ImplicitDiscreteProblem) +end diff --git a/src/systems/codegen.jl b/src/systems/codegen.jl index 7bfee0cead..edd804c6a2 100644 --- a/src/systems/codegen.jl +++ b/src/systems/codegen.jl @@ -41,8 +41,10 @@ function generate_rhs(sys::System, dvs = unknowns(sys), end ddvs = map(D, dvs) else - check_operator_variables(eqs, Differential) - check_lhs(eqs, Differential, Set(dvs)) + if !is_discrete_system(sys) + check_operator_variables(eqs, Differential) + check_lhs(eqs, Differential, Set(dvs)) + end rhss = [eq.rhs for eq in eqs] end From 1e004cb2790bac9badc7307bcf241213b9389821 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Thu, 17 Apr 2025 11:59:45 +0530 Subject: [PATCH 033/185] feat: add `check_is_continuous` to relevant compatibility checks --- src/problems/compatibility.jl | 9 +++++++++ src/problems/ddeproblem.jl | 1 + src/problems/odeproblem.jl | 1 + src/problems/sddeproblem.jl | 1 + src/problems/sdeproblem.jl | 1 + 5 files changed, 13 insertions(+) diff --git a/src/problems/compatibility.jl b/src/problems/compatibility.jl index 5e375cdaa6..cf354ceb07 100644 --- a/src/problems/compatibility.jl +++ b/src/problems/compatibility.jl @@ -107,6 +107,15 @@ function check_is_discrete(sys::System, T) end end +function check_is_continuous(sys::System, T) + altT = has_alg_equations(sys) ? ImplicitDiscreteProblem : DiscreteProblem + if is_discrete_system(sys) + throw(SystemCompatibilityError(""" + A discrete system cannot be used to construct a `$T`. Consider a `$altT` instead. + """)) + end +end + function check_is_explicit(sys::System, T, altT) if has_alg_equations(sys) throw(SystemCompatibilityError(""" diff --git a/src/problems/ddeproblem.jl b/src/problems/ddeproblem.jl index 186e531241..5aea37e4d5 100644 --- a/src/problems/ddeproblem.jl +++ b/src/problems/ddeproblem.jl @@ -69,4 +69,5 @@ function check_compatible_system(T::Union{Type{DDEFunction}, Type{DDEProblem}}, check_no_constraints(sys, T) check_no_jumps(sys, T) check_no_noise(sys, T) + check_is_continuous(sys, T) end diff --git a/src/problems/odeproblem.jl b/src/problems/odeproblem.jl index 2da4675002..fe5e9ac4ac 100644 --- a/src/problems/odeproblem.jl +++ b/src/problems/odeproblem.jl @@ -81,4 +81,5 @@ function check_compatible_system( check_no_constraints(sys, T) check_no_jumps(sys, T) check_no_noise(sys, T) + check_is_continuous(sys, T) end diff --git a/src/problems/sddeproblem.jl b/src/problems/sddeproblem.jl index 8bf1cc05d3..8c001294dd 100644 --- a/src/problems/sddeproblem.jl +++ b/src/problems/sddeproblem.jl @@ -74,4 +74,5 @@ function check_compatible_system( check_no_constraints(sys, T) check_no_jumps(sys, T) check_has_noise(sys, T) + check_is_continuous(sys, T) end diff --git a/src/problems/sdeproblem.jl b/src/problems/sdeproblem.jl index 2989bc485c..45ed21c9da 100644 --- a/src/problems/sdeproblem.jl +++ b/src/problems/sdeproblem.jl @@ -82,6 +82,7 @@ function check_compatible_system(T::Union{Type{SDEFunction}, Type{SDEProblem}}, check_no_constraints(sys, T) check_no_jumps(sys, T) check_has_noise(sys, T) + check_is_continuous(sys, T) end function calculate_noise_and_rate_prototype(sys::System, u0; sparsenoise = false) From 69073d099d9697e944f6e72c8edc41726ad65769 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Thu, 17 Apr 2025 12:06:14 +0530 Subject: [PATCH 034/185] feat: implement `ImplicitDiscreteProblem`, `ImplicitDiscreteFunction` for `System` --- src/problems/compatibility.jl | 8 ++++ src/problems/implicitdiscreteproblem.jl | 60 +++++++++++++++++++++++++ 2 files changed, 68 insertions(+) create mode 100644 src/problems/implicitdiscreteproblem.jl diff --git a/src/problems/compatibility.jl b/src/problems/compatibility.jl index cf354ceb07..c20b729f6b 100644 --- a/src/problems/compatibility.jl +++ b/src/problems/compatibility.jl @@ -123,3 +123,11 @@ function check_is_explicit(sys::System, T, altT) """)) end end + +function check_is_implicit(sys::System, T, altT) + if !has_alg_equations(sys) + throw(SystemCompatibilityError(""" + `$T` expects an implicit system. Consider a `$altT` instead. + """)) + end +end diff --git a/src/problems/implicitdiscreteproblem.jl b/src/problems/implicitdiscreteproblem.jl new file mode 100644 index 0000000000..06316a28cc --- /dev/null +++ b/src/problems/implicitdiscreteproblem.jl @@ -0,0 +1,60 @@ +@fallback_iip_specialize function SciMLBase.ImplicitDiscreteFunction{iip, spec}( + sys::System, _d = nothing, u0 = nothing, p = nothing; + t = nothing, eval_expression = false, eval_module = @__MODULE__, + checkbounds = false, analytic = nothing, simplify = false, cse = true, + initialization_data = nothing, check_compatibility = true, kwargs...) where { + iip, spec} + check_complete(sys, ImplicitDiscreteFunction) + check_compatibility && check_compatible_system(ImplicitDiscreteFunction, sys) + + iv = get_iv(sys) + dvs = unknowns(sys) + ps = parameters(sys) + f = generate_rhs(sys, dvs, ps; expression = Val{false}, implicit_dae = true, + eval_expression, eval_module, checkbounds = checkbounds, cse, + kwargs...) + + if spec === SciMLBase.FunctionWrapperSpecialize && iip + if u0 === nothing || p === nothing || t === nothing + error("u0, p, and t must be specified for FunctionWrapperSpecialize on ImplicitDiscreteFunction.") + end + f = SciMLBase.wrapfun_iip(f, (u0, u0, u0, p, t)) + end + + observedfun = ObservedFunctionCache( + sys; steady_state = false, eval_expression, eval_module, checkbounds, cse) + + ImplicitDiscreteFunction{iip, spec}(f; + sys = sys, + observed = observedfun, + analytic = analytic, + initialization_data) +end + +@fallback_iip_specialize function SciMLBase.ImplicitDiscreteProblem{iip, spec}( + sys::System, u0map, tspan, parammap = SciMLBase.NullParameters(); + check_compatibility = true, kwargs...) where {iip, spec} + check_complete(sys, ImplicitDiscreteProblem) + check_compatibility && check_compatible_system(ImplicitDiscreteProblem, sys) + + dvs = unknowns(sys) + f, u0, p = process_SciMLProblem( + ImplicitDiscreteFunction{iip, spec}, sys, u0map, parammap; + t = tspan !== nothing ? tspan[1] : tspan, check_compatibility, kwargs...) + + kwargs = process_kwargs(sys; kwargs...) + # Call `remake` so it runs initialization if it is trivial + return remake(ImplicitDiscreteProblem{iip}(f, u0, tspan, p; kwargs...)) +end + +function check_compatible_system( + T::Union{Type{ImplicitDiscreteFunction}, Type{ImplicitDiscreteProblem}}, sys::System) + check_time_dependent(sys, T) + check_not_dde(sys) + check_no_cost(sys, T) + check_no_constraints(sys, T) + check_no_jumps(sys, T) + check_no_noise(sys, T) + check_is_discrete(sys, T) + check_is_implicit(sys, T, DiscreteProblem) +end From ec2ed57b7086be97221f56b6e7c3846a85b17d47 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Thu, 17 Apr 2025 19:45:39 +0530 Subject: [PATCH 035/185] feat: allow manually choosing time-independent initialization --- src/systems/diffeqs/abstractodesystem.jl | 5 +++-- src/systems/nonlinear/initializesystem.jl | 8 +++++--- src/systems/problem_utils.jl | 23 ++++++++++++++++------- 3 files changed, 24 insertions(+), 12 deletions(-) diff --git a/src/systems/diffeqs/abstractodesystem.jl b/src/systems/diffeqs/abstractodesystem.jl index e293fa400a..7776b954b0 100644 --- a/src/systems/diffeqs/abstractodesystem.jl +++ b/src/systems/diffeqs/abstractodesystem.jl @@ -1378,6 +1378,7 @@ function InitializationProblem{iip, specialize}(sys::AbstractSystem, allow_incomplete = false, force_time_independent = false, algebraic_only = false, + time_dependent_init = is_time_dependent(sys), kwargs...) where {iip, specialize} if !iscomplete(sys) error("A completed system is required. Call `complete` or `structural_simplify` on the system before creating an `ODEProblem`") @@ -1392,7 +1393,7 @@ function InitializationProblem{iip, specialize}(sys::AbstractSystem, simplify_system = true else isys = generate_initializesystem( - sys; u0map, initialization_eqs, check_units, + sys; u0map, initialization_eqs, check_units, time_dependent_init, pmap = parammap, guesses, algebraic_only) simplify_system = true end @@ -1425,7 +1426,7 @@ function InitializationProblem{iip, specialize}(sys::AbstractSystem, # TODO: throw on uninitialized arrays filter!(x -> !(x isa Symbolics.Arr), uninit) - if is_time_dependent(sys) && !isempty(uninit) + if time_dependent_init && !isempty(uninit) allow_incomplete || throw(IncompleteInitializationError(uninit)) # for incomplete initialization, we will add the missing variables as parameters. # they will be updated by `update_initializeprob!` and `initializeprobmap` will diff --git a/src/systems/nonlinear/initializesystem.jl b/src/systems/nonlinear/initializesystem.jl index daa1e616de..b5b93c8158 100644 --- a/src/systems/nonlinear/initializesystem.jl +++ b/src/systems/nonlinear/initializesystem.jl @@ -1,5 +1,6 @@ -function generate_initializesystem(sys::AbstractSystem; kwargs...) - if is_time_dependent(sys) +function generate_initializesystem( + sys::AbstractSystem; time_dependent_init = is_time_dependent(sys), kwargs...) + if time_dependent_init generate_initializesystem_timevarying(sys; kwargs...) else generate_initializesystem_timeindependent(sys; kwargs...) @@ -545,6 +546,7 @@ function SciMLBase.remake_initialization_data( merge!(guesses, meta.guesses) use_scc = meta.use_scc initialization_eqs = meta.additional_initialization_eqs + time_dependent_init = meta.time_dependent_init else # there is no initializeprob, so the original problem construction # had no solvable parameters and had the differential variables @@ -591,7 +593,7 @@ function SciMLBase.remake_initialization_data( u0map, pmap, defs, cmap, dvs, ps) floatT = float_type_from_varmap(op) kws = maybe_build_initialization_problem( - sys, op, u0map, pmap, t0, defs, guesses, missing_unknowns; + sys, op, u0map, pmap, t0, defs, guesses, missing_unknowns; time_dependent_init, use_scc, initialization_eqs, floatT, allow_incomplete = true) return SciMLBase.remake_initialization_data(sys, kws, newu0, t0, newp, newu0, newp) diff --git a/src/systems/problem_utils.jl b/src/systems/problem_utils.jl index cf57aee102..95add1d990 100644 --- a/src/systems/problem_utils.jl +++ b/src/systems/problem_utils.jl @@ -792,6 +792,10 @@ struct InitializationMetadata{R <: ReconstructInitializeprob, GUU, SIU} """ use_scc::Bool """ + Whether the initialization uses the independent variable. + """ + time_dependent_init::Bool + """ `ReconstructInitializeprob` for this initialization problem. """ oop_reconstruct_u0_p::R @@ -864,7 +868,8 @@ All other keyword arguments are forwarded to `InitializationProblem`. """ function maybe_build_initialization_problem( sys::AbstractSystem, op::AbstractDict, u0map, pmap, t, defs, - guesses, missing_unknowns; implicit_dae = false, u0_constructor = identity, + guesses, missing_unknowns; implicit_dae = false, + time_dependent_init = is_time_dependent(sys), u0_constructor = identity, floatT = Float64, initialization_eqs = [], use_scc = true, kwargs...) guesses = merge(ModelingToolkit.guesses(sys), todict(guesses)) @@ -873,7 +878,8 @@ function maybe_build_initialization_problem( end initializeprob = ModelingToolkit.InitializationProblem{true, SciMLBase.FullSpecialize}( - sys, t, u0map, pmap; guesses, initialization_eqs, use_scc, kwargs...) + sys, t, u0map, pmap; guesses, time_dependent_init, initialization_eqs, use_scc, + kwargs...) if state_values(initializeprob) !== nothing initializeprob = remake(initializeprob; u0 = floatT.(state_values(initializeprob))) end @@ -897,10 +903,13 @@ function maybe_build_initialization_problem( end meta = InitializationMetadata( u0map, pmap, guesses, Vector{Equation}(initialization_eqs), - use_scc, ReconstructInitializeprob(sys, initializeprob.f.sys), + use_scc, time_dependent_init, ReconstructInitializeprob(sys, initializeprob.f.sys), get_initial_unknowns, setp(sys, Initial.(unknowns(sys)))) - if is_time_dependent(sys) + if time_dependent_init === nothing + time_dependent_init = is_time_dependent(sys) + end + if time_dependent_init all_init_syms = Set(all_symbols(initializeprob)) solved_unknowns = filter(var -> var in all_init_syms, unknowns(sys)) initializeprobmap = u0_constructor ∘ getu(initializeprob, solved_unknowns) @@ -940,7 +949,7 @@ function maybe_build_initialization_problem( end end - if is_time_dependent(sys) + if time_dependent_init for v in missing_unknowns op[v] = get_temporary_value(v, floatT) end @@ -1043,7 +1052,7 @@ function process_SciMLProblem( symbolic_u0 = false, warn_cyclic_dependency = false, circular_dependency_max_cycle_length = length(all_symbols(sys)), circular_dependency_max_cycles = 10, - substitution_limit = 100, use_scc = true, + substitution_limit = 100, use_scc = true, time_dependent_init = is_time_dependent(sys), force_initialization_time_independent = false, algebraic_only = false, allow_incomplete = false, is_initializeprob = false, kwargs...) dvs = unknowns(sys) @@ -1098,7 +1107,7 @@ function process_SciMLProblem( warn_cyclic_dependency, check_units = check_initialization_units, circular_dependency_max_cycle_length, circular_dependency_max_cycles, use_scc, force_time_independent = force_initialization_time_independent, algebraic_only, allow_incomplete, - u0_constructor, floatT) + u0_constructor, floatT, time_dependent_init) kwargs = merge(kwargs, kws) end From 282545c7f89ad329f8c54db10e99fdd4d03bad0c Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Thu, 17 Apr 2025 19:46:34 +0530 Subject: [PATCH 036/185] feat: implement `BVProblem` for `System` --- src/problems/bvproblem.jl | 34 +++++++++++++++++++++++ src/problems/compatibility.jl | 9 ++++++ src/systems/codegen.jl | 52 +++++++++++++++++++++++++++++++++++ 3 files changed, 95 insertions(+) create mode 100644 src/problems/bvproblem.jl diff --git a/src/problems/bvproblem.jl b/src/problems/bvproblem.jl new file mode 100644 index 0000000000..919496e108 --- /dev/null +++ b/src/problems/bvproblem.jl @@ -0,0 +1,34 @@ +@fallback_iip_specialize function SciMLBase.BVProblem{iip, spec}( + sys::System, u0map, tspan, parammap = SciMLBase.NullParameters(); + check_compatibility = true, cse = true, checkbounds = false, eval_expression = false, + eval_module = @__MODULE__, guesses = Dict(), kwargs...) where {iip, spec} + check_complete(sys, BVProblem) + check_compatibility && check_compatible_system(BVProblem, sys) + + # ODESystems without algebraic equations should use both fixed values + guesses + # for initialization. + _u0map = has_alg_eqs(sys) ? u0map : merge(Dict(u0map), Dict(guesses)) + fode, u0, p = process_SciMLProblem( + ODEFunction{iip, spec}, sys, _u0map, parammap; guesses, + t = tspan !== nothing ? tspan[1] : tspan, check_compatibility = false, cse, checkbounds, + time_dependent_init = false, kwargs...) + + dvs = unknowns(sys) + stidxmap = Dict([v => i for (i, v) in enumerate(dvs)]) + u0_idxs = has_alg_eqs(sys) ? collect(1:length(dvs)) : [stidxmap[k] for (k, v) in u0map] + fbc = generate_boundary_conditions( + sys, u0, u0_idxs, tspan; expression = Val{false}, cse, checkbounds) + kwargs = process_kwargs(sys; kwargs...) + # Call `remake` so it runs initialization if it is trivial + return remake(BVProblem{iip}(fode, fbc, u0, tspan[1], p; kwargs...)) +end + +function check_compatible_system(T::Union{Type{BVPFunction}, Type{BVProblem}}, sys::System) + check_time_dependent(sys, T) + check_not_dde(sys) + check_no_cost(sys, T) + check_has_constraints(sys, T) + check_no_jumps(sys, T) + check_no_noise(sys, T) + check_is_continuous(sys, T) +end diff --git a/src/problems/compatibility.jl b/src/problems/compatibility.jl index c20b729f6b..e1387bdd73 100644 --- a/src/problems/compatibility.jl +++ b/src/problems/compatibility.jl @@ -69,6 +69,15 @@ function check_no_constraints(sys::System, T) end end +function check_has_constraints(sys::System, T) + if isempty(constraints(sys)) + throw(SystemCompatibilityError(""" + A system without constraints cannot be used to construct a `$T`. Consider an \ + `ODEProblem` instead. + """)) + end +end + function check_no_jumps(sys::System, T) if !isempty(jumps(sys)) throw(SystemCompatibilityError(""" diff --git a/src/systems/codegen.jl b/src/systems/codegen.jl index edd804c6a2..16bfa108f4 100644 --- a/src/systems/codegen.jl +++ b/src/systems/codegen.jl @@ -317,3 +317,55 @@ function isautonomous(sys::System) tgrad = calculate_tgrad(sys; simplify = true) all(iszero, tgrad) end + +function get_bv_solution_symbol(ns) + only(@variables BV_SOLUTION(..)[1:ns]) +end + +function get_constraint_unknown_subs!(subs::Dict, cons::Vector, stidxmap::Dict, iv, sol) + vs = vars(cons) + for v in vs + iscall(v) || continue + op = operation(v) + args = arguments(v) + issym(op) && length(args) == 1 || continue + newv = op(iv) + haskey(stidxmap, newv) || continue + subs[v] = sol(args[1])[stidxmap[newv]] + end +end + +function generate_boundary_conditions(sys::System, u0, u0_idxs, t0; expression = Val{true}, + eval_expression = false, eval_module = @__MODULE__, kwargs...) + iv = get_iv(sys) + sts = unknowns(sys) + ps = parameters(sys) + np = length(ps) + ns = length(sts) + stidxmap = Dict([v => i for (i, v) in enumerate(sts)]) + pidxmap = Dict([v => i for (i, v) in enumerate(ps)]) + + sol = get_bv_solution_symbol(ns) + + cons = [con.lhs - con.rhs for con in constraints(sys)] + conssubs = Dict() + get_constraint_unknown_subs!(conssubs, cons, stidxmap, iv, sol) + cons = map(x -> fast_substitute(x, conssubs), cons) + + init_conds = Any[] + for i in u0_idxs + expr = sol(t0)[i] - u0[i] + push!(init_conds, expr) + end + + exprs = vcat(init_conds, cons) + _p = reorder_parameters(sys, ps) + + res = build_function_wrapper(sys, exprs, sol, _p..., iv; output_type = Array, kwargs...) + if expression == Val{true} + return res + end + + f_oop, f_iip = eval_or_rgf.(res; eval_expression, eval_module) + return GeneratedFunctionWrapper{(2, 3, is_split(sys))}(f_oop, f_iip) +end From 036a9fac22873c884e963815fcaa23c569727ec9 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Fri, 18 Apr 2025 12:10:14 +0530 Subject: [PATCH 037/185] feat: implement `OptimizationProblem`, `OptimizationFunction` for `System` --- src/problems/compatibility.jl | 18 ++++ src/problems/optimizationproblem.jl | 137 ++++++++++++++++++++++++++++ src/systems/codegen.jl | 119 ++++++++++++++++++++++++ 3 files changed, 274 insertions(+) create mode 100644 src/problems/optimizationproblem.jl diff --git a/src/problems/compatibility.jl b/src/problems/compatibility.jl index e1387bdd73..4b7429f023 100644 --- a/src/problems/compatibility.jl +++ b/src/problems/compatibility.jl @@ -61,6 +61,15 @@ function check_no_cost(sys::System, T) end end +function check_has_cost(sys::System, T) + cost = ModelingToolkit.cost(sys) + if _iszero(cost) + throw(SystemCompatibilityError(""" + A system without cost cannot be used to construct a `$T`. + """)) + end +end + function check_no_constraints(sys::System, T) if !isempty(constraints(sys)) throw(SystemCompatibilityError(""" @@ -140,3 +149,12 @@ function check_is_implicit(sys::System, T, altT) """)) end end + +function check_no_equations(sys::System, T) + if !isempty(equations(sys)) + throw(SystemCompatibilityError(""" + A system with equations cannot be used to construct a `$T`. Consider turning the + equations into constraints instead. + """)) + end +end diff --git a/src/problems/optimizationproblem.jl b/src/problems/optimizationproblem.jl new file mode 100644 index 0000000000..80c1dab1f1 --- /dev/null +++ b/src/problems/optimizationproblem.jl @@ -0,0 +1,137 @@ +function SciMLBase.OptimizationFunction(sys::System, args...; kwargs...) + return OptimizationFunction{true}(sys, args...; kwargs...) +end + +function SciMLBase.OptimizationFunction{iip}(sys::System, + _d = nothing, u0 = nothing, p = nothing; grad = false, hess = false, + sparse = false, cons_j = false, cons_h = false, cons_sparse = false, + linenumbers = true, eval_expression = false, eval_module = @__MODULE__, + simplify = false, check_compatibility = true, checkbounds = false, cse = true, + kwargs...) where {iip} + check_complete(sys, OptimizationFunction) + check_compatibility && check_compatible_system(OptimizationFunction, sys) + dvs = unknowns(sys) + ps = parameters(sys) + cstr = constraints(sys) + + f = generate_cost(sys; expression = Val{false}, eval_expression, + eval_module, checkbounds, cse, kwargs...) + + if grad + _grad = generate_cost_gradient(sys; expression = Val{false}, eval_expression, + eval_module, checkbounds, cse, kwargs...) + else + _grad = nothing + end + if hess + _hess, hess_prototype = generate_cost_hessian( + sys; expression = Val{false}, eval_expression, eval_module, + checkbounds, cse, sparse, simplify, return_sparsity = true, kwargs...) + else + _hess = hess_prototype = nothing + end + if isempty(cstr) + cons = lcons = ucons = _cons_j = cons_jac_prototype = _cons_h = nothing + cons_hess_prototype = cons_expr = nothing + else + cons = generate_cons(sys; expression = Val{false}, eval_expression, + eval_module, checkbounds, cse, kwargs...) + if cons_j + _cons_j, cons_jac_prototype = generate_constraint_jacobian( + sys; expression = Val{false}, eval_expression, eval_module, checkbounds, + cse, simplify, sparse = cons_sparse, return_sparsity = true, kwargs...) + else + _cons_j = cons_jac_prototype = nothing + end + if cons_h + _cons_h, cons_hess_prototype = generate_constraint_hessian( + sys; expression = Val{false}, eval_expression, eval_module, checkbounds, + cse, simplify, sparse = cons_sparse, return_sparsity = true, kwargs...) + else + _cons_h = cons_hess_prototype = nothing + end + cons_expr = toexpr.(subs_constants(cstr)) + end + + obj_expr = subs_constants(cost(sys)) + + observedfun = ObservedFunctionCache(sys; eval_expression, eval_module, checkbounds, cse) + + return OptimizationFunction{iip}(f, SciMLBase.NoAD(); + sys = sys, + grad = _grad, + hess = _hess, + hess_prototype = hess_prototype, + cons = cons, + cons_j = _cons_j, + cons_jac_prototype = cons_jac_prototype, + cons_h = _cons_h, + cons_hess_prototype = cons_hess_prototype, + cons_expr = cons_expr, + expr = obj_expr, + observed = observedfun) +end + +function SciMLBase.OptimizationProblem(sys::System, args...; kwargs...) + return OptimizationProblem{true}(sys, args...; kwargs...) +end + +function SciMLBase.OptimizationProblem{iip}( + sys::System, u0map, parammap = SciMLBase.NullParameters(); lb = nothing, ub = nothing, + check_compatibility = true, kwargs...) where {iip} + check_complete(sys, OptimizationProblem) + check_compatibility && check_compatible_system(OptimizationProblem, sys) + + f, u0, p = process_SciMLProblem(OptimizationFunction{iip}, sys, u0map, parammap; + check_compatibility, tofloat = false, check_length = false, kwargs...) + + dvs = unknowns(sys) + int = symtype.(unwrap.(dvs)) .<: Integer + if lb === nothing && ub === nothing + lb = first.(getbounds.(dvs)) + ub = last.(getbounds.(dvs)) + isboolean = symtype.(unwrap.(dvs)) .<: Bool + lb[isboolean] .= 0 + ub[isboolean] .= 1 + else + xor(isnothing(lb), isnothing(ub)) && + throw(ArgumentError("Expected both `lb` and `ub` to be supplied")) + !isnothing(lb) && length(lb) != length(dvs) && + throw(ArgumentError("Expected both `lb` to be of the same length as the vector of optimization variables")) + !isnothing(ub) && length(ub) != length(dvs) && + throw(ArgumentError("Expected both `ub` to be of the same length as the vector of optimization variables")) + end + + ps = parameters(sys) + defs = merge(defaults(sys), to_varmap(parammap, ps), to_varmap(u0map, dvs)) + lb = varmap_to_vars(dvs .=> lb, dvs; defaults = defs, tofloat = false) + ub = varmap_to_vars(dvs .=> ub, dvs; defaults = defs, tofloat = false) + + if !isnothing(lb) && all(lb .== -Inf) && !isnothing(ub) && all(ub .== Inf) + lb = nothing + ub = nothing + end + + cstr = constraints(sys) + if isempty(cstr) + lcons = ucons = nothing + else + lcons = fill(-Inf, length(cstr)) + ucons = zeros(length(cstr)) + lcons[findall(Base.Fix2(isa, Equation), cstr)] .= 0.0 + end + + kwargs = process_kwargs(sys; kwargs...) + # Call `remake` so it runs initialization if it is trivial + return remake(OptimizationProblem{iip}(f, u0, p; lb, ub, int, lcons, ucons, kwargs...)) +end + +function check_compatible_system( + T::Union{Type{OptimizationFunction}, Type{OptimizationProblem}}, sys::System) + check_time_independent(sys, T) + check_not_dde(sys) + check_has_cost(sys, T) + check_no_jumps(sys, T) + check_no_noise(sys, T) + check_no_equations(sys, T) +end diff --git a/src/systems/codegen.jl b/src/systems/codegen.jl index 16bfa108f4..be71917cbc 100644 --- a/src/systems/codegen.jl +++ b/src/systems/codegen.jl @@ -369,3 +369,122 @@ function generate_boundary_conditions(sys::System, u0, u0_idxs, t0; expression = f_oop, f_iip = eval_or_rgf.(res; eval_expression, eval_module) return GeneratedFunctionWrapper{(2, 3, is_split(sys))}(f_oop, f_iip) end + +function generate_cost(sys::System; expression = Val{true}, eval_expression = false, + eval_module = @__MODULE__, kwargs...) + obj = cost(sys) + dvs = unknowns(sys) + ps = reorder_parameters(sys) + res = build_function_wrapper(sys, obj, dvs, ps...; expression = Val{true}, kwargs...) + if expression == Val{true} + return res + end + f_oop = eval_or_rgf(res; eval_expression, eval_module) + return GeneratedFunctionWrapper{(2, 2, is_split(sys))}(f_oop, nothing) +end + +function generate_cost_gradient( + sys::System; expression = Val{true}, eval_expression = false, + eval_module = @__MODULE__, simplify = false, kwargs...) + obj = cost(sys) + dvs = unknowns(sys) + ps = reorder_parameters(sys) + exprs = Symbolics.gradient(obj, dvs; simplify) + res = build_function_wrapper(sys, exprs, dvs, ps...; expression = Val{true}, kwargs...) + if expression == Val{true} + return res + end + f_oop, f_iip = eval_or_rgf.(res; eval_expression, eval_module) + return GeneratedFunctionWrapper{(2, 2, is_split(sys))}(f_oop, f_iip) +end + +function generate_cost_hessian( + sys::System; expression = Val{true}, eval_expression = false, + eval_module = @__MODULE__, simplify = false, + sparse = false, return_sparsity = false, kwargs...) + obj = cost(sys) + dvs = unknowns(sys) + ps = reorder_parameters(sys) + sparsity = nothing + if sparse + exprs = Symbolics.sparsehessian(obj, dvs; simplify)::AbstractSparseArray + sparsity = similar(exprs, Float64) + else + exprs = Symbolics.hessian(obj, dvs; simplify) + end + res = build_function_wrapper(sys, exprs, dvs, ps...; expression = Val{true}, kwargs...) + if expression == Val{true} + return return_sparsity ? (res, sparsity) : res + end + f_oop, f_iip = eval_or_rgf.(res; eval_expression, eval_module) + fn = GeneratedFunctionWrapper{(2, 2, is_split(sys))}(f_oop, f_iip) + return return_sparsity ? (fn, sparsity) : fn +end + +function canonical_constraints(sys::System) + return map(constraints(sys)) do cstr + Symbolics.canonical_form(cstr).lhs + end +end + +function generate_cons(sys::System; expression = Val{true}, eval_expression = false, + eval_module = @__MODULE__, kwargs...) + cons = canonical_constraints(sys) + dvs = unknowns(sys) + ps = reorder_parameters(sys) + res = build_function_wrapper(sys, cons, dvs, ps...; expression = Val{true}, kwargs...) + if expression == Val{true} + return res + end + f_oop, f_iip = eval_or_rgf.(res; eval_expression, eval_module) + fn = GeneratedFunctionWrapper{(2, 2, is_split(sys))}(f_oop, f_iip) + return fn +end + +function generate_constraint_jacobian( + sys::System; expression = Val{true}, eval_expression = false, + eval_module = @__MODULE__, return_sparsity = false, + simplify = false, sparse = false, kwargs...) + cons = canonical_constraints(sys) + dvs = unknowns(sys) + ps = reorder_parameters(sys) + sparsity = nothing + if sparse + jac = Symbolics.sparsejacobian(cons, dvs; simplify)::AbstractSparseArray + sparsity = similar(jac, Float64) + else + jac = Symbolics.jacobian(cons, dvs; simplify) + end + res = build_function_wrapper(sys, jac, dvs, ps...; expression = Val{true}, kwargs...) + if expression == Val{true} + return return_sparsity ? (res, sparsity) : res + end + f_oop, f_iip = eval_or_rgf.(res; eval_expression, eval_module) + fn = GeneratedFunctionWrapper{(2, 2, is_split(sys))}(f_oop, f_iip) + return return_sparsity ? (fn, sparsity) : fn +end + +function generate_constraint_hessian( + sys::System; expression = Val{true}, eval_expression = false, + eval_module = @__MODULE__, return_sparsity = false, + simplify = false, sparse = false, kwargs...) + cons = canonical_constraints(sys) + dvs = unknowns(sys) + ps = reorder_parameters(sys) + sparsity = nothing + if sparse + hess = map(cons) do cstr + Symbolics.sparsehessian(cstr, dvs; simplify)::AbstractSparseArray + end + sparsity = similar.(hess, Float64) + else + hess = [Symbolics.hessian(cstr, dvs; simplify) for cstr in cons] + end + res = build_function_wrapper(sys, hess, dvs, ps...; expression = Val{true}, kwargs...) + if expression == Val{true} + return return_sparsity ? (res, sparsity) : res + end + f_oop, f_iip = eval_or_rgf.(res; eval_expression, eval_module) + fn = GeneratedFunctionWrapper{(2, 2, is_split(sys))}(f_oop, f_iip) + return return_sparsity ? (fn, sparsity) : fn +end From 5d210a997d95b2f80d6bff17bf4401d91be6e6f8 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Sat, 19 Apr 2025 16:48:11 +0530 Subject: [PATCH 038/185] feat: implement `supports_initialization(::System)` --- src/systems/system.jl | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/systems/system.jl b/src/systems/system.jl index 98272aef6f..ec85fe3b85 100644 --- a/src/systems/system.jl +++ b/src/systems/system.jl @@ -379,3 +379,8 @@ function Base.showerror(io::IO, err::EventsInTimeIndependentSystemError) $(err.devents) """) end + +function supports_initialization(sys::System) + return isempty(jumps(sys)) && !is_discrete_system(sys) && _iszero(cost(sys)) && + isempty(constraints(sys)) +end From 1cb451ccc2a00efd88c6bb38ad178a683c591d02 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Sat, 19 Apr 2025 16:56:35 +0530 Subject: [PATCH 039/185] feat: implement `JumpProblem` for `System` --- src/problems/compatibility.jl | 6 + src/problems/jumpproblem.jl | 207 ++++++++++++++++++++++++++++++++ src/systems/codegen.jl | 56 +++++++++ src/systems/jumps/jumpsystem.jl | 153 ----------------------- src/systems/system.jl | 6 + 5 files changed, 275 insertions(+), 153 deletions(-) create mode 100644 src/problems/jumpproblem.jl diff --git a/src/problems/compatibility.jl b/src/problems/compatibility.jl index 4b7429f023..60a7e9adce 100644 --- a/src/problems/compatibility.jl +++ b/src/problems/compatibility.jl @@ -96,6 +96,12 @@ function check_no_jumps(sys::System, T) end end +function check_has_jumps(sys::System, T) + if isempty(jumps(sys)) + throw(SystemCompatibilityError("`$T` requires a system with jumps.")) + end +end + function check_no_noise(sys::System, T) altT = is_dde(sys) ? SDDEProblem : SDEProblem if get_noise_eqs(sys) !== nothing diff --git a/src/problems/jumpproblem.jl b/src/problems/jumpproblem.jl new file mode 100644 index 0000000000..b4d042e4f8 --- /dev/null +++ b/src/problems/jumpproblem.jl @@ -0,0 +1,207 @@ +@fallback_iip_specialize function JumpProcesses.JumpProblem{iip, spec}( + sys::System, u0map, tspan::Union{Tuple, Nothing}, pmap = SciMLBase.NullParameters(); + check_compatibility = true, eval_expression = false, eval_module = @__MODULE__, + checkbounds = false, cse = true, aggregator = JumpProcesses.NullAggregator(), + callback = nothing, kwargs...) where {iip, spec} + check_complete(sys, JumpProblem) + check_compatibility && check_compatible_system(JumpProblem, sys) + + has_vrjs = any(x -> x isa VariableRateJump, jumps(sys)) + has_eqs = !isempty(equations(sys)) + has_noise = get_noise_eqs(sys) !== nothing + + if (has_vrjs || has_eqs) + if has_eqs && has_noise + prob = SDEProblem{iip, spec}( + sys, u0map, tspan, pmap; check_compatibility = false, + build_initializeprob = false, checkbounds, cse, kwargs...) + elseif has_eqs + prob = ODEProblem{iip, spec}( + sys, u0map, tspan, pmap; check_compatibility = false, + build_initializeprob = false, checkbounds, cse, kwargs...) + else + _, u0, p = process_SciMLProblem(EmptySciMLFunction, sys, u0map, pmap; + t = tspan === nothing ? nothing : tspan[1], tofloat = false, + check_length = false, build_initializeprob = false) + observedfun = ObservedFunctionCache(sys; eval_expression, eval_module, + checkbounds, cse) + f = (du, u, p, t) -> (du .= 0; nothing) + df = ODEFunction{true, spec}(f; sys, observed = observedfun) + prob = ODEProblem{true}(df, u0, tspan, p; kwargs...) + end + else + _f, u0, p = process_SciMLProblem(EmptySciMLFunction, sys, u0map, pmap; + t = tspan === nothing ? nothing : tspan[1], tofloat = false, check_length = false, build_initializeprob = false, cse) + f = DiffEqBase.DISCRETE_INPLACE_DEFAULT + + observedfun = ObservedFunctionCache( + sys; eval_expression, eval_module, checkbounds, cse) + + df = DiscreteFunction{true, true}(f; sys = sys, observed = observedfun, + initialization_data = get(_f.kwargs, :initialization_data, nothing)) + prob = DiscreteProblem(df, u0, tspan, p; kwargs...) + end + + dvs = unknowns(sys) + unknowntoid = Dict(value(unknown) => i for (i, unknown) in enumerate(dvs)) + js = jumps(sys) + invttype = prob.tspan[1] === nothing ? Float64 : typeof(1 / prob.tspan[2]) + + # handling parameter substitution and empty param vecs + p = (prob.p isa DiffEqBase.NullParameters || prob.p === nothing) ? Num[] : prob.p + + majpmapper = JumpSysMajParamMapper(sys, p; jseqs = js, rateconsttype = invttype) + _majs = filter(x -> x isa MassActionJump, js) + _crjs = filter(x -> x isa ConstantRateJump, js) + vrjs = filter(x -> x isa VariableRateJump, js) + majs = isempty(_majs) ? nothing : assemble_maj(_majs, unknowntoid, majpmapper) + crjs = ConstantRateJump[assemble_crj(sys, j, unknowntoid; eval_expression, eval_module) + for j in _crjs] + vrjs = VariableRateJump[assemble_vrj(sys, j, unknowntoid; eval_expression, eval_module) + for j in vrjs] + jset = JumpSet(Tuple(vrjs), Tuple(crjs), nothing, majs) + + # dep graphs are only for constant rate jumps + nonvrjs = ArrayPartition(_majs, _crjs) + if needs_vartojumps_map(aggregator) || needs_depgraph(aggregator) || + (aggregator isa JumpProcesses.NullAggregator) + jdeps = asgraph(sys; eqs = nonvrjs) + vdeps = variable_dependencies(sys; eqs = nonvrjs) + vtoj = jdeps.badjlist + jtov = vdeps.badjlist + jtoj = needs_depgraph(aggregator) ? eqeq_dependencies(jdeps, vdeps).fadjlist : + nothing + else + vtoj = nothing + jtov = nothing + jtoj = nothing + end + + # handle events, making sure to reset aggregators in the generated affect functions + cbs = process_events(sys; callback, eval_expression, eval_module, + postprocess_affect_expr! = _reset_aggregator!) + + return JumpProblem(prob, aggregator, jset; dep_graph = jtoj, vartojumps_map = vtoj, + jumptovars_map = jtov, scale_rates = false, nocopy = true, + callback = cbs, kwargs...) +end + +function check_compatible_system(T::Union{Type{JumpProblem}}, sys::System) + check_time_dependent(sys, T) + check_not_dde(sys) + check_no_cost(sys, T) + check_no_constraints(sys, T) + check_has_jumps(sys, T) + check_is_continuous(sys, T) +end + +###################### parameter mapper ########################### +struct JumpSysMajParamMapper{U, V, W} + paramexprs::U # the parameter expressions to use for each jump rate constant + sympars::V # parameters(sys) from the underlying JumpSystem + subdict::Any # mapping from an element of parameters(sys) to its current numerical value +end + +function JumpSysMajParamMapper(js::System, p; jseqs = nothing, rateconsttype = Float64) + eqs = (jseqs === nothing) ? jumps(js) : jseqs + majs = MassActionJump[x for x in eqs if x isa MassActionJump] + paramexprs = [maj.scaled_rates for maj in majs] + psyms = reduce(vcat, reorder_parameters(js); init = []) + paramdict = Dict(value(k) => value(v) for (k, v) in zip(psyms, vcat(p...))) + JumpSysMajParamMapper{typeof(paramexprs), typeof(psyms), rateconsttype}(paramexprs, + psyms, + paramdict) +end + +function updateparams!(ratemap::JumpSysMajParamMapper{U, V, W}, + params) where {U <: AbstractArray, V <: AbstractArray, W} + for (i, p) in enumerate(params) + sympar = ratemap.sympars[i] + ratemap.subdict[sympar] = p + end + nothing +end + +function updateparams!(ratemap::JumpSysMajParamMapper{U, V, W}, + params::MTKParameters) where {U <: AbstractArray, V <: AbstractArray, W} + for (i, p) in enumerate(ArrayPartition(params...)) + sympar = ratemap.sympars[i] + ratemap.subdict[sympar] = p + end + nothing +end + +function updateparams!(::JumpSysMajParamMapper{U, V, W}, + params::Nothing) where {U <: AbstractArray, V <: AbstractArray, W} + nothing +end + +# update a maj with parameter vectors +function (ratemap::JumpSysMajParamMapper{U, V, W})(maj::MassActionJump, newparams; + scale_rates, + kwargs...) where {U <: AbstractArray, + V <: AbstractArray, W} + updateparams!(ratemap, newparams) + for i in 1:get_num_majumps(maj) + maj.scaled_rates[i] = convert(W, + value(substitute(ratemap.paramexprs[i], + ratemap.subdict))) + end + scale_rates && JumpProcesses.scalerates!(maj.scaled_rates, maj.reactant_stoch) + nothing +end + +##### MTK dispatches for Symbolic jumps ##### +eqtype_supports_collect_vars(j::MassActionJump) = true +function collect_vars!(unknowns, parameters, j::MassActionJump, iv; depth = 0, + op = Differential) + collect_vars!(unknowns, parameters, j.scaled_rates, iv; depth, op) + for field in (j.reactant_stoch, j.net_stoch) + for el in field + collect_vars!(unknowns, parameters, el, iv; depth, op) + end + end + return nothing +end + +eqtype_supports_collect_vars(j::Union{ConstantRateJump, VariableRateJump}) = true +function collect_vars!(unknowns, parameters, j::Union{ConstantRateJump, VariableRateJump}, + iv; depth = 0, op = Differential) + collect_vars!(unknowns, parameters, j.rate, iv; depth, op) + for eq in j.affect! + (eq isa Equation) && collect_vars!(unknowns, parameters, eq, iv; depth, op) + end + return nothing +end + +### Functions to determine which unknowns a jump depends on +function get_variables!(dep, jump::Union{ConstantRateJump, VariableRateJump}, variables) + jr = value(jump.rate) + (jr isa Symbolic) && get_variables!(dep, jr, variables) + dep +end + +function get_variables!(dep, jump::MassActionJump, variables) + sr = value(jump.scaled_rates) + (sr isa Symbolic) && get_variables!(dep, sr, variables) + for varasop in jump.reactant_stoch + any(isequal(varasop[1]), variables) && push!(dep, varasop[1]) + end + dep +end + +### Functions to determine which unknowns are modified by a given jump +function modified_unknowns!(munknowns, jump::Union{ConstantRateJump, VariableRateJump}, sts) + for eq in jump.affect! + st = eq.lhs + any(isequal(st), sts) && push!(munknowns, st) + end + munknowns +end + +function modified_unknowns!(munknowns, jump::MassActionJump, sts) + for (unknown, stoich) in jump.net_stoch + any(isequal(unknown), sts) && push!(munknowns, unknown) + end + munknowns +end diff --git a/src/systems/codegen.jl b/src/systems/codegen.jl index be71917cbc..1a66f2e457 100644 --- a/src/systems/codegen.jl +++ b/src/systems/codegen.jl @@ -488,3 +488,59 @@ function generate_constraint_hessian( fn = GeneratedFunctionWrapper{(2, 2, is_split(sys))}(f_oop, f_iip) return return_sparsity ? (fn, sparsity) : fn end + +# modifies the expression representing an affect function to +# call reset_aggregated_jumps!(integrator). +# assumes iip +function _reset_aggregator!(expr, integrator) + @assert Meta.isexpr(expr, :function) + body = expr.args[end] + body = quote + $body + $reset_aggregated_jumps!($integrator) + end + expr.args[end] = body + return nothing +end + +function generate_rate_function(js::System, rate) + p = reorder_parameters(js) + build_function_wrapper(js, rate, unknowns(js), p..., + get_iv(js), + expression = Val{true}) +end + +function generate_affect_function(js::System, affect, outputidxs) + compile_affect( + affect, nothing, js, unknowns(js), parameters(js); outputidxs = outputidxs, + expression = Val{true}, checkvars = false) +end + +function assemble_vrj( + js, vrj, unknowntoid; eval_expression = false, eval_module = @__MODULE__) + rate = eval_or_rgf(generate_rate_function(js, vrj.rate); eval_expression, eval_module) + rate = GeneratedFunctionWrapper{(2, 3, is_split(js))}(rate, nothing) + outputvars = (value(affect.lhs) for affect in vrj.affect!) + outputidxs = [unknowntoid[var] for var in outputvars] + affect = eval_or_rgf(generate_affect_function(js, vrj.affect!, outputidxs); + eval_expression, eval_module) + VariableRateJump(rate, affect; save_positions = vrj.save_positions) +end + +function assemble_crj( + js, crj, unknowntoid; eval_expression = false, eval_module = @__MODULE__) + rate = eval_or_rgf(generate_rate_function(js, crj.rate); eval_expression, eval_module) + rate = GeneratedFunctionWrapper{(2, 3, is_split(js))}(rate, nothing) + outputvars = (value(affect.lhs) for affect in crj.affect!) + outputidxs = [unknowntoid[var] for var in outputvars] + affect = eval_or_rgf(generate_affect_function(js, crj.affect!, outputidxs); + eval_expression, eval_module) + ConstantRateJump(rate, affect) +end + +# assemble a numeric MassActionJump from a MT symbolics MassActionJumps +function assemble_maj(majv::Vector{U}, unknowntoid, pmapper) where {U <: MassActionJump} + rs = [numericrstoich(maj.reactant_stoch, unknowntoid) for maj in majv] + ns = [numericnstoich(maj.net_stoch, unknowntoid) for maj in majv] + MassActionJump(rs, ns; param_mapper = pmapper, nocopy = true) +end diff --git a/src/systems/jumps/jumpsystem.jl b/src/systems/jumps/jumpsystem.jl index 06f5e1b623..ae44a4a929 100644 --- a/src/systems/jumps/jumpsystem.jl +++ b/src/systems/jumps/jumpsystem.jl @@ -1,19 +1,5 @@ const JumpType = Union{VariableRateJump, ConstantRateJump, MassActionJump} -# modifies the expression representing an affect function to -# call reset_aggregated_jumps!(integrator). -# assumes iip -function _reset_aggregator!(expr, integrator) - @assert Meta.isexpr(expr, :function) - body = expr.args[end] - body = quote - $body - $reset_aggregated_jumps!($integrator) - end - expr.args[end] = body - return nothing -end - """ $(TYPEDEF) @@ -240,29 +226,6 @@ function JumpSystem(eqs, iv, unknowns, ps; parameter_dependencies, metadata, gui_metadata, checks = checks) end -##### MTK dispatches for JumpSystems ##### -eqtype_supports_collect_vars(j::MassActionJump) = true -function collect_vars!(unknowns, parameters, j::MassActionJump, iv; depth = 0, - op = Differential) - collect_vars!(unknowns, parameters, j.scaled_rates, iv; depth, op) - for field in (j.reactant_stoch, j.net_stoch) - for el in field - collect_vars!(unknowns, parameters, el, iv; depth, op) - end - end - return nothing -end - -eqtype_supports_collect_vars(j::Union{ConstantRateJump, VariableRateJump}) = true -function collect_vars!(unknowns, parameters, j::Union{ConstantRateJump, VariableRateJump}, - iv; depth = 0, op = Differential) - collect_vars!(unknowns, parameters, j.rate, iv; depth, op) - for eq in j.affect! - (eq isa Equation) && collect_vars!(unknowns, parameters, eq, iv; depth, op) - end - return nothing -end - ########################################## has_massactionjumps(js::JumpSystem) = !isempty(equations(js).x[1]) @@ -293,17 +256,6 @@ function generate_affect_function(js::JumpSystem, affect, outputidxs) expression = Val{true}, checkvars = false) end -function assemble_vrj( - js, vrj, unknowntoid; eval_expression = false, eval_module = @__MODULE__) - rate = eval_or_rgf(generate_rate_function(js, vrj.rate); eval_expression, eval_module) - rate = GeneratedFunctionWrapper{(2, 3, is_split(js))}(rate, nothing) - outputvars = (value(affect.lhs) for affect in vrj.affect!) - outputidxs = [unknowntoid[var] for var in outputvars] - affect = eval_or_rgf(generate_affect_function(js, vrj.affect!, outputidxs); - eval_expression, eval_module) - VariableRateJump(rate, affect; save_positions = vrj.save_positions) -end - function assemble_vrj_expr(js, vrj, unknowntoid) rate = generate_rate_function(js, vrj.rate) outputvars = (value(affect.lhs) for affect in vrj.affect!) @@ -317,17 +269,6 @@ function assemble_vrj_expr(js, vrj, unknowntoid) end end -function assemble_crj( - js, crj, unknowntoid; eval_expression = false, eval_module = @__MODULE__) - rate = eval_or_rgf(generate_rate_function(js, crj.rate); eval_expression, eval_module) - rate = GeneratedFunctionWrapper{(2, 3, is_split(js))}(rate, nothing) - outputvars = (value(affect.lhs) for affect in crj.affect!) - outputidxs = [unknowntoid[var] for var in outputvars] - affect = eval_or_rgf(generate_affect_function(js, crj.affect!, outputidxs); - eval_expression, eval_module) - ConstantRateJump(rate, affect) -end - function assemble_crj_expr(js, crj, unknowntoid) rate = generate_rate_function(js, crj.rate) outputvars = (value(affect.lhs) for affect in crj.affect!) @@ -366,13 +307,6 @@ function numericnstoich(mtrs::Vector{Pair{V, W}}, unknowntoid) where {V, W} sort!(ns) end -# assemble a numeric MassActionJump from a MT symbolics MassActionJumps -function assemble_maj(majv::Vector{U}, unknowntoid, pmapper) where {U <: MassActionJump} - rs = [numericrstoich(maj.reactant_stoch, unknowntoid) for maj in majv] - ns = [numericnstoich(maj.net_stoch, unknowntoid) for maj in majv] - MassActionJump(rs, ns; param_mapper = pmapper, nocopy = true) -end - """ ```julia DiffEqBase.DiscreteProblem(sys::JumpSystem, u0map, tspan, @@ -582,78 +516,6 @@ function JumpProcesses.JumpProblem(js::JumpSystem, prob, callback = cbs, kwargs...) end -### Functions to determine which unknowns a jump depends on -function get_variables!(dep, jump::Union{ConstantRateJump, VariableRateJump}, variables) - jr = value(jump.rate) - (jr isa Symbolic) && get_variables!(dep, jr, variables) - dep -end - -function get_variables!(dep, jump::MassActionJump, variables) - sr = value(jump.scaled_rates) - (sr isa Symbolic) && get_variables!(dep, sr, variables) - for varasop in jump.reactant_stoch - any(isequal(varasop[1]), variables) && push!(dep, varasop[1]) - end - dep -end - -### Functions to determine which unknowns are modified by a given jump -function modified_unknowns!(munknowns, jump::Union{ConstantRateJump, VariableRateJump}, sts) - for eq in jump.affect! - st = eq.lhs - any(isequal(st), sts) && push!(munknowns, st) - end - munknowns -end - -function modified_unknowns!(munknowns, jump::MassActionJump, sts) - for (unknown, stoich) in jump.net_stoch - any(isequal(unknown), sts) && push!(munknowns, unknown) - end - munknowns -end - -###################### parameter mapper ########################### -struct JumpSysMajParamMapper{U, V, W} - paramexprs::U # the parameter expressions to use for each jump rate constant - sympars::V # parameters(sys) from the underlying JumpSystem - subdict::Any # mapping from an element of parameters(sys) to its current numerical value -end - -function JumpSysMajParamMapper(js::JumpSystem, p; jseqs = nothing, rateconsttype = Float64) - eqs = (jseqs === nothing) ? equations(js) : jseqs - paramexprs = [maj.scaled_rates for maj in eqs.x[1]] - psyms = reduce(vcat, reorder_parameters(js); init = []) - paramdict = Dict(value(k) => value(v) for (k, v) in zip(psyms, vcat(p...))) - JumpSysMajParamMapper{typeof(paramexprs), typeof(psyms), rateconsttype}(paramexprs, - psyms, - paramdict) -end - -function updateparams!(ratemap::JumpSysMajParamMapper{U, V, W}, - params) where {U <: AbstractArray, V <: AbstractArray, W} - for (i, p) in enumerate(params) - sympar = ratemap.sympars[i] - ratemap.subdict[sympar] = p - end - nothing -end - -function updateparams!(ratemap::JumpSysMajParamMapper{U, V, W}, - params::MTKParameters) where {U <: AbstractArray, V <: AbstractArray, W} - for (i, p) in enumerate(ArrayPartition(params...)) - sympar = ratemap.sympars[i] - ratemap.subdict[sympar] = p - end - nothing -end - -function updateparams!(::JumpSysMajParamMapper{U, V, W}, - params::Nothing) where {U <: AbstractArray, V <: AbstractArray, W} - nothing -end - # create the initial parameter vector for use in a MassActionJump function (ratemap::JumpSysMajParamMapper{ U, @@ -666,19 +528,4 @@ function (ratemap::JumpSysMajParamMapper{ for paramexpr in ratemap.paramexprs] end -# update a maj with parameter vectors -function (ratemap::JumpSysMajParamMapper{U, V, W})(maj::MassActionJump, newparams; - scale_rates, - kwargs...) where {U <: AbstractArray, - V <: AbstractArray, W} - updateparams!(ratemap, newparams) - for i in 1:get_num_majumps(maj) - maj.scaled_rates[i] = convert(W, - value(substitute(ratemap.paramexprs[i], - ratemap.subdict))) - end - scale_rates && JumpProcesses.scalerates!(maj.scaled_rates, maj.reactant_stoch) - nothing -end - supports_initialization(::JumpSystem) = false diff --git a/src/systems/system.jl b/src/systems/system.jl index ec85fe3b85..d0788b00c1 100644 --- a/src/systems/system.jl +++ b/src/systems/system.jl @@ -306,6 +306,12 @@ function flatten(sys::System, noeqs = false) description = description(sys), name = nameof(sys)) end +has_massactionjumps(js::System) = any(x -> x isa MassActionJump, jumps(js)) +has_constantratejumps(js::System) = any(x -> x isa ConstantRateJump, jumps(js)) +has_variableratejumps(js::System) = any(x -> x isa VariableRateJump, jumps(js)) +# TODO: do we need this? it's kind of weird to keep +has_equations(js::System) = !isempty(equations(js)) + """ $(TYPEDSIGNATURES) """ From 3c2fb63b4743230c4d660533f412151bec333b1e Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Sat, 19 Apr 2025 18:24:42 +0530 Subject: [PATCH 040/185] refactor: move `InitializationProblem` to its own file --- src/problems/initializationproblem.jl | 140 ++++++++++++++++++ src/systems/diffeqs/abstractodesystem.jl | 175 ----------------------- 2 files changed, 140 insertions(+), 175 deletions(-) create mode 100644 src/problems/initializationproblem.jl diff --git a/src/problems/initializationproblem.jl b/src/problems/initializationproblem.jl new file mode 100644 index 0000000000..202afb8537 --- /dev/null +++ b/src/problems/initializationproblem.jl @@ -0,0 +1,140 @@ +struct InitializationProblem{iip, specialization} end + +""" +```julia +InitializationProblem{iip}(sys::AbstractSystem, t, u0map, + parammap = DiffEqBase.NullParameters(); + version = nothing, tgrad = false, + jac = false, + checkbounds = false, sparse = false, + simplify = false, + linenumbers = true, parallel = SerialForm(), + initialization_eqs = [], + fully_determined = false, + kwargs...) where {iip} +``` + +Generates a NonlinearProblem or NonlinearLeastSquaresProblem from a System +which represents the initialization, i.e. the calculation of the consistent +initial conditions for the given DAE. +""" +@fallback_iip_specialize function InitializationProblem{iip, specialize}( + sys::AbstractSystem, + t, u0map = [], + parammap = DiffEqBase.NullParameters(); + guesses = [], + check_length = true, + warn_initialize_determined = true, + initialization_eqs = [], + fully_determined = nothing, + check_units = true, + use_scc = true, + allow_incomplete = false, + force_time_independent = false, + algebraic_only = false, + time_dependent_init = is_time_dependent(sys), + kwargs...) where {iip, specialize} + if !iscomplete(sys) + error("A completed system is required. Call `complete` or `structural_simplify` on the system before creating an `ODEProblem`") + end + if isempty(u0map) && get_initializesystem(sys) !== nothing + isys = get_initializesystem(sys; initialization_eqs, check_units) + simplify_system = false + elseif isempty(u0map) && get_initializesystem(sys) === nothing + isys = generate_initializesystem( + sys; initialization_eqs, check_units, pmap = parammap, + guesses, algebraic_only) + simplify_system = true + else + isys = generate_initializesystem( + sys; u0map, initialization_eqs, check_units, time_dependent_init, + pmap = parammap, guesses, algebraic_only) + simplify_system = true + end + + # useful for `SteadyStateProblem` since `f` has to be autonomous and the + # initialization should be too + if force_time_independent + idx = findfirst(isequal(get_iv(sys)), get_ps(isys)) + idx === nothing || deleteat!(get_ps(isys), idx) + end + + if simplify_system + isys = structural_simplify(isys; fully_determined) + end + + ts = get_tearing_state(isys) + unassigned_vars = StructuralTransformations.singular_check(ts) + if warn_initialize_determined && !isempty(unassigned_vars) + errmsg = """ + The initialization system is structurally singular. Guess values may \ + significantly affect the initial values of the ODE. The problematic variables \ + are $unassigned_vars. + + Note that the identification of problematic variables is a best-effort heuristic. + """ + @warn errmsg + end + + uninit = setdiff(unknowns(sys), [unknowns(isys); observables(isys)]) + + # TODO: throw on uninitialized arrays + filter!(x -> !(x isa Symbolics.Arr), uninit) + if time_dependent_init && !isempty(uninit) + allow_incomplete || throw(IncompleteInitializationError(uninit)) + # for incomplete initialization, we will add the missing variables as parameters. + # they will be updated by `update_initializeprob!` and `initializeprobmap` will + # use them to construct the new `u0`. + newparams = map(toparam, uninit) + append!(get_ps(isys), newparams) + isys = complete(isys) + end + + neqs = length(equations(isys)) + nunknown = length(unknowns(isys)) + + if use_scc + scc_message = "`SCCNonlinearProblem` can only be used for initialization of fully determined systems and hence will not be used here. " + else + scc_message = "" + end + + if warn_initialize_determined && neqs > nunknown + @warn "Initialization system is overdetermined. $neqs equations for $nunknown unknowns. Initialization will default to using least squares. $(scc_message)To suppress this warning pass warn_initialize_determined = false. To make this warning into an error, pass fully_determined = true" + end + if warn_initialize_determined && neqs < nunknown + @warn "Initialization system is underdetermined. $neqs equations for $nunknown unknowns. Initialization will default to using least squares. $(scc_message)To suppress this warning pass warn_initialize_determined = false. To make this warning into an error, pass fully_determined = true" + end + + parammap = recursive_unwrap(anydict(parammap)) + if t !== nothing + parammap[get_iv(sys)] = t + end + filter!(kvp -> kvp[2] !== missing, parammap) + + u0map = to_varmap(u0map, unknowns(sys)) + if isempty(guesses) + guesses = Dict() + end + + filter_missing_values!(u0map) + filter_missing_values!(parammap) + u0map = merge(ModelingToolkit.guesses(sys), todict(guesses), u0map) + + TProb = if neqs == nunknown && isempty(unassigned_vars) + if use_scc && neqs > 0 + if is_split(isys) + SCCNonlinearProblem + else + @warn "`SCCNonlinearProblem` can only be used with `split = true` systems. Simplify your `ODESystem` with `split = true` or pass `use_scc = false` to disable this warning" + NonlinearProblem + end + else + NonlinearProblem + end + else + NonlinearLeastSquaresProblem + end + TProb(isys, u0map, parammap; kwargs..., + build_initializeprob = false, is_initializeprob = true) +end diff --git a/src/systems/diffeqs/abstractodesystem.jl b/src/systems/diffeqs/abstractodesystem.jl index 7776b954b0..7ae0eb3e52 100644 --- a/src/systems/diffeqs/abstractodesystem.jl +++ b/src/systems/diffeqs/abstractodesystem.jl @@ -1309,178 +1309,3 @@ function flatten_equations(eqs) end end end - -struct InitializationProblem{iip, specialization} end - -""" -```julia -InitializationProblem{iip}(sys::AbstractODESystem, t, u0map, - parammap = DiffEqBase.NullParameters(); - version = nothing, tgrad = false, - jac = false, - checkbounds = false, sparse = false, - simplify = false, - linenumbers = true, parallel = SerialForm(), - initialization_eqs = [], - fully_determined = false, - kwargs...) where {iip} -``` - -Generates a NonlinearProblem or NonlinearLeastSquaresProblem from an ODESystem -which represents the initialization, i.e. the calculation of the consistent -initial conditions for the given DAE. -""" -function InitializationProblem(sys::AbstractSystem, args...; kwargs...) - InitializationProblem{true}(sys, args...; kwargs...) -end - -function InitializationProblem(sys::AbstractSystem, t, - u0map::StaticArray, - args...; - kwargs...) - InitializationProblem{false, SciMLBase.FullSpecialize}( - sys, t, u0map, args...; kwargs...) -end - -function InitializationProblem{true}(sys::AbstractSystem, args...; kwargs...) - InitializationProblem{true, SciMLBase.AutoSpecialize}(sys, args...; kwargs...) -end - -function InitializationProblem{false}(sys::AbstractSystem, args...; kwargs...) - InitializationProblem{false, SciMLBase.FullSpecialize}(sys, args...; kwargs...) -end - -const INCOMPLETE_INITIALIZATION_MESSAGE = """ - Initialization incomplete. Not all of the state variables of the - DAE system can be determined by the initialization. Missing - variables: - """ - -struct IncompleteInitializationError <: Exception - uninit::Any -end - -function Base.showerror(io::IO, e::IncompleteInitializationError) - println(io, INCOMPLETE_INITIALIZATION_MESSAGE) - println(io, e.uninit) -end - -function InitializationProblem{iip, specialize}(sys::AbstractSystem, - t, u0map = [], - parammap = DiffEqBase.NullParameters(); - guesses = [], - check_length = true, - warn_initialize_determined = true, - initialization_eqs = [], - fully_determined = nothing, - check_units = true, - use_scc = true, - allow_incomplete = false, - force_time_independent = false, - algebraic_only = false, - time_dependent_init = is_time_dependent(sys), - kwargs...) where {iip, specialize} - if !iscomplete(sys) - error("A completed system is required. Call `complete` or `structural_simplify` on the system before creating an `ODEProblem`") - end - if isempty(u0map) && get_initializesystem(sys) !== nothing - isys = get_initializesystem(sys; initialization_eqs, check_units) - simplify_system = false - elseif isempty(u0map) && get_initializesystem(sys) === nothing - isys = generate_initializesystem( - sys; initialization_eqs, check_units, pmap = parammap, - guesses, algebraic_only) - simplify_system = true - else - isys = generate_initializesystem( - sys; u0map, initialization_eqs, check_units, time_dependent_init, - pmap = parammap, guesses, algebraic_only) - simplify_system = true - end - - # useful for `SteadyStateProblem` since `f` has to be autonomous and the - # initialization should be too - if force_time_independent - idx = findfirst(isequal(get_iv(sys)), get_ps(isys)) - idx === nothing || deleteat!(get_ps(isys), idx) - end - - if simplify_system - isys = structural_simplify(isys; fully_determined) - end - - ts = get_tearing_state(isys) - unassigned_vars = StructuralTransformations.singular_check(ts) - if warn_initialize_determined && !isempty(unassigned_vars) - errmsg = """ - The initialization system is structurally singular. Guess values may \ - significantly affect the initial values of the ODE. The problematic variables \ - are $unassigned_vars. - - Note that the identification of problematic variables is a best-effort heuristic. - """ - @warn errmsg - end - - uninit = setdiff(unknowns(sys), [unknowns(isys); observables(isys)]) - - # TODO: throw on uninitialized arrays - filter!(x -> !(x isa Symbolics.Arr), uninit) - if time_dependent_init && !isempty(uninit) - allow_incomplete || throw(IncompleteInitializationError(uninit)) - # for incomplete initialization, we will add the missing variables as parameters. - # they will be updated by `update_initializeprob!` and `initializeprobmap` will - # use them to construct the new `u0`. - newparams = map(toparam, uninit) - append!(get_ps(isys), newparams) - isys = complete(isys) - end - - neqs = length(equations(isys)) - nunknown = length(unknowns(isys)) - - if use_scc - scc_message = "`SCCNonlinearProblem` can only be used for initialization of fully determined systems and hence will not be used here. " - else - scc_message = "" - end - - if warn_initialize_determined && neqs > nunknown - @warn "Initialization system is overdetermined. $neqs equations for $nunknown unknowns. Initialization will default to using least squares. $(scc_message)To suppress this warning pass warn_initialize_determined = false. To make this warning into an error, pass fully_determined = true" - end - if warn_initialize_determined && neqs < nunknown - @warn "Initialization system is underdetermined. $neqs equations for $nunknown unknowns. Initialization will default to using least squares. $(scc_message)To suppress this warning pass warn_initialize_determined = false. To make this warning into an error, pass fully_determined = true" - end - - parammap = recursive_unwrap(anydict(parammap)) - if t !== nothing - parammap[get_iv(sys)] = t - end - filter!(kvp -> kvp[2] !== missing, parammap) - - u0map = to_varmap(u0map, unknowns(sys)) - if isempty(guesses) - guesses = Dict() - end - - filter_missing_values!(u0map) - filter_missing_values!(parammap) - u0map = merge(ModelingToolkit.guesses(sys), todict(guesses), u0map) - - TProb = if neqs == nunknown && isempty(unassigned_vars) - if use_scc && neqs > 0 - if is_split(isys) - SCCNonlinearProblem - else - @warn "`SCCNonlinearProblem` can only be used with `split = true` systems. Simplify your `ODESystem` with `split = true` or pass `use_scc = false` to disable this warning" - NonlinearProblem - end - else - NonlinearProblem - end - else - NonlinearLeastSquaresProblem - end - TProb(isys, u0map, parammap; kwargs..., - build_initializeprob = false, is_initializeprob = true) -end From 9ba48b932f4231aceb809ab3e35730e74245502a Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Sat, 19 Apr 2025 19:13:38 +0530 Subject: [PATCH 041/185] feat: add `isscheduled` to `System` --- src/systems/system.jl | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/systems/system.jl b/src/systems/system.jl index d0788b00c1..8a66d2ceb3 100644 --- a/src/systems/system.jl +++ b/src/systems/system.jl @@ -38,6 +38,7 @@ struct System <: AbstractSystem ignored_connections::Union{ Nothing, Tuple{Vector{IgnoredAnalysisPoint}, Vector{IgnoredAnalysisPoint}}} parent::Union{Nothing, System} + isscheduled::Bool function System( tag, eqs, noise_eqs, jumps, constraints, costs, consolidate, unknowns, ps, @@ -47,7 +48,7 @@ struct System <: AbstractSystem metadata = nothing, gui_metadata = nothing, is_dde = false, tstops = [], tearing_state = nothing, namespacing = true, complete = false, index_cache = nothing, ignored_connections = nothing, - parent = nothing; checks::Union{Bool, Int} = true) + parent = nothing, isscheduled = false; checks::Union{Bool, Int} = true) if (checks == true || (checks & CheckComponents) > 0) && iv !== nothing check_independent_variables([iv]) check_variables(unknowns, iv) @@ -71,7 +72,7 @@ struct System <: AbstractSystem guesses, systems, initialization_eqs, continuous_events, discrete_events, connector_type, assertions, metadata, gui_metadata, is_dde, tstops, tearing_state, namespacing, complete, index_cache, ignored_connections, - parent) + parent, isscheduled) end end From f6f8915f2bf50b70fef99af854e42fc65954ec4b Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Sat, 19 Apr 2025 20:00:19 +0530 Subject: [PATCH 042/185] refactor: move improved handling of ODE costs/constraints to `System` --- src/systems/diffeqs/odesystem.jl | 101 ------------------------------- src/systems/system.jl | 79 ++++++++++++++++++++++-- 2 files changed, 75 insertions(+), 105 deletions(-) diff --git a/src/systems/diffeqs/odesystem.jl b/src/systems/diffeqs/odesystem.jl index 01b0ca5fbb..a37e6bc06d 100644 --- a/src/systems/diffeqs/odesystem.jl +++ b/src/systems/diffeqs/odesystem.jl @@ -755,104 +755,3 @@ function Base.show(io::IO, mime::MIME"text/plain", sys::ODESystem; hint = true, return nothing end - -""" -Build the constraint system for the ODESystem. -""" -function process_constraint_system( - constraints::Vector{Equation}, sts, ps, iv; consname = :cons) - isempty(constraints) && return nothing - - constraintsts = OrderedSet() - constraintps = OrderedSet() - for cons in constraints - collect_vars!(constraintsts, constraintps, cons, iv) - end - - # Validate the states. - validate_vars_and_find_ps!(constraintsts, constraintps, sts, iv) - - ConstraintsSystem( - constraints, collect(constraintsts), collect(constraintps); name = consname) -end - -""" -Process the costs for the constraint system. -""" -function process_costs(costs::Vector, sts, ps, iv) - coststs = OrderedSet() - costps = OrderedSet() - for cost in costs - collect_vars!(coststs, costps, cost, iv) - end - - validate_vars_and_find_ps!(coststs, costps, sts, iv) - coststs, costps -end - -""" -Validate that all the variables in an auxiliary system of the ODESystem (constraint or costs) are -well-formed states or parameters. - - Callable/delay variables (e.g. of the form x(0.6) should be unknowns of the system (and have one arg, etc.) - - Callable/delay parameters should be parameters of the system - -Return the set of additional parameters found in the system, e.g. in x(p) ~ 3 then p should be added as a -parameter of the system. -""" -function validate_vars_and_find_ps!(auxvars, auxps, sysvars, iv) - sts = sysvars - - for var in auxvars - if !iscall(var) - occursin(iv, var) && (var ∈ sts || - throw(ArgumentError("Time-dependent variable $var is not an unknown of the system."))) - elseif length(arguments(var)) > 1 - throw(ArgumentError("Too many arguments for variable $var.")) - elseif length(arguments(var)) == 1 - arg = only(arguments(var)) - operation(var)(iv) ∈ sts || - throw(ArgumentError("Variable $var is not a variable of the ODESystem. Called variables must be variables of the ODESystem.")) - - isequal(arg, iv) || isparameter(arg) || arg isa Integer || - arg isa AbstractFloat || - throw(ArgumentError("Invalid argument specified for variable $var. The argument of the variable should be either $iv, a parameter, or a value specifying the time that the constraint holds.")) - - isparameter(arg) && push!(auxps, arg) - else - var ∈ sts && - @warn "Variable $var has no argument. It will be interpreted as $var($iv), and the constraint will apply to the entire interval." - end - end -end - -""" -Generate a function that takes a solution object and computes the cost function obtained by coalescing the costs vector. -""" -function generate_cost_function(sys::ODESystem, kwargs...) - costs = get_costs(sys) - consolidate = get_consolidate(sys) - iv = get_iv(sys) - - ps = parameters(sys; initial_parameters = false) - sts = unknowns(sys) - np = length(ps) - ns = length(sts) - stidxmap = Dict([v => i for (i, v) in enumerate(sts)]) - pidxmap = Dict([v => i for (i, v) in enumerate(ps)]) - - @variables sol(..)[1:ns] - for st in vars(costs) - x = operation(st) - t = only(arguments(st)) - idx = stidxmap[x(iv)] - - costs = map(c -> Symbolics.fast_substitute(c, Dict(x(t) => sol(t)[idx])), costs) - end - - _p = reorder_parameters(sys, ps) - fs = build_function_wrapper(sys, costs, sol, _p..., t; output_type = Array, kwargs...) - vc_oop, vc_iip = eval_or_rgf.(fs) - - cost(sol, p, t) = consolidate(vc_oop(sol, p, t)) - return cost -end diff --git a/src/systems/system.jl b/src/systems/system.jl index 8a66d2ceb3..a8d6159b06 100644 --- a/src/systems/system.jl +++ b/src/systems/system.jl @@ -170,9 +170,10 @@ function System(eqs, iv; kwargs...) collect_vars!(allunknowns, ps, eq, iv) end - for eq in get(kwargs, :constraints, Equation[]) - collect_vars!(allunknowns, ps, eq, iv) - end + cstrs = get(kwargs, :constraints, Equation[]) + cstrunknowns, cstrps = process_constraint_system(cstrs, allunknowns, ps, iv) + union!(allunknowns, cstrunknowns) + union!(ps, cstrps) for ssys in get(kwargs, :systems, System[]) collect_scoped_vars!(allunknowns, ps, ssys, iv) @@ -180,7 +181,9 @@ function System(eqs, iv; kwargs...) costs = get(kwargs, :costs, nothing) if costs !== nothing - collect_vars!(allunknowns, ps, costs, iv) + costunknowns, costps = process_costs(costs, allunknowns, ps, iv) + union!(allunknowns, costunknowns) + union!(ps, costps) end for v in allunknowns @@ -251,6 +254,74 @@ function gather_array_params(ps) return new_ps end +""" +Process variables in constraints of the (ODE) System. +""" +function process_constraint_system( + constraints::Vector{Equation}, sts, ps, iv; consname = :cons) + isempty(constraints) && return Set(), Set() + + constraintsts = OrderedSet() + constraintps = OrderedSet() + for cons in constraints + collect_vars!(constraintsts, constraintps, cons, iv) + end + + # Validate the states. + validate_vars_and_find_ps!(constraintsts, constraintps, sts, iv) + + return constraintsts, constraintps +end + +""" +Process the costs for the constraint system. +""" +function process_costs(costs::Vector, sts, ps, iv) + coststs = OrderedSet() + costps = OrderedSet() + for cost in costs + collect_vars!(coststs, costps, cost, iv) + end + + validate_vars_and_find_ps!(coststs, costps, sts, iv) + coststs, costps +end + +""" +Validate that all the variables in an auxiliary system of the (ODE) System (constraint or costs) are +well-formed states or parameters. + - Callable/delay variables (e.g. of the form x(0.6) should be unknowns of the system (and have one arg, etc.) + - Callable/delay parameters should be parameters of the system + +Return the set of additional parameters found in the system, e.g. in x(p) ~ 3 then p should be added as a +parameter of the system. +""" +function validate_vars_and_find_ps!(auxvars, auxps, sysvars, iv) + sts = sysvars + + for var in auxvars + if !iscall(var) + occursin(iv, var) && (var ∈ sts || + throw(ArgumentError("Time-dependent variable $var is not an unknown of the system."))) + elseif length(arguments(var)) > 1 + throw(ArgumentError("Too many arguments for variable $var.")) + elseif length(arguments(var)) == 1 + arg = only(arguments(var)) + operation(var)(iv) ∈ sts || + throw(ArgumentError("Variable $var is not a variable of the ODESystem. Called variables must be variables of the ODESystem.")) + + isequal(arg, iv) || isparameter(arg) || arg isa Integer || + arg isa AbstractFloat || + throw(ArgumentError("Invalid argument specified for variable $var. The argument of the variable should be either $iv, a parameter, or a value specifying the time that the constraint holds.")) + + isparameter(arg) && push!(auxps, arg) + else + var ∈ sts && + @warn "Variable $var has no argument. It will be interpreted as $var($iv), and the constraint will apply to the entire interval." + end + end +end + """ $(TYPEDSIGNATURES) From 47978dd3205c8dd735b05e9d97de293eed6f8d15 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Sat, 19 Apr 2025 19:59:26 +0530 Subject: [PATCH 043/185] feat: implement `Base.:(==)` for `System` --- src/systems/system.jl | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/src/systems/system.jl b/src/systems/system.jl index a8d6159b06..f9f86757bf 100644 --- a/src/systems/system.jl +++ b/src/systems/system.jl @@ -384,6 +384,31 @@ has_variableratejumps(js::System) = any(x -> x isa VariableRateJump, jumps(js)) # TODO: do we need this? it's kind of weird to keep has_equations(js::System) = !isempty(equations(js)) +# TODO: hash out the semantics of this +function Base.:(==)(sys1::System, sys2::System) + sys1 === sys2 && return true + iv1 = get_iv(sys1) + iv2 = get_iv(sys2) + isequal(iv1, iv2) && + isequal(nameof(sys1), nameof(sys2)) && + _eq_unordered(get_eqs(sys1), get_eqs(sys2)) && + _eq_unordered(get_noise_eqs(sys1), get_noise_eqs(sys2)) && + _eq_unordered(get_jumps(sys1), get_jumps(sys2)) && + _eq_unordered(get_constraints(sys1), get_constraints(sys2)) && + _eq_unordered(get_costs(sys1), get_costs(sys2)) && + _eq_unordered(get_unknowns(sys1), get_unknowns(sys2)) && + _eq_unordered(get_ps(sys1), get_ps(sys2)) && + _eq_unordered(get_brownians(sys1), get_brownians(sys2)) && + _eq_unordered(get_observed(sys1), get_observed(sys2)) && + _eq_unordered(get_parameter_dependencies(sys1), get_parameter_dependencies(sys2)) && + _eq_unordered(get_continuous_events(sys1), get_continuous_events(sys2)) && + _eq_unordered(get_discrete_events(sys1), get_discrete_events(sys2)) && + get_assertions(sys1) == get_assertions(sys2) && + get_is_dde(sys1) == get_is_dde(sys2) && + get_tstops(sys1) == get_tstops(sys2) && + all(s1 == s2 for (s1, s2) in zip(get_systems(sys1), get_systems(sys2))) +end + """ $(TYPEDSIGNATURES) """ From b3d7a4d4dd5a754e2e4e5c1c5a448fbdb3192f18 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Sun, 20 Apr 2025 14:00:45 +0530 Subject: [PATCH 044/185] refactor: remove `odesystem.jl` --- src/ModelingToolkit.jl | 4 +- src/systems/diffeqs/odesystem.jl | 757 ------------------------------- 2 files changed, 1 insertion(+), 760 deletions(-) delete mode 100644 src/systems/diffeqs/odesystem.jl diff --git a/src/ModelingToolkit.jl b/src/ModelingToolkit.jl index 205c9dd477..eccf3026b2 100644 --- a/src/ModelingToolkit.jl +++ b/src/ModelingToolkit.jl @@ -168,7 +168,6 @@ include("systems/optimization/modelingtoolkitize.jl") include("systems/nonlinear/nonlinearsystem.jl") include("systems/nonlinear/homotopy_continuation.jl") -include("systems/diffeqs/odesystem.jl") include("systems/diffeqs/sdesystem.jl") include("systems/diffeqs/abstractodesystem.jl") include("systems/nonlinear/modelingtoolkitize.jl") @@ -261,8 +260,7 @@ export AbstractTimeDependentSystem, AbstractTimeIndependentSystem, AbstractMultivariateSystem -export ODESystem, - ODEFunction, ODEFunctionExpr, ODEProblemExpr, convert_system, +export ODEFunction, ODEFunctionExpr, ODEProblemExpr, convert_system, add_accumulations, System export DAEFunctionExpr, DAEProblemExpr export SDESystem, SDEFunction, SDEFunctionExpr, SDEProblemExpr diff --git a/src/systems/diffeqs/odesystem.jl b/src/systems/diffeqs/odesystem.jl deleted file mode 100644 index a37e6bc06d..0000000000 --- a/src/systems/diffeqs/odesystem.jl +++ /dev/null @@ -1,757 +0,0 @@ -""" -$(TYPEDEF) - -A system of ordinary differential equations. - -# Fields -$(FIELDS) - -# Example - -```julia -using ModelingToolkit -using ModelingToolkit: t_nounits as t, D_nounits as D - -@parameters σ ρ β -@variables x(t) y(t) z(t) - -eqs = [D(x) ~ σ*(y-x), - D(y) ~ x*(ρ-z)-y, - D(z) ~ x*y - β*z] - -@named de = ODESystem(eqs,t,[x,y,z],[σ,ρ,β],tspan=(0, 1000.0)) -``` -""" -struct ODESystem <: AbstractODESystem - """ - A tag for the system. If two systems have the same tag, then they are - structurally identical. - """ - tag::UInt - """The ODEs defining the system.""" - eqs::Vector{Equation} - """Independent variable.""" - iv::BasicSymbolic{Real} - """ - Dependent (unknown) variables. Must not contain the independent variable. - - N.B.: If `torn_matching !== nothing`, this includes all variables. Actual - ODE unknowns are determined by the `SelectedState()` entries in `torn_matching`. - """ - unknowns::Vector - """Parameter variables. Must not contain the independent variable.""" - ps::Vector - """Time span.""" - tspan::Union{NTuple{2, Any}, Nothing} - """Array variables.""" - var_to_name::Any - """Control parameters (some subset of `ps`).""" - ctrls::Vector - """Observed equations.""" - observed::Vector{Equation} - """System of constraints that must be satisfied by the solution to the system.""" - constraintsystem::Union{Nothing, ConstraintsSystem} - """A set of expressions defining the costs of the system for optimal control.""" - costs::Vector - """Takes the cost vector and returns a scalar for optimization.""" - consolidate::Union{Nothing, Function} - """ - Time-derivative matrix. Note: this field will not be defined until - [`calculate_tgrad`](@ref) is called on the system. - """ - tgrad::RefValue{Vector{Num}} - """ - Jacobian matrix. Note: this field will not be defined until - [`calculate_jacobian`](@ref) is called on the system. - """ - jac::RefValue{Any} - """ - Control Jacobian matrix. Note: this field will not be defined until - [`calculate_control_jacobian`](@ref) is called on the system. - """ - ctrl_jac::RefValue{Any} - """ - Note: this field will not be defined until - [`generate_factorized_W`](@ref) is called on the system. - """ - Wfact::RefValue{Matrix{Num}} - """ - Note: this field will not be defined until - [`generate_factorized_W`](@ref) is called on the system. - """ - Wfact_t::RefValue{Matrix{Num}} - """ - The name of the system. - """ - name::Symbol - """ - A description of the system. - """ - description::String - """ - The internal systems. These are required to have unique names. - """ - systems::Vector{ODESystem} - """ - The default values to use when initial conditions and/or - parameters are not supplied in `ODEProblem`. - """ - defaults::Dict - """ - The guesses to use as the initial conditions for the - initialization system. - """ - guesses::Dict - """ - Tearing result specifying how to solve the system. - """ - torn_matching::Union{Matching, Nothing} - """ - The system for performing the initialization. - """ - initializesystem::Union{Nothing, NonlinearSystem} - """ - Extra equations to be enforced during the initialization sequence. - """ - initialization_eqs::Vector{Equation} - """ - The schedule for the code generation process. - """ - schedule::Any - """ - Type of the system. - """ - connector_type::Any - """ - Inject assignment statements before the evaluation of the RHS function. - """ - preface::Any - """ - A `Vector{SymbolicContinuousCallback}` that model events. - The integrator will use root finding to guarantee that it steps at each zero crossing. - """ - continuous_events::Vector{SymbolicContinuousCallback} - """ - A `Vector{SymbolicDiscreteCallback}` that models events. Symbolic - analog to `SciMLBase.DiscreteCallback` that executes an affect when a given condition is - true at the end of an integration step. - """ - discrete_events::Vector{SymbolicDiscreteCallback} - """ - Topologically sorted parameter dependency equations, where all symbols are parameters and - the LHS is a single parameter. - """ - parameter_dependencies::Vector{Equation} - """ - Mapping of conditions which should be true throughout the solution process to corresponding error - messages. These will be added to the equations when calling `debug_system`. - """ - assertions::Dict{BasicSymbolic, String} - """ - Metadata for the system, to be used by downstream packages. - """ - metadata::Any - """ - Metadata for MTK GUI. - """ - gui_metadata::Union{Nothing, GUIMetadata} - """ - A boolean indicating if the given `ODESystem` represents a system of DDEs. - """ - is_dde::Bool - """ - A list of points to provide to the solver as tstops. Uses the same syntax as discrete - events. - """ - tstops::Vector{Any} - """ - Cache for intermediate tearing state. - """ - tearing_state::Any - """ - Substitutions generated by tearing. - """ - substitutions::Any - """ - If false, then `sys.x` no longer performs namespacing. - """ - namespacing::Bool - """ - If true, denotes the model will not be modified any further. - """ - complete::Bool - """ - Cached data for fast symbolic indexing. - """ - index_cache::Union{Nothing, IndexCache} - """ - A list of discrete subsystems. - """ - discrete_subsystems::Any - """ - A list of actual unknowns needed to be solved by solvers. - """ - solved_unknowns::Union{Nothing, Vector{Any}} - """ - A vector of vectors of indices for the split parameters. - """ - split_idxs::Union{Nothing, Vector{Vector{Int}}} - """ - The analysis points removed by transformations, representing connections to be - ignored. The first element of the tuple analysis points connecting systems and - the second are ones connecting variables (for the trivial form of `connect`). - """ - ignored_connections::Union{ - Nothing, Tuple{Vector{IgnoredAnalysisPoint}, Vector{IgnoredAnalysisPoint}}} - """ - The hierarchical parent system before simplification. - """ - parent::Any - - function ODESystem( - tag, deqs, iv, dvs, ps, tspan, var_to_name, ctrls, - observed, constraints, costs, consolidate, tgrad, - jac, ctrl_jac, Wfact, Wfact_t, name, description, systems, defaults, guesses, - torn_matching, initializesystem, initialization_eqs, schedule, - connector_type, preface, cevents, - devents, parameter_dependencies, assertions = Dict{BasicSymbolic, String}(), - metadata = nothing, gui_metadata = nothing, is_dde = false, - tstops = [], tearing_state = nothing, substitutions = nothing, - namespacing = true, complete = false, index_cache = nothing, - discrete_subsystems = nothing, solved_unknowns = nothing, - split_idxs = nothing, ignored_connections = nothing, parent = nothing; - checks::Union{Bool, Int} = true) - if checks == true || (checks & CheckComponents) > 0 - check_independent_variables([iv]) - check_variables(dvs, iv) - check_parameters(ps, iv) - check_equations(deqs, iv) - check_equations(equations(cevents), iv) - check_subsystems(systems) - end - if checks == true || (checks & CheckUnits) > 0 - u = __get_unit_type(dvs, ps, iv) - check_units(u, deqs) - end - new(tag, deqs, iv, dvs, ps, tspan, var_to_name, - ctrls, observed, constraints, costs, consolidate, tgrad, jac, - ctrl_jac, Wfact, Wfact_t, name, description, systems, defaults, guesses, torn_matching, - initializesystem, initialization_eqs, schedule, connector_type, preface, - cevents, devents, parameter_dependencies, assertions, metadata, - gui_metadata, is_dde, tstops, tearing_state, substitutions, namespacing, - complete, index_cache, - discrete_subsystems, solved_unknowns, split_idxs, ignored_connections, parent) - end -end - -function ODESystem(deqs::AbstractVector{<:Equation}, iv, dvs, ps; - controls = Num[], - observed = Equation[], - constraintsystem = nothing, - costs = Num[], - consolidate = nothing, - systems = ODESystem[], - tspan = nothing, - name = nothing, - description = "", - default_u0 = Dict(), - default_p = Dict(), - defaults = _merge(Dict(default_u0), Dict(default_p)), - guesses = Dict(), - initializesystem = nothing, - initialization_eqs = Equation[], - schedule = nothing, - connector_type = nothing, - preface = nothing, - continuous_events = nothing, - discrete_events = nothing, - parameter_dependencies = Equation[], - assertions = Dict(), - checks = true, - metadata = nothing, - gui_metadata = nothing, - is_dde = nothing, - tstops = [], - discover_from_metadata = true) - name === nothing && - throw(ArgumentError("The `name` keyword must be provided. Please consider using the `@named` macro")) - @assert all(control -> any(isequal.(control, ps)), controls) "All controls must also be parameters." - iv′ = value(iv) - ps′ = value.(ps) - ctrl′ = value.(controls) - dvs′ = value.(dvs) - dvs′ = filter(x -> !isdelay(x, iv), dvs′) - parameter_dependencies, ps′ = process_parameter_dependencies( - parameter_dependencies, ps′) - if !(isempty(default_u0) && isempty(default_p)) - Base.depwarn( - "`default_u0` and `default_p` are deprecated. Use `defaults` instead.", - :ODESystem, force = true) - end - defaults = Dict{Any, Any}(todict(defaults)) - guesses = Dict{Any, Any}(todict(guesses)) - var_to_name = Dict() - let defaults = discover_from_metadata ? defaults : Dict(), - guesses = discover_from_metadata ? guesses : Dict() - - process_variables!(var_to_name, defaults, guesses, dvs′) - process_variables!(var_to_name, defaults, guesses, ps′) - process_variables!( - var_to_name, defaults, guesses, [eq.lhs for eq in parameter_dependencies]) - process_variables!( - var_to_name, defaults, guesses, [eq.rhs for eq in parameter_dependencies]) - end - defaults = Dict{Any, Any}(value(k) => value(v) - for (k, v) in pairs(defaults) if v !== nothing) - guesses = Dict{Any, Any}(value(k) => value(v) - for (k, v) in pairs(guesses) if v !== nothing) - - isempty(observed) || collect_var_to_name!(var_to_name, (eq.lhs for eq in observed)) - - tgrad = RefValue(EMPTY_TGRAD) - jac = RefValue{Any}(EMPTY_JAC) - ctrl_jac = RefValue{Any}(EMPTY_JAC) - Wfact = RefValue(EMPTY_JAC) - Wfact_t = RefValue(EMPTY_JAC) - sysnames = nameof.(systems) - if length(unique(sysnames)) != length(sysnames) - throw(ArgumentError("System names must be unique.")) - end - cont_callbacks = SymbolicContinuousCallbacks(continuous_events) - disc_callbacks = SymbolicDiscreteCallbacks(discrete_events) - - if is_dde === nothing - is_dde = _check_if_dde(deqs, iv′, systems) - end - - if !isempty(systems) && !isnothing(constraintsystem) - conssystems = ConstraintsSystem[] - for sys in systems - cons = get_constraintsystem(sys) - cons !== nothing && push!(conssystems, cons) - end - @set! constraintsystem.systems = conssystems - end - costs = wrap.(costs) - - if length(costs) > 1 && isnothing(consolidate) - error("Must specify a consolidation function for the costs vector.") - end - - assertions = Dict{BasicSymbolic, Any}(unwrap(k) => v for (k, v) in assertions) - - ODESystem(Threads.atomic_add!(SYSTEM_COUNT, UInt(1)), - deqs, iv′, dvs′, ps′, tspan, var_to_name, ctrl′, observed, - constraintsystem, costs, consolidate, tgrad, jac, - ctrl_jac, Wfact, Wfact_t, name, description, systems, - defaults, guesses, nothing, initializesystem, - initialization_eqs, schedule, connector_type, preface, cont_callbacks, - disc_callbacks, parameter_dependencies, assertions, - metadata, gui_metadata, is_dde, tstops, checks = checks) -end - -function ODESystem(eqs, iv; constraints = Equation[], costs = Num[], kwargs...) - diffvars, allunknowns, ps, eqs = process_equations(eqs, iv) - - for eq in get(kwargs, :parameter_dependencies, Equation[]) - collect_vars!(allunknowns, ps, eq, iv) - end - - for ssys in get(kwargs, :systems, ODESystem[]) - collect_scoped_vars!(allunknowns, ps, ssys, iv) - end - - for v in allunknowns - isdelay(v, iv) || continue - collect_vars!(allunknowns, ps, arguments(v)[1], iv) - end - - new_ps = OrderedSet() - for p in ps - if iscall(p) && operation(p) === getindex - par = arguments(p)[begin] - if Symbolics.shape(Symbolics.unwrap(par)) !== Symbolics.Unknown() && - all(par[i] in ps for i in eachindex(par)) - push!(new_ps, par) - else - push!(new_ps, p) - end - else - push!(new_ps, p) - end - end - algevars = setdiff(allunknowns, diffvars) - - consvars = OrderedSet() - constraintsystem = nothing - if !isempty(constraints) - constraintsystem = process_constraint_system(constraints, allunknowns, new_ps, iv) - for st in get_unknowns(constraintsystem) - iscall(st) ? - !in(operation(st)(iv), allunknowns) && push!(consvars, st) : - !in(st, allunknowns) && push!(consvars, st) - end - for p in parameters(constraintsystem) - !in(p, new_ps) && push!(new_ps, p) - end - end - - if !isempty(costs) - coststs, costps = process_costs(costs, allunknowns, new_ps, iv) - for p in costps - !in(p, new_ps) && push!(new_ps, p) - end - end - costs = wrap.(costs) - - return ODESystem(eqs, iv, collect(Iterators.flatten((diffvars, algevars, consvars))), - collect(new_ps); constraintsystem, costs, kwargs...) -end - -# NOTE: equality does not check cached Jacobian -function Base.:(==)(sys1::ODESystem, sys2::ODESystem) - sys1 === sys2 && return true - iv1 = get_iv(sys1) - iv2 = get_iv(sys2) - isequal(iv1, iv2) && - isequal(nameof(sys1), nameof(sys2)) && - _eq_unordered(get_eqs(sys1), get_eqs(sys2)) && - _eq_unordered(get_unknowns(sys1), get_unknowns(sys2)) && - _eq_unordered(get_ps(sys1), get_ps(sys2)) && - _eq_unordered(continuous_events(sys1), continuous_events(sys2)) && - _eq_unordered(discrete_events(sys1), discrete_events(sys2)) && - all(s1 == s2 for (s1, s2) in zip(get_systems(sys1), get_systems(sys2))) && - isequal(get_constraintsystem(sys1), get_constraintsystem(sys2)) && - _eq_unordered(get_costs(sys1), get_costs(sys2)) -end - -function flatten(sys::ODESystem, noeqs = false) - systems = get_systems(sys) - if isempty(systems) - return sys - else - return ODESystem(noeqs ? Equation[] : equations(sys), - get_iv(sys), - unknowns(sys), - parameters(sys; initial_parameters = true), - parameter_dependencies = parameter_dependencies(sys), - guesses = guesses(sys), - observed = observed(sys), - continuous_events = continuous_events(sys), - discrete_events = discrete_events(sys), - defaults = defaults(sys), - name = nameof(sys), - description = description(sys), - initialization_eqs = initialization_equations(sys), - assertions = assertions(sys), - is_dde = is_dde(sys), - tstops = symbolic_tstops(sys), - metadata = get_metadata(sys), - checks = false, - # without this, any defaults/guesses obtained from metadata that were - # later removed by the user will be re-added. Right now, we just want to - # retain `defaults(sys)` as-is. - discover_from_metadata = false) - end -end - -ODESystem(eq::Equation, args...; kwargs...) = ODESystem([eq], args...; kwargs...) - -""" - build_explicit_observed_function(sys, ts; kwargs...) -> Function(s) - -Generates a function that computes the observed value(s) `ts` in the system `sys`, while making the assumption that there are no cycles in the equations. - -## Arguments -- `sys`: The system for which to generate the function -- `ts`: The symbolic observed values whose value should be computed - -## Keywords -- `return_inplace = false`: If true and the observed value is a vector, then return both the in place and out of place methods. -- `expression = false`: Generates a Julia `Expr`` computing the observed value if `expression` is true -- `eval_expression = false`: If true and `expression = false`, evaluates the returned function in the module `eval_module` -- `output_type = Array` the type of the array generated by a out-of-place vector-valued function -- `param_only = false` if true, only allow the generated function to access system parameters -- `inputs = nothing` additinoal symbolic variables that should be provided to the generated function -- `checkbounds = true` checks bounds if true when destructuring parameters -- `op = Operator` sets the recursion terminator for the walk done by `vars` to identify the variables that appear in `ts`. See the documentation for `vars` for more detail. -- `throw = true` if true, throw an error when generating a function for `ts` that reference variables that do not exist. -- `mkarray`: only used if the output is an array (that is, `!isscalar(ts)` and `ts` is not a tuple, in which case the result will always be a tuple). Called as `mkarray(ts, output_type)` where `ts` are the expressions to put in the array and `output_type` is the argument of the same name passed to build_explicit_observed_function. -- `cse = true`: Whether to use Common Subexpression Elimination (CSE) to generate a more efficient function. - -## Returns - -The return value will be either: -* a single function `f_oop` if the input is a scalar or if the input is a Vector but `return_inplace` is false -* the out of place and in-place functions `(f_ip, f_oop)` if `return_inplace` is true and the input is a `Vector` - -The function(s) `f_oop` (and potentially `f_ip`) will be: -* `RuntimeGeneratedFunction`s by default, -* A Julia `Expr` if `expression` is true, -* A directly evaluated Julia function in the module `eval_module` if `eval_expression` is true and `expression` is false. - -The signatures will be of the form `g(...)` with arguments: - -- `output` for in-place functions -- `unknowns` if `param_only` is `false` -- `inputs` if `inputs` is an array of symbolic inputs that should be available in `ts` -- `p...` unconditionally; note that in the case of `MTKParameters` more than one parameters argument may be present, so it must be splatted -- `t` if the system is time-dependent; for example `NonlinearSystem` will not have `t` - -For example, a function `g(op, unknowns, p..., inputs, t)` will be the in-place function generated if `return_inplace` is true, `ts` is a vector, -an array of inputs `inputs` is given, and `param_only` is false for a time-dependent system. -""" -function build_explicit_observed_function(sys, ts; - inputs = nothing, - disturbance_inputs = nothing, - disturbance_argument = false, - expression = false, - eval_expression = false, - eval_module = @__MODULE__, - output_type = Array, - checkbounds = true, - ps = parameters(sys; initial_parameters = true), - return_inplace = false, - param_only = false, - op = Operator, - throw = true, - cse = true, - mkarray = nothing) - is_tuple = ts isa Tuple - if is_tuple - ts = collect(ts) - output_type = Tuple - end - - allsyms = all_symbols(sys) - if symbolic_type(ts) == NotSymbolic() && ts isa AbstractArray - ts = map(x -> symbol_to_symbolic(sys, x; allsyms), ts) - else - ts = symbol_to_symbolic(sys, ts; allsyms) - end - - vs = ModelingToolkit.vars(ts; op) - namespace_subs = Dict() - ns_map = Dict{Any, Any}(renamespace(sys, obs) => obs for obs in observables(sys)) - for sym in unknowns(sys) - ns_map[renamespace(sys, sym)] = sym - if iscall(sym) && operation(sym) === getindex - ns_map[renamespace(sys, arguments(sym)[1])] = arguments(sym)[1] - end - end - for sym in full_parameters(sys) - ns_map[renamespace(sys, sym)] = sym - if iscall(sym) && operation(sym) === getindex - ns_map[renamespace(sys, arguments(sym)[1])] = arguments(sym)[1] - end - end - allsyms = Set(all_symbols(sys)) - iv = has_iv(sys) ? get_iv(sys) : nothing - for var in vs - var = unwrap(var) - newvar = get(ns_map, var, nothing) - if newvar !== nothing - namespace_subs[var] = newvar - var = newvar - end - if throw && !var_in_varlist(var, allsyms, iv) - Base.throw(ArgumentError("Symbol $var is not present in the system.")) - end - end - ts = fast_substitute(ts, namespace_subs) - - obsfilter = if param_only - if is_split(sys) - let ic = get_index_cache(sys) - eq -> !(ContinuousTimeseries() in ic.observed_syms_to_timeseries[eq.lhs]) - end - else - Returns(false) - end - else - Returns(true) - end - dvs = if param_only - () - else - (unknowns(sys),) - end - if inputs === nothing - inputs = () - else - ps = setdiff(ps, inputs) # Inputs have been converted to parameters by io_preprocessing, remove those from the parameter list - inputs = (inputs,) - end - if disturbance_inputs !== nothing - # Disturbance inputs may or may not be included as inputs, depending on disturbance_argument - ps = setdiff(ps, disturbance_inputs) - end - if disturbance_argument - disturbance_inputs = (disturbance_inputs,) - else - disturbance_inputs = () - end - ps = reorder_parameters(sys, ps) - iv = if is_time_dependent(sys) - (get_iv(sys),) - else - () - end - args = (dvs..., inputs..., ps..., iv..., disturbance_inputs...) - p_start = length(dvs) + length(inputs) + 1 - p_end = length(dvs) + length(inputs) + length(ps) - fns = build_function_wrapper( - sys, ts, args...; p_start, p_end, filter_observed = obsfilter, - output_type, mkarray, try_namespaced = true, expression = Val{true}, cse) - if fns isa Tuple - if expression - return return_inplace ? fns : fns[1] - end - oop, iip = eval_or_rgf.(fns; eval_expression, eval_module) - f = GeneratedFunctionWrapper{( - p_start + is_dde(sys), length(args) - length(ps) + 1 + is_dde(sys), is_split(sys))}( - oop, iip) - return return_inplace ? (f, f) : f - else - if expression - return fns - end - f = eval_or_rgf(fns; eval_expression, eval_module) - f = GeneratedFunctionWrapper{( - p_start + is_dde(sys), length(args) - length(ps) + 1 + is_dde(sys), is_split(sys))}( - f, nothing) - return f - end -end - -function populate_delays(delays::Set, obsexprs, histfn, sys, sym) - _vars_util = vars(sym) - for v in _vars_util - v in delays && continue - iscall(v) && issym(operation(v)) && (args = arguments(v); length(args) == 1) && - iscall(only(args)) || continue - - idx = variable_index(sys, operation(v)(get_iv(sys))) - idx === nothing && error("Delay term $v is not an unknown in the system") - push!(delays, v) - push!(obsexprs, v ← histfn(only(args))[idx]) - end -end - -function _eq_unordered(a, b) - # a and b may be multidimensional - # e.g. comparing noiseeqs of SDESystem - a = vec(a) - b = vec(b) - length(a) === length(b) || return false - n = length(a) - idxs = Set(1:n) - for x in a - idx = findfirst(isequal(x), b) - # loop since there might be multiple identical entries in a/b - # and while we might have already matched the first there could - # be a second that is equal to x - while idx !== nothing && !(idx in idxs) - idx = findnext(isequal(x), b, idx + 1) - end - idx === nothing && return false - delete!(idxs, idx) - end - return true -end - -# We have a stand-alone function to convert a `NonlinearSystem` or `ODESystem` -# to an `ODESystem` to connect systems, and we later can reply on -# `structural_simplify` to convert `ODESystem`s to `NonlinearSystem`s. -""" -$(TYPEDSIGNATURES) - -Convert a `NonlinearSystem` to an `ODESystem` or converts an `ODESystem` to a -new `ODESystem` with a different independent variable. -""" -function convert_system(::Type{<:ODESystem}, sys, t; name = nameof(sys)) - isempty(observed(sys)) || - throw(ArgumentError("`convert_system` cannot handle reduced model (i.e. observed(sys) is non-empty).")) - t = value(t) - varmap = Dict() - sts = unknowns(sys) - newsts = similar(sts, Any) - for (i, s) in enumerate(sts) - if iscall(s) - args = arguments(s) - length(args) == 1 || - throw(InvalidSystemException("Illegal unknown: $s. The unknown can have at most one argument like `x(t)`.")) - arg = args[1] - if isequal(arg, t) - newsts[i] = s - continue - end - ns = maketerm(typeof(s), operation(s), Any[t], - SymbolicUtils.metadata(s)) - newsts[i] = ns - varmap[s] = ns - else - ns = variable(getname(s); T = FnType)(t) - newsts[i] = ns - varmap[s] = ns - end - end - sub = Base.Fix2(substitute, varmap) - if sys isa AbstractODESystem - iv = only(independent_variables(sys)) - sub.x[iv] = t # otherwise the Differentials aren't fixed - end - neweqs = map(sub, equations(sys)) - defs = Dict(sub(k) => sub(v) for (k, v) in defaults(sys)) - return ODESystem(neweqs, t, newsts, parameters(sys); defaults = defs, name = name, - checks = false) -end - -""" -$(SIGNATURES) - -Add accumulation variables for `vars`. -""" -function add_accumulations(sys::ODESystem, vars = unknowns(sys)) - avars = [rename(v, Symbol(:accumulation_, getname(v))) for v in vars] - add_accumulations(sys, avars .=> vars) -end - -""" -$(SIGNATURES) - -Add accumulation variables for `vars`. `vars` is a vector of pairs in the form -of - -```julia -[cumulative_var1 => x + y, cumulative_var2 => x^2] -``` -Then, cumulative variables `cumulative_var1` and `cumulative_var2` that computes -the cumulative `x + y` and `x^2` would be added to `sys`. -""" -function add_accumulations(sys::ODESystem, vars::Vector{<:Pair}) - eqs = get_eqs(sys) - avars = map(first, vars) - if (ints = intersect(avars, unknowns(sys)); !isempty(ints)) - error("$ints already exist in the system!") - end - D = Differential(get_iv(sys)) - @set! sys.eqs = [eqs; Equation[D(a) ~ v[2] for (a, v) in zip(avars, vars)]] - @set! sys.unknowns = [get_unknowns(sys); avars] - @set! sys.defaults = merge(get_defaults(sys), Dict(a => 0.0 for a in avars)) -end - -function Base.show(io::IO, mime::MIME"text/plain", sys::ODESystem; hint = true, bold = true) - # Print general AbstractSystem information - invoke(Base.show, Tuple{typeof(io), typeof(mime), AbstractSystem}, - io, mime, sys; hint, bold) - - name = nameof(sys) - - # Print initialization equations (unique to ODESystems) - nini = length(initialization_equations(sys)) - nini > 0 && printstyled(io, "\nInitialization equations ($nini):"; bold) - nini > 0 && hint && print(io, " see initialization_equations($name)") - - return nothing -end From b8a00b5b539d7859b7ea8e396e562e81211fb13b Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Sun, 20 Apr 2025 14:00:59 +0530 Subject: [PATCH 045/185] refactor: add `_eq_unordered` to `utils.jl` --- src/utils.jl | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/src/utils.jl b/src/utils.jl index 2b3cbedab0..ec58de13b5 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -1297,3 +1297,32 @@ function var_in_varlist(var, varlist::AbstractSet, iv) # delayed variables (isdelay(var, iv) && var_in_varlist(operation(var)(iv), varlist, iv)) end + +""" + $(TYPEDSIGNATURES) + +Check if `a` and `b` contain identical elements, regardless of order. This is not +equivalent to `issetequal` because the latter does not account for identical elements that +have different multiplicities in `a` and `b`. +""" +function _eq_unordered(a::AbstractArray, b::AbstractArray) + # a and b may be multidimensional + # e.g. comparing noiseeqs of SDESystem + a = vec(a) + b = vec(b) + length(a) === length(b) || return false + n = length(a) + idxs = Set(1:n) + for x in a + idx = findfirst(isequal(x), b) + # loop since there might be multiple identical entries in a/b + # and while we might have already matched the first there could + # be a second that is equal to x + while idx !== nothing && !(idx in idxs) + idx = findnext(isequal(x), b, idx + 1) + end + idx === nothing && return false + delete!(idxs, idx) + end + return true +end From bf725074f404573cf66bfee42134bbaf70b4eeda Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Sun, 20 Apr 2025 14:01:12 +0530 Subject: [PATCH 046/185] refactor: port `build_explicit_observed_function` to `codegen.jl` --- src/systems/codegen.jl | 168 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 168 insertions(+) diff --git a/src/systems/codegen.jl b/src/systems/codegen.jl index 1a66f2e457..0e8833f677 100644 --- a/src/systems/codegen.jl +++ b/src/systems/codegen.jl @@ -544,3 +544,171 @@ function assemble_maj(majv::Vector{U}, unknowntoid, pmapper) where {U <: MassAct ns = [numericnstoich(maj.net_stoch, unknowntoid) for maj in majv] MassActionJump(rs, ns; param_mapper = pmapper, nocopy = true) end + +""" + build_explicit_observed_function(sys, ts; kwargs...) -> Function(s) + +Generates a function that computes the observed value(s) `ts` in the system `sys`, while making the assumption that there are no cycles in the equations. + +## Arguments +- `sys`: The system for which to generate the function +- `ts`: The symbolic observed values whose value should be computed + +## Keywords +- `return_inplace = false`: If true and the observed value is a vector, then return both the in place and out of place methods. +- `expression = false`: Generates a Julia `Expr`` computing the observed value if `expression` is true +- `eval_expression = false`: If true and `expression = false`, evaluates the returned function in the module `eval_module` +- `output_type = Array` the type of the array generated by a out-of-place vector-valued function +- `param_only = false` if true, only allow the generated function to access system parameters +- `inputs = nothing` additinoal symbolic variables that should be provided to the generated function +- `checkbounds = true` checks bounds if true when destructuring parameters +- `op = Operator` sets the recursion terminator for the walk done by `vars` to identify the variables that appear in `ts`. See the documentation for `vars` for more detail. +- `throw = true` if true, throw an error when generating a function for `ts` that reference variables that do not exist. +- `mkarray`: only used if the output is an array (that is, `!isscalar(ts)` and `ts` is not a tuple, in which case the result will always be a tuple). Called as `mkarray(ts, output_type)` where `ts` are the expressions to put in the array and `output_type` is the argument of the same name passed to build_explicit_observed_function. +- `cse = true`: Whether to use Common Subexpression Elimination (CSE) to generate a more efficient function. + +## Returns + +The return value will be either: +* a single function `f_oop` if the input is a scalar or if the input is a Vector but `return_inplace` is false +* the out of place and in-place functions `(f_ip, f_oop)` if `return_inplace` is true and the input is a `Vector` + +The function(s) `f_oop` (and potentially `f_ip`) will be: +* `RuntimeGeneratedFunction`s by default, +* A Julia `Expr` if `expression` is true, +* A directly evaluated Julia function in the module `eval_module` if `eval_expression` is true and `expression` is false. + +The signatures will be of the form `g(...)` with arguments: + +- `output` for in-place functions +- `unknowns` if `param_only` is `false` +- `inputs` if `inputs` is an array of symbolic inputs that should be available in `ts` +- `p...` unconditionally; note that in the case of `MTKParameters` more than one parameters argument may be present, so it must be splatted +- `t` if the system is time-dependent; for example `NonlinearSystem` will not have `t` + +For example, a function `g(op, unknowns, p..., inputs, t)` will be the in-place function generated if `return_inplace` is true, `ts` is a vector, +an array of inputs `inputs` is given, and `param_only` is false for a time-dependent system. +""" +function build_explicit_observed_function(sys, ts; + inputs = nothing, + disturbance_inputs = nothing, + disturbance_argument = false, + expression = false, + eval_expression = false, + eval_module = @__MODULE__, + output_type = Array, + checkbounds = true, + ps = parameters(sys; initial_parameters = true), + return_inplace = false, + param_only = false, + op = Operator, + throw = true, + cse = true, + mkarray = nothing) + # TODO: cleanup + is_tuple = ts isa Tuple + if is_tuple + ts = collect(ts) + output_type = Tuple + end + + allsyms = all_symbols(sys) + if symbolic_type(ts) == NotSymbolic() && ts isa AbstractArray + ts = map(x -> symbol_to_symbolic(sys, x; allsyms), ts) + else + ts = symbol_to_symbolic(sys, ts; allsyms) + end + + vs = ModelingToolkit.vars(ts; op) + namespace_subs = Dict() + ns_map = Dict{Any, Any}(renamespace(sys, eq.lhs) => eq.lhs for eq in observed(sys)) + for sym in unknowns(sys) + ns_map[renamespace(sys, sym)] = sym + if iscall(sym) && operation(sym) === getindex + ns_map[renamespace(sys, arguments(sym)[1])] = arguments(sym)[1] + end + end + for sym in full_parameters(sys) + ns_map[renamespace(sys, sym)] = sym + if iscall(sym) && operation(sym) === getindex + ns_map[renamespace(sys, arguments(sym)[1])] = arguments(sym)[1] + end + end + allsyms = Set(all_symbols(sys)) + iv = has_iv(sys) ? get_iv(sys) : nothing + for var in vs + var = unwrap(var) + newvar = get(ns_map, var, nothing) + if newvar !== nothing + namespace_subs[var] = newvar + var = newvar + end + if throw && !var_in_varlist(var, allsyms, iv) + Base.throw(ArgumentError("Symbol $var is not present in the system.")) + end + end + ts = fast_substitute(ts, namespace_subs) + + obsfilter = if param_only + if is_split(sys) + let ic = get_index_cache(sys) + eq -> !(ContinuousTimeseries() in ic.observed_syms_to_timeseries[eq.lhs]) + end + else + Returns(false) + end + else + Returns(true) + end + dvs = if param_only + () + else + (unknowns(sys),) + end + if inputs === nothing + inputs = () + else + ps = setdiff(ps, inputs) # Inputs have been converted to parameters by io_preprocessing, remove those from the parameter list + inputs = (inputs,) + end + if disturbance_inputs !== nothing + # Disturbance inputs may or may not be included as inputs, depending on disturbance_argument + ps = setdiff(ps, disturbance_inputs) + end + if disturbance_argument + disturbance_inputs = (disturbance_inputs,) + else + disturbance_inputs = () + end + ps = reorder_parameters(sys, ps) + iv = if is_time_dependent(sys) + (get_iv(sys),) + else + () + end + args = (dvs..., inputs..., ps..., iv..., disturbance_inputs...) + p_start = length(dvs) + length(inputs) + 1 + p_end = length(dvs) + length(inputs) + length(ps) + fns = build_function_wrapper( + sys, ts, args...; p_start, p_end, filter_observed = obsfilter, + output_type, mkarray, try_namespaced = true, expression = Val{true}, cse) + if fns isa Tuple + if expression + return return_inplace ? fns : fns[1] + end + oop, iip = eval_or_rgf.(fns; eval_expression, eval_module) + f = GeneratedFunctionWrapper{( + p_start + is_dde(sys), length(args) - length(ps) + 1 + is_dde(sys), is_split(sys))}( + oop, iip) + return return_inplace ? (f, f) : f + else + if expression + return fns + end + f = eval_or_rgf(fns; eval_expression, eval_module) + f = GeneratedFunctionWrapper{( + p_start + is_dde(sys), length(args) - length(ps) + 1 + is_dde(sys), is_split(sys))}( + f, nothing) + return f + end +end From cd4edc4553cf9d98eb7136219ba652f7e5f16cac Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Sun, 20 Apr 2025 15:21:23 +0530 Subject: [PATCH 047/185] refactor: remove `sdesystem.jl` --- src/ModelingToolkit.jl | 3 +- src/systems/diffeqs/sdesystem.jl | 931 ------------------------------- 2 files changed, 1 insertion(+), 933 deletions(-) delete mode 100644 src/systems/diffeqs/sdesystem.jl diff --git a/src/ModelingToolkit.jl b/src/ModelingToolkit.jl index eccf3026b2..94a44e6dfc 100644 --- a/src/ModelingToolkit.jl +++ b/src/ModelingToolkit.jl @@ -168,7 +168,6 @@ include("systems/optimization/modelingtoolkitize.jl") include("systems/nonlinear/nonlinearsystem.jl") include("systems/nonlinear/homotopy_continuation.jl") -include("systems/diffeqs/sdesystem.jl") include("systems/diffeqs/abstractodesystem.jl") include("systems/nonlinear/modelingtoolkitize.jl") include("systems/nonlinear/initializesystem.jl") @@ -263,7 +262,7 @@ export AbstractTimeDependentSystem, export ODEFunction, ODEFunctionExpr, ODEProblemExpr, convert_system, add_accumulations, System export DAEFunctionExpr, DAEProblemExpr -export SDESystem, SDEFunction, SDEFunctionExpr, SDEProblemExpr +export SDEFunction, SDEFunctionExpr, SDEProblemExpr export SystemStructure export DiscreteSystem, DiscreteProblem, DiscreteFunction, DiscreteFunctionExpr export ImplicitDiscreteSystem, ImplicitDiscreteProblem, ImplicitDiscreteFunction, diff --git a/src/systems/diffeqs/sdesystem.jl b/src/systems/diffeqs/sdesystem.jl deleted file mode 100644 index c5299c28be..0000000000 --- a/src/systems/diffeqs/sdesystem.jl +++ /dev/null @@ -1,931 +0,0 @@ -""" -$(TYPEDEF) - -A system of stochastic differential equations. - -# Fields -$(FIELDS) - -# Example - -```julia -using ModelingToolkit -using ModelingToolkit: t_nounits as t, D_nounits as D - -@parameters σ ρ β -@variables x(t) y(t) z(t) - -eqs = [D(x) ~ σ*(y-x), - D(y) ~ x*(ρ-z)-y, - D(z) ~ x*y - β*z] - -noiseeqs = [0.1*x, - 0.1*y, - 0.1*z] - -@named de = SDESystem(eqs,noiseeqs,t,[x,y,z],[σ,ρ,β]; tspan = (0, 1000.0)) -``` -""" -struct SDESystem <: AbstractODESystem - """ - A tag for the system. If two systems have the same tag, then they are - structurally identical. - """ - tag::UInt - """The expressions defining the drift term.""" - eqs::Vector{Equation} - """The expressions defining the diffusion term.""" - noiseeqs::AbstractArray - """Independent variable.""" - iv::BasicSymbolic{Real} - """Dependent variables. Must not contain the independent variable.""" - unknowns::Vector - """Parameter variables. Must not contain the independent variable.""" - ps::Vector - """Time span.""" - tspan::Union{NTuple{2, Any}, Nothing} - """Array variables.""" - var_to_name::Any - """Control parameters (some subset of `ps`).""" - ctrls::Vector - """Observed equations.""" - observed::Vector{Equation} - """ - Time-derivative matrix. Note: this field will not be defined until - [`calculate_tgrad`](@ref) is called on the system. - """ - tgrad::RefValue - """ - Jacobian matrix. Note: this field will not be defined until - [`calculate_jacobian`](@ref) is called on the system. - """ - jac::RefValue - """ - Control Jacobian matrix. Note: this field will not be defined until - [`calculate_control_jacobian`](@ref) is called on the system. - """ - ctrl_jac::RefValue{Any} - """ - Note: this field will not be defined until - [`generate_factorized_W`](@ref) is called on the system. - """ - Wfact::RefValue - """ - Note: this field will not be defined until - [`generate_factorized_W`](@ref) is called on the system. - """ - Wfact_t::RefValue - """ - The name of the system. - """ - name::Symbol - """ - A description of the system. - """ - description::String - """ - The internal systems. These are required to have unique names. - """ - systems::Vector{SDESystem} - """ - The default values to use when initial conditions and/or - parameters are not supplied in `ODEProblem`. - """ - defaults::Dict - """ - The guesses to use as the initial conditions for the - initialization system. - """ - guesses::Dict - """ - The system for performing the initialization. - """ - initializesystem::Union{Nothing, NonlinearSystem} - """ - Extra equations to be enforced during the initialization sequence. - """ - initialization_eqs::Vector{Equation} - """ - Type of the system. - """ - connector_type::Any - """ - A `Vector{SymbolicContinuousCallback}` that model events. - The integrator will use root finding to guarantee that it steps at each zero crossing. - """ - continuous_events::Vector{SymbolicContinuousCallback} - """ - A `Vector{SymbolicDiscreteCallback}` that models events. Symbolic - analog to `SciMLBase.DiscreteCallback` that executes an affect when a given condition is - true at the end of an integration step. - """ - discrete_events::Vector{SymbolicDiscreteCallback} - """ - Topologically sorted parameter dependency equations, where all symbols are parameters and - the LHS is a single parameter. - """ - parameter_dependencies::Vector{Equation} - """ - Mapping of conditions which should be true throughout the solution process to corresponding error - messages. These will be added to the equations when calling `debug_system`. - """ - assertions::Dict{BasicSymbolic, String} - """ - Metadata for the system, to be used by downstream packages. - """ - metadata::Any - """ - Metadata for MTK GUI. - """ - gui_metadata::Union{Nothing, GUIMetadata} - """ - If false, then `sys.x` no longer performs namespacing. - """ - namespacing::Bool - """ - If true, denotes the model will not be modified any further. - """ - complete::Bool - """ - Cached data for fast symbolic indexing. - """ - index_cache::Union{Nothing, IndexCache} - """ - The hierarchical parent system before simplification. - """ - parent::Any - """ - Signal for whether the noise equations should be treated as a scalar process. This should only - be `true` when `noiseeqs isa Vector`. - """ - is_scalar_noise::Bool - """ - A boolean indicating if the given `ODESystem` represents a system of DDEs. - """ - is_dde::Bool - isscheduled::Bool - tearing_state::Any - - function SDESystem(tag, deqs, neqs, iv, dvs, ps, tspan, var_to_name, ctrls, observed, - tgrad, jac, ctrl_jac, Wfact, Wfact_t, name, description, systems, defaults, - guesses, initializesystem, initialization_eqs, connector_type, - cevents, devents, parameter_dependencies, assertions = Dict{ - BasicSymbolic, Nothing}, - metadata = nothing, gui_metadata = nothing, namespacing = true, - complete = false, index_cache = nothing, parent = nothing, is_scalar_noise = false, - is_dde = false, - isscheduled = false, - tearing_state = nothing; - checks::Union{Bool, Int} = true) - if checks == true || (checks & CheckComponents) > 0 - check_independent_variables([iv]) - check_variables(dvs, iv) - check_parameters(ps, iv) - check_equations(deqs, iv) - check_equations(neqs, dvs) - if size(neqs, 1) != length(deqs) - throw(ArgumentError("Noise equations ill-formed. Number of rows must match number of drift equations. size(neqs,1) = $(size(neqs,1)) != length(deqs) = $(length(deqs))")) - end - check_equations(equations(cevents), iv) - if is_scalar_noise && neqs isa AbstractMatrix - throw(ArgumentError("Noise equations ill-formed. Received a matrix of noise equations of size $(size(neqs)), but `is_scalar_noise` was set to `true`. Scalar noise is only compatible with an `AbstractVector` of noise equations.")) - end - check_subsystems(systems) - end - if checks == true || (checks & CheckUnits) > 0 - u = __get_unit_type(dvs, ps, iv) - check_units(u, deqs, neqs) - end - new(tag, deqs, neqs, iv, dvs, ps, tspan, var_to_name, ctrls, observed, tgrad, jac, - ctrl_jac, Wfact, Wfact_t, name, description, systems, - defaults, guesses, initializesystem, initialization_eqs, connector_type, cevents, - devents, parameter_dependencies, assertions, metadata, gui_metadata, namespacing, - complete, index_cache, parent, is_scalar_noise, is_dde, isscheduled, tearing_state) - end -end - -function SDESystem(deqs::AbstractVector{<:Equation}, neqs::AbstractArray, iv, dvs, ps; - controls = Num[], - observed = Num[], - systems = SDESystem[], - tspan = nothing, - default_u0 = Dict(), - default_p = Dict(), - defaults = _merge(Dict(default_u0), Dict(default_p)), - guesses = Dict(), - initializesystem = nothing, - initialization_eqs = Equation[], - name = nothing, - description = "", - connector_type = nothing, - checks = true, - continuous_events = nothing, - discrete_events = nothing, - parameter_dependencies = Equation[], - assertions = Dict{BasicSymbolic, String}(), - metadata = nothing, - gui_metadata = nothing, - index_cache = nothing, - parent = nothing, - is_scalar_noise = false, - is_dde = nothing) - name === nothing && - throw(ArgumentError("The `name` keyword must be provided. Please consider using the `@named` macro")) - iv′ = value(iv) - dvs′ = value.(dvs) - ps′ = value.(ps) - ctrl′ = value.(controls) - parameter_dependencies, ps′ = process_parameter_dependencies( - parameter_dependencies, ps′) - - sysnames = nameof.(systems) - if length(unique(sysnames)) != length(sysnames) - throw(ArgumentError("System names must be unique.")) - end - if !(isempty(default_u0) && isempty(default_p)) - Base.depwarn( - "`default_u0` and `default_p` are deprecated. Use `defaults` instead.", - :SDESystem, force = true) - end - - defaults = Dict{Any, Any}(todict(defaults)) - guesses = Dict{Any, Any}(todict(guesses)) - var_to_name = Dict() - process_variables!(var_to_name, defaults, guesses, dvs′) - process_variables!(var_to_name, defaults, guesses, ps′) - process_variables!( - var_to_name, defaults, guesses, [eq.lhs for eq in parameter_dependencies]) - process_variables!( - var_to_name, defaults, guesses, [eq.rhs for eq in parameter_dependencies]) - defaults = Dict{Any, Any}(value(k) => value(v) - for (k, v) in pairs(defaults) if v !== nothing) - guesses = Dict{Any, Any}(value(k) => value(v) - for (k, v) in pairs(guesses) if v !== nothing) - - isempty(observed) || collect_var_to_name!(var_to_name, (eq.lhs for eq in observed)) - - tgrad = RefValue(EMPTY_TGRAD) - jac = RefValue{Any}(EMPTY_JAC) - ctrl_jac = RefValue{Any}(EMPTY_JAC) - Wfact = RefValue(EMPTY_JAC) - Wfact_t = RefValue(EMPTY_JAC) - cont_callbacks = SymbolicContinuousCallbacks(continuous_events) - disc_callbacks = SymbolicDiscreteCallbacks(discrete_events) - if is_dde === nothing - is_dde = _check_if_dde(deqs, iv′, systems) - end - assertions = Dict{BasicSymbolic, Any}(unwrap(k) => v for (k, v) in assertions) - SDESystem(Threads.atomic_add!(SYSTEM_COUNT, UInt(1)), - deqs, neqs, iv′, dvs′, ps′, tspan, var_to_name, ctrl′, observed, tgrad, jac, - ctrl_jac, Wfact, Wfact_t, name, description, systems, defaults, guesses, - initializesystem, initialization_eqs, connector_type, - cont_callbacks, disc_callbacks, parameter_dependencies, assertions, metadata, gui_metadata, - true, false, index_cache, parent, is_scalar_noise, is_dde; checks = checks) -end - -function SDESystem(sys::ODESystem, neqs; kwargs...) - SDESystem(equations(sys), neqs, get_iv(sys), unknowns(sys), parameters(sys); kwargs...) -end - -function SDESystem(eqs::Vector{Equation}, noiseeqs::AbstractArray, iv; kwargs...) - diffvars, allunknowns, ps, eqs = process_equations(eqs, iv) - - for eq in get(kwargs, :parameter_dependencies, Equation[]) - collect_vars!(allunknowns, ps, eq, iv) - end - - for ssys in get(kwargs, :systems, ODESystem[]) - collect_scoped_vars!(allunknowns, ps, ssys, iv) - end - - for v in allunknowns - isdelay(v, iv) || continue - collect_vars!(allunknowns, ps, arguments(v)[1], iv) - end - - new_ps = OrderedSet() - for p in ps - if iscall(p) && operation(p) === getindex - par = arguments(p)[begin] - if Symbolics.shape(Symbolics.unwrap(par)) !== Symbolics.Unknown() && - all(par[i] in ps for i in eachindex(par)) - push!(new_ps, par) - else - push!(new_ps, p) - end - else - push!(new_ps, p) - end - end - - # validate noise equations - noisedvs = OrderedSet() - noiseps = OrderedSet() - collect_vars!(noisedvs, noiseps, noiseeqs, iv) - for dv in noisedvs - dv ∈ allunknowns || - throw(ArgumentError("Variable $dv in noise equations is not an unknown of the system.")) - end - algevars = setdiff(allunknowns, diffvars) - return SDESystem(eqs, noiseeqs, iv, Iterators.flatten((diffvars, algevars)), - [collect(ps); collect(noiseps)]; kwargs...) -end - -function SDESystem(eq::Equation, noiseeqs::AbstractArray, args...; kwargs...) - SDESystem([eq], noiseeqs, args...; kwargs...) -end -function SDESystem(eq::Equation, noiseeq, args...; kwargs...) - SDESystem([eq], [noiseeq], args...; kwargs...) -end - -function Base.:(==)(sys1::SDESystem, sys2::SDESystem) - sys1 === sys2 && return true - iv1 = get_iv(sys1) - iv2 = get_iv(sys2) - isequal(iv1, iv2) && - isequal(nameof(sys1), nameof(sys2)) && - _eq_unordered(get_eqs(sys1), get_eqs(sys2)) && - _eq_unordered(get_noiseeqs(sys1), get_noiseeqs(sys2)) && - isequal(get_is_scalar_noise(sys1), get_is_scalar_noise(sys2)) && - _eq_unordered(get_unknowns(sys1), get_unknowns(sys2)) && - _eq_unordered(get_ps(sys1), get_ps(sys2)) && - _eq_unordered(continuous_events(sys1), continuous_events(sys2)) && - _eq_unordered(discrete_events(sys1), discrete_events(sys2)) && - all(s1 == s2 for (s1, s2) in zip(get_systems(sys1), get_systems(sys2))) -end - -""" - function ODESystem(sys::SDESystem) - -Convert an `SDESystem` to the equivalent `ODESystem` using `@brownian` variables instead -of noise equations. The returned system will not be `iscomplete` and will not have an -index cache, regardless of `iscomplete(sys)`. -""" -function ODESystem(sys::SDESystem) - neqs = get_noiseeqs(sys) - eqs = equations(sys) - is_scalar_noise = get_is_scalar_noise(sys) - nbrownian = if is_scalar_noise - length(neqs) - else - size(neqs, 2) - end - brownvars = map(1:nbrownian) do i - name = gensym(Symbol(:brown_, i)) - only(@brownian $name) - end - if is_scalar_noise - brownterms = reduce(+, neqs .* brownvars; init = 0) - neweqs = map(eqs) do eq - eq.lhs ~ eq.rhs + brownterms - end - else - if neqs isa AbstractVector - neqs = reshape(neqs, (length(neqs), 1)) - end - brownterms = neqs * brownvars - neweqs = map(eqs, brownterms) do eq, brown - eq.lhs ~ eq.rhs + brown - end - end - newsys = ODESystem(neweqs, get_iv(sys), unknowns(sys), parameters(sys); - parameter_dependencies = parameter_dependencies(sys), defaults = defaults(sys), - continuous_events = continuous_events(sys), discrete_events = discrete_events(sys), - assertions = assertions(sys), - name = nameof(sys), description = description(sys), metadata = get_metadata(sys)) - @set newsys.parent = sys -end - -function __num_isdiag_noise(mat) - for i in axes(mat, 1) - nnz = 0 - for j in axes(mat, 2) - if !isequal(mat[i, j], 0) - nnz += 1 - end - end - if nnz > 1 - return (false) - end - end - true -end -function __get_num_diag_noise(mat) - map(axes(mat, 1)) do i - for j in axes(mat, 2) - mij = mat[i, j] - if !isequal(mij, 0) - return mij - end - end - 0 - end -end - -function generate_diffusion_function(sys::SDESystem, dvs = unknowns(sys), - ps = parameters(sys; initial_parameters = true); isdde = false, kwargs...) - eqs = get_noiseeqs(sys) - p = reorder_parameters(sys, ps) - return build_function_wrapper(sys, eqs, dvs, p..., get_iv(sys); kwargs...) -end - -""" -$(TYPEDSIGNATURES) - -Choose correction_factor=-1//2 (1//2) to convert Ito -> Stratonovich (Stratonovich->Ito). -""" -function stochastic_integral_transform(sys::SDESystem, correction_factor) - name = nameof(sys) - # use the general interface - if typeof(get_noiseeqs(sys)) <: Vector - eqs = vcat([equations(sys)[i].lhs ~ get_noiseeqs(sys)[i] - for i in eachindex(unknowns(sys))]...) - de = ODESystem(eqs, get_iv(sys), unknowns(sys), parameters(sys), name = name, - checks = false) - - jac = calculate_jacobian(de, sparse = false, simplify = false) - ∇σσ′ = simplify.(jac * get_noiseeqs(sys)) - - deqs = vcat([equations(sys)[i].lhs ~ equations(sys)[i].rhs + - correction_factor * ∇σσ′[i] - for i in eachindex(unknowns(sys))]...) - else - dimunknowns, m = size(get_noiseeqs(sys)) - eqs = vcat([equations(sys)[i].lhs ~ get_noiseeqs(sys)[i] - for i in eachindex(unknowns(sys))]...) - de = ODESystem(eqs, get_iv(sys), unknowns(sys), parameters(sys), name = name, - checks = false) - - jac = calculate_jacobian(de, sparse = false, simplify = false) - ∇σσ′ = simplify.(jac * get_noiseeqs(sys)[:, 1]) - for k in 2:m - eqs = vcat([equations(sys)[i].lhs ~ get_noiseeqs(sys)[Int(i + - (k - 1) * - dimunknowns)] - for i in eachindex(unknowns(sys))]...) - de = ODESystem(eqs, get_iv(sys), unknowns(sys), parameters(sys), name = name, - checks = false) - - jac = calculate_jacobian(de, sparse = false, simplify = false) - ∇σσ′ = ∇σσ′ + simplify.(jac * get_noiseeqs(sys)[:, k]) - end - - deqs = vcat([equations(sys)[i].lhs ~ equations(sys)[i].rhs + - correction_factor * ∇σσ′[i] - for i in eachindex(unknowns(sys))]...) - end - - SDESystem(deqs, get_noiseeqs(sys), get_iv(sys), unknowns(sys), parameters(sys), - name = name, description = description(sys), - parameter_dependencies = parameter_dependencies(sys), checks = false) -end - -""" -$(TYPEDSIGNATURES) - -Measure transformation method that allows for a reduction in the variance of an estimator `Exp(g(X_t))`. -Input: Original SDE system and symbolic function `u(t,x)` with scalar output that - defines the adjustable parameters `d` in the Girsanov transformation. Optional: initial - condition for `θ0`. -Output: Modified SDESystem with additional component `θ_t` and initial value `θ0`, as well as - the weight `θ_t/θ0` as observed equation, such that the estimator `Exp(g(X_t)θ_t/θ0)` - has a smaller variance. - -Reference: -Kloeden, P. E., Platen, E., & Schurz, H. (2012). Numerical solution of SDE through computer -experiments. Springer Science & Business Media. - -# Example - -```julia -using ModelingToolkit -using ModelingToolkit: t_nounits as t, D_nounits as D - -@parameters α β -@variables x(t) y(t) z(t) - -eqs = [D(x) ~ α*x] -noiseeqs = [β*x] - -@named de = SDESystem(eqs,noiseeqs,t,[x],[α,β]) - -# define u (user choice) -u = x -θ0 = 0.1 -g(x) = x[1]^2 -demod = ModelingToolkit.Girsanov_transform(de, u; θ0=0.1) - -u0modmap = [ - x => x0 -] - -parammap = [ - α => 1.5, - β => 1.0 -] - -probmod = SDEProblem(complete(demod),u0modmap,(0.0,1.0),parammap) -ensemble_probmod = EnsembleProblem(probmod; - output_func = (sol,i) -> (g(sol[x,end])*sol[demod.weight,end],false), - ) - -simmod = solve(ensemble_probmod,EM(),dt=dt,trajectories=numtraj) -``` - -""" -function Girsanov_transform(sys::SDESystem, u; θ0 = 1.0) - name = nameof(sys) - - # register new variable θ corresponding to 1D correction process θ(t) - t = get_iv(sys) - D = Differential(t) - @variables θ(t), weight(t) - - # determine the adjustable parameters `d` given `u` - # gradient of u with respect to unknowns - grad = Symbolics.gradient(u, unknowns(sys)) - - noiseeqs = get_noiseeqs(sys) - if noiseeqs isa Vector - d = simplify.(-(noiseeqs .* grad) / u) - drift_correction = noiseeqs .* d - else - d = simplify.(-noiseeqs * grad / u) - drift_correction = noiseeqs * d - end - - # transformation adds additional unknowns θ: newX = (X,θ) - # drift function for unknowns is modified - # θ has zero drift - deqs = vcat([equations(sys)[i].lhs ~ equations(sys)[i].rhs - drift_correction[i] - for i in eachindex(unknowns(sys))]...) - deqsθ = D(θ) ~ 0 - push!(deqs, deqsθ) - - # diffusion matrix is of size d x m (d unknowns, m noise), with diagonal noise represented as a d-dimensional vector - # for diagonal noise processes with m>1, the noise process will become non-diagonal; extra unknown component but no new noise process. - # new diffusion matrix is of size d+1 x M - # diffusion for state is unchanged - - noiseqsθ = θ * d - - if noiseeqs isa Vector - m = size(noiseeqs) - if m == 1 - push!(noiseeqs, noiseqsθ) - else - noiseeqs = [Array(Diagonal(noiseeqs)); noiseqsθ'] - end - else - noiseeqs = [Array(noiseeqs); noiseqsθ'] - end - - unknown_vars = [unknowns(sys); θ] - - # return modified SDE System - SDESystem(deqs, noiseeqs, get_iv(sys), unknown_vars, parameters(sys); - defaults = Dict(θ => θ0), observed = [weight ~ θ / θ0], - name = name, description = description(sys), - parameter_dependencies = parameter_dependencies(sys), - checks = false) -end - -function DiffEqBase.SDEFunction{iip, specialize}(sys::SDESystem, dvs = unknowns(sys), - ps = parameters(sys), - u0 = nothing; - version = nothing, tgrad = false, sparse = false, - jac = false, Wfact = false, eval_expression = false, - sparsity = false, analytic = nothing, - eval_module = @__MODULE__, - checkbounds = false, initialization_data = nothing, - cse = true, kwargs...) where {iip, specialize} - if !iscomplete(sys) - error("A completed `SDESystem` is required. Call `complete` or `structural_simplify` on the system before creating an `SDEFunction`") - end - dvs = scalarize.(dvs) - - f_gen = generate_function(sys, dvs, ps; expression = Val{true}, cse, kwargs...) - f_oop, f_iip = eval_or_rgf.(f_gen; eval_expression, eval_module) - g_gen = generate_diffusion_function(sys, dvs, ps; expression = Val{true}, - cse, kwargs...) - g_oop, g_iip = eval_or_rgf.(g_gen; eval_expression, eval_module) - - f = GeneratedFunctionWrapper{(2, 3, is_split(sys))}(f_oop, f_iip) - g = GeneratedFunctionWrapper{(2, 3, is_split(sys))}(g_oop, g_iip) - - if tgrad - tgrad_gen = generate_tgrad(sys, dvs, ps; expression = Val{true}, cse, - kwargs...) - tgrad_oop, tgrad_iip = eval_or_rgf.(tgrad_gen; eval_expression, eval_module) - _tgrad = GeneratedFunctionWrapper{(2, 3, is_split(sys))}(tgrad_oop, tgrad_iip) - else - _tgrad = nothing - end - - if jac - jac_gen = generate_jacobian(sys, dvs, ps; expression = Val{true}, - sparse = sparse, cse, kwargs...) - jac_oop, jac_iip = eval_or_rgf.(jac_gen; eval_expression, eval_module) - - _jac = GeneratedFunctionWrapper{(2, 3, is_split(sys))}(jac_oop, jac_iip) - else - _jac = nothing - end - - if Wfact - tmp_Wfact, tmp_Wfact_t = generate_factorized_W(sys, dvs, ps, true; - expression = Val{true}, cse, kwargs...) - Wfact_oop, Wfact_iip = eval_or_rgf.(tmp_Wfact; eval_expression, eval_module) - Wfact_oop_t, Wfact_iip_t = eval_or_rgf.(tmp_Wfact_t; eval_expression, eval_module) - - _Wfact = GeneratedFunctionWrapper{(2, 4, is_split(sys))}(Wfact_oop, Wfact_iip) - _Wfact_t = GeneratedFunctionWrapper{(2, 4, is_split(sys))}(Wfact_oop_t, Wfact_iip_t) - else - _Wfact, _Wfact_t = nothing, nothing - end - - M = calculate_massmatrix(sys) - if sparse - uElType = u0 === nothing ? Float64 : eltype(u0) - W_prototype = similar(W_sparsity(sys), uElType) - else - W_prototype = nothing - end - - _M = (u0 === nothing || M == I) ? M : ArrayInterface.restructure(u0 .* u0', M) - - observedfun = ObservedFunctionCache( - sys; eval_expression, eval_module, checkbounds = get(kwargs, :checkbounds, false), cse) - - SDEFunction{iip, specialize}(f, g; - sys = sys, - jac = _jac === nothing ? nothing : _jac, - tgrad = _tgrad === nothing ? nothing : _tgrad, - mass_matrix = _M, - jac_prototype = W_prototype, - observed = observedfun, - sparsity = sparsity ? W_sparsity(sys) : nothing, - analytic = analytic, - Wfact = _Wfact === nothing ? nothing : _Wfact, - Wfact_t = _Wfact_t === nothing ? nothing : _Wfact_t, - initialization_data) -end - -""" -```julia -DiffEqBase.SDEFunction{iip}(sys::SDESystem, dvs = sys.unknowns, ps = sys.ps; - version = nothing, tgrad = false, sparse = false, - jac = false, Wfact = false, kwargs...) where {iip} -``` - -Create an `SDEFunction` from the [`SDESystem`](@ref). The arguments `dvs` and `ps` -are used to set the order of the dependent variable and parameter vectors, -respectively. -""" -function DiffEqBase.SDEFunction(sys::SDESystem, args...; kwargs...) - SDEFunction{true}(sys, args...; kwargs...) -end - -function DiffEqBase.SDEFunction{true}(sys::SDESystem, args...; - kwargs...) - SDEFunction{true, SciMLBase.AutoSpecialize}(sys, args...; kwargs...) -end - -function DiffEqBase.SDEFunction{false}(sys::SDESystem, args...; - kwargs...) - SDEFunction{false, SciMLBase.FullSpecialize}(sys, args...; kwargs...) -end - -""" -```julia -DiffEqBase.SDEFunctionExpr{iip}(sys::AbstractODESystem, dvs = unknowns(sys), - ps = parameters(sys); - version = nothing, tgrad = false, - jac = false, Wfact = false, - skipzeros = true, fillzeros = true, - sparse = false, - kwargs...) where {iip} -``` - -Create a Julia expression for an `SDEFunction` from the [`SDESystem`](@ref). -The arguments `dvs` and `ps` are used to set the order of the dependent -variable and parameter vectors, respectively. -""" -struct SDEFunctionExpr{iip} end - -function SDEFunctionExpr{iip}(sys::SDESystem, dvs = unknowns(sys), - ps = parameters(sys), u0 = nothing; - version = nothing, tgrad = false, - jac = false, Wfact = false, - sparse = false, linenumbers = false, - kwargs...) where {iip} - if !iscomplete(sys) - error("A completed `SDESystem` is required. Call `complete` or `structural_simplify` on the system before creating an `SDEFunctionExpr`") - end - idx = iip ? 2 : 1 - f = generate_function(sys, dvs, ps; expression = Val{true}, kwargs...)[idx] - g = generate_diffusion_function(sys, dvs, ps; expression = Val{true}, kwargs...)[idx] - if tgrad - _tgrad = generate_tgrad(sys, dvs, ps; expression = Val{true}, kwargs...)[idx] - else - _tgrad = :nothing - end - - if jac - _jac = generate_jacobian(sys, dvs, ps; sparse = sparse, expression = Val{true}, - kwargs...)[idx] - else - _jac = :nothing - end - - M = calculate_massmatrix(sys) - _M = (u0 === nothing || M == I) ? M : ArrayInterface.restructure(u0 .* u0', M) - - if sparse - uElType = u0 === nothing ? Float64 : eltype(u0) - W_prototype = similar(W_sparsity(sys), uElType) - else - W_prototype = nothing - end - - if Wfact - tmp_Wfact, tmp_Wfact_t = generate_factorized_W( - sys, dvs, ps; expression = Val{true}, - kwargs...) - _Wfact = tmp_Wfact[idx] - _Wfact_t = tmp_Wfact_t[idx] - else - _Wfact, _Wfact_t = :nothing, :nothing - end - - ex = quote - f = $f - g = $g - tgrad = $_tgrad - jac = $_jac - W_prototype = $W_prototype - Wfact = $_Wfact - Wfact_t = $_Wfact_t - M = $_M - SDEFunction{$iip}(f, g, - jac = jac, - jac_prototype = W_prototype, - tgrad = tgrad, - Wfact = Wfact, - Wfact_t = Wfact_t, - mass_matrix = M) - end - !linenumbers ? Base.remove_linenums!(ex) : ex -end - -function SDEFunctionExpr(sys::SDESystem, args...; kwargs...) - SDEFunctionExpr{true}(sys, args...; kwargs...) -end - -function DiffEqBase.SDEProblem{iip, specialize}( - sys::SDESystem, u0map = [], tspan = get_tspan(sys), - parammap = DiffEqBase.NullParameters(); - sparsenoise = nothing, check_length = true, - callback = nothing, kwargs...) where {iip, specialize} - if !iscomplete(sys) - error("A completed `SDESystem` is required. Call `complete` or `structural_simplify` on the system before creating an `SDEProblem`") - end - - f, u0, p = process_SciMLProblem( - SDEFunction{iip, specialize}, sys, u0map, parammap; check_length, - t = tspan === nothing ? nothing : tspan[1], kwargs...) - cbs = process_events(sys; callback, kwargs...) - sparsenoise === nothing && (sparsenoise = get(kwargs, :sparse, false)) - - noiseeqs = get_noiseeqs(sys) - is_scalar_noise = get_is_scalar_noise(sys) - if noiseeqs isa AbstractVector - noise_rate_prototype = nothing - if is_scalar_noise - noise = WienerProcess(0.0, 0.0, 0.0) - else - noise = nothing - end - elseif sparsenoise - I, J, V = findnz(SparseArrays.sparse(noiseeqs)) - noise_rate_prototype = SparseArrays.sparse(I, J, zero(eltype(u0))) - noise = nothing - else - noise_rate_prototype = zeros(eltype(u0), size(noiseeqs)) - noise = nothing - end - - kwargs = filter_kwargs(kwargs) - - # Call `remake` so it runs initialization if it is trivial - return remake(SDEProblem{iip}(f, u0, tspan, p; callback = cbs, noise, - noise_rate_prototype = noise_rate_prototype, kwargs...)) -end - -function DiffEqBase.SDEProblem(sys::ODESystem, args...; kwargs...) - if any(ModelingToolkit.isbrownian, unknowns(sys)) - error("SDESystem constructed by defining Brownian variables with @brownian must be simplified by calling `structural_simplify` before a SDEProblem can be constructed.") - else - error("Cannot construct SDEProblem from a normal ODESystem.") - end -end - -""" -```julia -DiffEqBase.SDEProblem{iip}(sys::SDESystem, u0map, tspan, p = parammap; - version = nothing, tgrad = false, - jac = false, Wfact = false, - checkbounds = false, sparse = false, - sparsenoise = sparse, - skipzeros = true, fillzeros = true, - linenumbers = true, parallel = SerialForm(), - kwargs...) -``` - -Generates an SDEProblem from an SDESystem and allows for automatically -symbolically calculating numerical enhancements. -""" -function DiffEqBase.SDEProblem(sys::SDESystem, args...; kwargs...) - SDEProblem{true}(sys, args...; kwargs...) -end - -function DiffEqBase.SDEProblem(sys::SDESystem, - u0map::StaticArray, - args...; - kwargs...) - SDEProblem{false, SciMLBase.FullSpecialize}(sys, u0map, args...; kwargs...) -end - -function DiffEqBase.SDEProblem{true}(sys::SDESystem, args...; kwargs...) - SDEProblem{true, SciMLBase.AutoSpecialize}(sys, args...; kwargs...) -end - -function DiffEqBase.SDEProblem{false}(sys::SDESystem, args...; kwargs...) - SDEProblem{false, SciMLBase.FullSpecialize}(sys, args...; kwargs...) -end - -""" -```julia -DiffEqBase.SDEProblemExpr{iip}(sys::AbstractODESystem, u0map, tspan, - parammap = DiffEqBase.NullParameters(); - version = nothing, tgrad = false, - jac = false, Wfact = false, - checkbounds = false, sparse = false, - linenumbers = true, parallel = SerialForm(), - kwargs...) where {iip} -``` - -Generates a Julia expression for constructing an ODEProblem from an -ODESystem and allows for automatically symbolically calculating -numerical enhancements. -""" -struct SDEProblemExpr{iip} end - -function SDEProblemExpr{iip}(sys::SDESystem, u0map, tspan, - parammap = DiffEqBase.NullParameters(); - sparsenoise = nothing, check_length = true, - kwargs...) where {iip} - if !iscomplete(sys) - error("A completed `SDESystem` is required. Call `complete` or `structural_simplify` on the system before creating an `SDEProblemExpr`") - end - f, u0, p = process_SciMLProblem( - SDEFunctionExpr{iip}, sys, u0map, parammap; check_length, - kwargs...) - linenumbers = get(kwargs, :linenumbers, true) - sparsenoise === nothing && (sparsenoise = get(kwargs, :sparse, false)) - - noiseeqs = get_noiseeqs(sys) - is_scalar_noise = get_is_scalar_noise(sys) - if noiseeqs isa AbstractVector - noise_rate_prototype = nothing - if is_scalar_noise - noise = WienerProcess(0.0, 0.0, 0.0) - else - noise = nothing - end - elseif sparsenoise - I, J, V = findnz(SparseArrays.sparse(noiseeqs)) - noise_rate_prototype = SparseArrays.sparse(I, J, zero(eltype(u0))) - noise = nothing - else - T = u0 === nothing ? Float64 : eltype(u0) - noise_rate_prototype = zeros(T, size(get_noiseeqs(sys))) - noise = nothing - end - ex = quote - f = $f - u0 = $u0 - tspan = $tspan - p = $p - noise_rate_prototype = $noise_rate_prototype - noise = $noise - SDEProblem( - f, u0, tspan, p; noise_rate_prototype = noise_rate_prototype, noise = noise, - $(kwargs...)) - end - !linenumbers ? Base.remove_linenums!(ex) : ex -end - -function SDEProblemExpr(sys::SDESystem, args...; kwargs...) - SDEProblemExpr{true}(sys, args...; kwargs...) -end From 2aa44b32baa876618f373d66e04820da2e33b07d Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Sun, 20 Apr 2025 15:22:32 +0530 Subject: [PATCH 048/185] refactor: port `stochastic_integral_transform` and `Girsanov_transform` --- src/systems/diffeqs/basic_transformations.jl | 165 +++++++++++++++++++ 1 file changed, 165 insertions(+) diff --git a/src/systems/diffeqs/basic_transformations.jl b/src/systems/diffeqs/basic_transformations.jl index a08c83ffb6..5600239c57 100644 --- a/src/systems/diffeqs/basic_transformations.jl +++ b/src/systems/diffeqs/basic_transformations.jl @@ -196,3 +196,168 @@ function change_independent_variable( end return transform(sys) end + +""" +$(TYPEDSIGNATURES) + +Choose correction_factor=-1//2 (1//2) to convert Ito -> Stratonovich (Stratonovich->Ito). +""" +function stochastic_integral_transform(sys::System, correction_factor) + if !isempty(get_systems(sys)) + throw(ArgumentError("The system must be flattened.")) + end + if get_noise_eqs(sys) === nothing + throw(ArgumentError(""" + `$stochastic_integral_transform` expects a system with noise_eqs. If your \ + noise is specified using brownian variables, consider calling \ + `structural_simplify`. + """)) + end + name = nameof(sys) + noise_eqs = get_noise_eqs(sys) + eqs = equations(sys) + dvs = unknowns(sys) + ps = parameters(sys) + # use the general interface + if noise_eqs isa Vector + _eqs = reduce(vcat, [eqs[i].lhs ~ noise_eqs[i] for i in eachindex(dvs)]) + de = System(_eqs, get_iv(sys), dvs, ps, name = name, checks = false) + + jac = calculate_jacobian(de, sparse = false, simplify = false) + ∇σσ′ = simplify.(jac * noise_eqs) + else + dimunknowns, m = size(noise_eqs) + _eqs = reduce(vcat, [eqs[i].lhs ~ noise_eqs[i] for i in eachindex(dvs)]) + de = System(_eqs, get_iv(sys), dvs, ps, name = name, checks = false) + + jac = calculate_jacobian(de, sparse = false, simplify = false) + ∇σσ′ = simplify.(jac * noise_eqs[:, 1]) + for k in 2:m + __eqs = reduce(vcat, + [eqs[i].lhs ~ noise_eqs[Int(i + (k - 1) * dimunknowns)] + for i in eachindex(dvs)]) + de = System(__eqs, get_iv(sys), dvs, dvs, name = name, checks = false) + + jac = calculate_jacobian(de, sparse = false, simplify = false) + ∇σσ′ = ∇σσ′ + simplify.(jac * noise_eqs[:, k]) + end + end + deqs = reduce(vcat, + [eqs[i].lhs ~ eqs[i].rhs + correction_factor * ∇σσ′[i] for i in eachindex(dvs)]) + + return @set sys.eqs = deqs +end + +""" +$(TYPEDSIGNATURES) + +Measure transformation method that allows for a reduction in the variance of an estimator `Exp(g(X_t))`. +Input: Original SDE system and symbolic function `u(t,x)` with scalar output that + defines the adjustable parameters `d` in the Girsanov transformation. Optional: initial + condition for `θ0`. +Output: Modified SDESystem with additional component `θ_t` and initial value `θ0`, as well as + the weight `θ_t/θ0` as observed equation, such that the estimator `Exp(g(X_t)θ_t/θ0)` + has a smaller variance. + +Reference: +Kloeden, P. E., Platen, E., & Schurz, H. (2012). Numerical solution of SDE through computer +experiments. Springer Science & Business Media. + +# Example + +```julia +using ModelingToolkit +using ModelingToolkit: t_nounits as t, D_nounits as D + +@parameters α β +@variables x(t) y(t) z(t) + +eqs = [D(x) ~ α*x] +noiseeqs = [β*x] + +@named de = SDESystem(eqs,noiseeqs,t,[x],[α,β]) + +# define u (user choice) +u = x +θ0 = 0.1 +g(x) = x[1]^2 +demod = ModelingToolkit.Girsanov_transform(de, u; θ0=0.1) + +u0modmap = [ + x => x0 +] + +parammap = [ + α => 1.5, + β => 1.0 +] + +probmod = SDEProblem(complete(demod),u0modmap,(0.0,1.0),parammap) +ensemble_probmod = EnsembleProblem(probmod; + output_func = (sol,i) -> (g(sol[x,end])*sol[demod.weight,end],false), + ) + +simmod = solve(ensemble_probmod,EM(),dt=dt,trajectories=numtraj) +``` + +""" +function Girsanov_transform(sys::System, u; θ0 = 1.0) + name = nameof(sys) + + # register new variable θ corresponding to 1D correction process θ(t) + t = get_iv(sys) + D = Differential(t) + @variables θ(t), weight(t) + + # determine the adjustable parameters `d` given `u` + # gradient of u with respect to unknowns + grad = Symbolics.gradient(u, unknowns(sys)) + + noiseeqs = copy(get_noise_eqs(sys)) + if noiseeqs isa Vector + d = simplify.(-(noiseeqs .* grad) / u) + drift_correction = noiseeqs .* d + else + d = simplify.(-noiseeqs * grad / u) + drift_correction = noiseeqs * d + end + + eqs = equations(sys) + dvs = unknowns(sys) + # transformation adds additional unknowns θ: newX = (X,θ) + # drift function for unknowns is modified + # θ has zero drift + deqs = reduce( + vcat, [eqs[i].lhs ~ eqs[i].rhs - drift_correction[i] for i in eachindex(dvs)]) + deqsθ = D(θ) ~ 0 + push!(deqs, deqsθ) + + # diffusion matrix is of size d x m (d unknowns, m noise), with diagonal noise represented as a d-dimensional vector + # for diagonal noise processes with m>1, the noise process will become non-diagonal; extra unknown component but no new noise process. + # new diffusion matrix is of size d+1 x M + # diffusion for state is unchanged + + noiseqsθ = θ * d + + if noiseeqs isa Vector + m = size(noiseeqs) + if m == 1 + push!(noiseeqs, noiseqsθ) + else + noiseeqs = [Array(Diagonal(noiseeqs)); noiseqsθ'] + end + else + noiseeqs = [Array(noiseeqs); noiseqsθ'] + end + + unknown_vars = [dvs; θ] + + # return modified SDE System + @set! sys.eqs = deqs + @set! sys.noise_eqs = noiseeqs + @set! sys.unknowns = unknown_vars + get_defaults(sys)[θ] = θ0 + obs = observed(sys) + @set! sys.observed = [weight ~ θ / θ0; obs] + return sys +end From 4cc06d1d4207b939f37cbe9e5e036583a79eefe5 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Sun, 20 Apr 2025 20:18:52 +0530 Subject: [PATCH 049/185] refactor: move `__num_isdiag_noise` to location where it is used --- src/systems/systems.jl | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/systems/systems.jl b/src/systems/systems.jl index 273953936c..82a47d4f52 100644 --- a/src/systems/systems.jl +++ b/src/systems/systems.jl @@ -158,6 +158,21 @@ function __structural_simplify( end end +function __num_isdiag_noise(mat) + for i in axes(mat, 1) + nnz = 0 + for j in axes(mat, 2) + if !isequal(mat[i, j], 0) + nnz += 1 + end + end + if nnz > 1 + return (false) + end + end + true +end + """ $(TYPEDSIGNATURES) From a8ae8cff2d30e63ce5e41dae9e3649c0accaddf3 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Sun, 20 Apr 2025 20:21:30 +0530 Subject: [PATCH 050/185] refactor: move `shift_u0map_forward` to `discreteproblem.jl` --- src/problems/discreteproblem.jl | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/src/problems/discreteproblem.jl b/src/problems/discreteproblem.jl index 69b4f601fe..8f8f3ca3d1 100644 --- a/src/problems/discreteproblem.jl +++ b/src/problems/discreteproblem.jl @@ -59,3 +59,31 @@ function check_compatible_system( check_is_discrete(sys, T) check_is_explicit(sys, T, ImplicitDiscreteProblem) end + +function shift_u0map_forward(sys::System, u0map, defs) + iv = get_iv(sys) + updated = AnyDict() + for k in collect(keys(u0map)) + v = u0map[k] + if !((op = operation(k)) isa Shift) + isnothing(getunshifted(k)) && + error("Initial conditions must be for the past state of the unknowns. Instead of providing the condition for $k, provide the condition for $(Shift(iv, -1)(k)).") + + updated[Shift(iv, 1)(k)] = v + elseif op.steps > 0 + error("Initial conditions must be for the past state of the unknowns. Instead of providing the condition for $k, provide the condition for $(Shift(iv, -1)(only(arguments(k)))).") + else + updated[Shift(iv, op.steps + 1)(only(arguments(k)))] = v + end + end + for var in unknowns(sys) + op = operation(var) + root = getunshifted(var) + shift = getshift(var) + isnothing(root) && continue + (haskey(updated, Shift(iv, shift)(root)) || haskey(updated, var)) && continue + haskey(defs, root) || error("Initial condition for $var not provided.") + updated[var] = defs[root] + end + return updated +end From 32774c96652c47f258e7e6ee0abacc3748836079 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Sun, 20 Apr 2025 20:22:55 +0530 Subject: [PATCH 051/185] refactor: remove `discrete_system.jl` --- src/ModelingToolkit.jl | 3 +- .../discrete_system/discrete_system.jl | 431 ------------------ 2 files changed, 1 insertion(+), 433 deletions(-) delete mode 100644 src/systems/discrete_system/discrete_system.jl diff --git a/src/ModelingToolkit.jl b/src/ModelingToolkit.jl index 94a44e6dfc..7da6ff4308 100644 --- a/src/ModelingToolkit.jl +++ b/src/ModelingToolkit.jl @@ -175,7 +175,6 @@ include("systems/diffeqs/first_order_transform.jl") include("systems/diffeqs/modelingtoolkitize.jl") include("systems/diffeqs/basic_transformations.jl") -include("systems/discrete_system/discrete_system.jl") include("systems/discrete_system/implicit_discrete_system.jl") include("systems/jumps/jumpsystem.jl") @@ -264,7 +263,7 @@ export ODEFunction, ODEFunctionExpr, ODEProblemExpr, convert_system, export DAEFunctionExpr, DAEProblemExpr export SDEFunction, SDEFunctionExpr, SDEProblemExpr export SystemStructure -export DiscreteSystem, DiscreteProblem, DiscreteFunction, DiscreteFunctionExpr +export DiscreteProblem, DiscreteFunction, DiscreteFunctionExpr export ImplicitDiscreteSystem, ImplicitDiscreteProblem, ImplicitDiscreteFunction, ImplicitDiscreteFunctionExpr export JumpSystem diff --git a/src/systems/discrete_system/discrete_system.jl b/src/systems/discrete_system/discrete_system.jl deleted file mode 100644 index 5f7c986659..0000000000 --- a/src/systems/discrete_system/discrete_system.jl +++ /dev/null @@ -1,431 +0,0 @@ -""" -$(TYPEDEF) -A system of difference equations. -# Fields -$(FIELDS) -# Example -``` -using ModelingToolkit -using ModelingToolkit: t_nounits as t -@parameters σ=28.0 ρ=10.0 β=8/3 δt=0.1 -@variables x(t)=1.0 y(t)=0.0 z(t)=0.0 -k = ShiftIndex(t) -eqs = [x(k+1) ~ σ*(y-x), - y(k+1) ~ x*(ρ-z)-y, - z(k+1) ~ x*y - β*z] -@named de = DiscreteSystem(eqs,t,[x,y,z],[σ,ρ,β]; tspan = (0, 1000.0)) # or -@named de = DiscreteSystem(eqs) -``` -""" -struct DiscreteSystem <: AbstractDiscreteSystem - """ - A tag for the system. If two systems have the same tag, then they are - structurally identical. - """ - tag::UInt - """The differential equations defining the discrete system.""" - eqs::Vector{Equation} - """Independent variable.""" - iv::BasicSymbolic{Real} - """Dependent (state) variables. Must not contain the independent variable.""" - unknowns::Vector - """Parameter variables. Must not contain the independent variable.""" - ps::Vector - """Time span.""" - tspan::Union{NTuple{2, Any}, Nothing} - """Array variables.""" - var_to_name::Any - """Observed states.""" - observed::Vector{Equation} - """ - The name of the system - """ - name::Symbol - """ - A description of the system. - """ - description::String - """ - The internal systems. These are required to have unique names. - """ - systems::Vector{DiscreteSystem} - """ - The default values to use when initial conditions and/or - parameters are not supplied in `DiscreteProblem`. - """ - defaults::Dict - """ - The guesses to use as the initial conditions for the - initialization system. - """ - guesses::Dict - """ - The system for performing the initialization. - """ - initializesystem::Union{Nothing, NonlinearSystem} - """ - Extra equations to be enforced during the initialization sequence. - """ - initialization_eqs::Vector{Equation} - """ - Inject assignment statements before the evaluation of the RHS function. - """ - preface::Any - """ - Type of the system. - """ - connector_type::Any - """ - Topologically sorted parameter dependency equations, where all symbols are parameters and - the LHS is a single parameter. - """ - parameter_dependencies::Vector{Equation} - """ - Metadata for the system, to be used by downstream packages. - """ - metadata::Any - """ - Metadata for MTK GUI. - """ - gui_metadata::Union{Nothing, GUIMetadata} - """ - Cache for intermediate tearing state. - """ - tearing_state::Any - """ - Substitutions generated by tearing. - """ - substitutions::Any - """ - If false, then `sys.x` no longer performs namespacing. - """ - namespacing::Bool - """ - If true, denotes the model will not be modified any further. - """ - complete::Bool - """ - Cached data for fast symbolic indexing. - """ - index_cache::Union{Nothing, IndexCache} - """ - The hierarchical parent system before simplification. - """ - parent::Any - isscheduled::Bool - - function DiscreteSystem(tag, discreteEqs, iv, dvs, ps, tspan, var_to_name, - observed, name, description, systems, defaults, guesses, initializesystem, - initialization_eqs, preface, connector_type, parameter_dependencies = Equation[], - metadata = nothing, gui_metadata = nothing, - tearing_state = nothing, substitutions = nothing, namespacing = true, - complete = false, index_cache = nothing, parent = nothing, - isscheduled = false; - checks::Union{Bool, Int} = true) - if checks == true || (checks & CheckComponents) > 0 - check_independent_variables([iv]) - check_variables(dvs, iv) - check_parameters(ps, iv) - check_subsystems(systems) - end - if checks == true || (checks & CheckUnits) > 0 - u = __get_unit_type(dvs, ps, iv) - check_units(u, discreteEqs) - end - new(tag, discreteEqs, iv, dvs, ps, tspan, var_to_name, observed, name, description, - systems, defaults, guesses, initializesystem, initialization_eqs, - preface, connector_type, parameter_dependencies, metadata, gui_metadata, - tearing_state, substitutions, namespacing, complete, index_cache, parent, - isscheduled) - end -end - -""" - $(TYPEDSIGNATURES) -Constructs a DiscreteSystem. -""" -function DiscreteSystem(eqs::AbstractVector{<:Equation}, iv, dvs, ps; - observed = Num[], - systems = DiscreteSystem[], - tspan = nothing, - name = nothing, - description = "", - default_u0 = Dict(), - default_p = Dict(), - guesses = Dict(), - initializesystem = nothing, - initialization_eqs = Equation[], - defaults = _merge(Dict(default_u0), Dict(default_p)), - preface = nothing, - connector_type = nothing, - parameter_dependencies = Equation[], - metadata = nothing, - gui_metadata = nothing, - kwargs...) - name === nothing && - throw(ArgumentError("The `name` keyword must be provided. Please consider using the `@named` macro")) - iv′ = value(iv) - dvs′ = value.(dvs) - ps′ = value.(ps) - if any(hasderiv, eqs) || any(hashold, eqs) || any(hassample, eqs) || any(hasdiff, eqs) - error("Equations in a `DiscreteSystem` can only have `Shift` operators.") - end - if !(isempty(default_u0) && isempty(default_p)) - Base.depwarn( - "`default_u0` and `default_p` are deprecated. Use `defaults` instead.", - :DiscreteSystem, force = true) - end - - defaults = Dict{Any, Any}(todict(defaults)) - guesses = Dict{Any, Any}(todict(guesses)) - var_to_name = Dict() - process_variables!(var_to_name, defaults, guesses, dvs′) - process_variables!(var_to_name, defaults, guesses, ps′) - process_variables!( - var_to_name, defaults, guesses, [eq.lhs for eq in parameter_dependencies]) - process_variables!( - var_to_name, defaults, guesses, [eq.rhs for eq in parameter_dependencies]) - defaults = Dict{Any, Any}(value(k) => value(v) - for (k, v) in pairs(defaults) if v !== nothing) - guesses = Dict{Any, Any}(value(k) => value(v) - for (k, v) in pairs(guesses) if v !== nothing) - - isempty(observed) || collect_var_to_name!(var_to_name, (eq.lhs for eq in observed)) - - sysnames = nameof.(systems) - if length(unique(sysnames)) != length(sysnames) - throw(ArgumentError("System names must be unique.")) - end - DiscreteSystem(Threads.atomic_add!(SYSTEM_COUNT, UInt(1)), - eqs, iv′, dvs′, ps′, tspan, var_to_name, observed, name, description, systems, - defaults, guesses, initializesystem, initialization_eqs, preface, connector_type, - parameter_dependencies, metadata, gui_metadata, kwargs...) -end - -function DiscreteSystem(eqs, iv; kwargs...) - eqs = collect(eqs) - diffvars = OrderedSet() - allunknowns = OrderedSet() - ps = OrderedSet() - iv = value(iv) - for eq in eqs - collect_vars!(allunknowns, ps, eq, iv; op = Shift) - if iscall(eq.lhs) && operation(eq.lhs) isa Shift - isequal(iv, operation(eq.lhs).t) || - throw(ArgumentError("A DiscreteSystem can only have one independent variable.")) - eq.lhs in diffvars && - throw(ArgumentError("The shift variable $(eq.lhs) is not unique in the system of equations.")) - push!(diffvars, eq.lhs) - end - end - for eq in get(kwargs, :parameter_dependencies, Equation[]) - if eq isa Pair - collect_vars!(allunknowns, ps, eq, iv) - else - collect_vars!(allunknowns, ps, eq, iv) - end - end - new_ps = OrderedSet() - for p in ps - if iscall(p) && operation(p) === getindex - par = arguments(p)[begin] - if Symbolics.shape(Symbolics.unwrap(par)) !== Symbolics.Unknown() && - all(par[i] in ps for i in eachindex(par)) - push!(new_ps, par) - else - push!(new_ps, p) - end - else - push!(new_ps, p) - end - end - return DiscreteSystem(eqs, iv, - collect(allunknowns), collect(new_ps); kwargs...) -end - -DiscreteSystem(eq::Equation, args...; kwargs...) = DiscreteSystem([eq], args...; kwargs...) - -function flatten(sys::DiscreteSystem, noeqs = false) - systems = get_systems(sys) - if isempty(systems) - return sys - else - return DiscreteSystem(noeqs ? Equation[] : equations(sys), - get_iv(sys), - unknowns(sys), - parameters(sys), - observed = observed(sys), - defaults = defaults(sys), - guesses = guesses(sys), - initialization_eqs = initialization_equations(sys), - name = nameof(sys), - description = description(sys), - metadata = get_metadata(sys), - checks = false) - end -end - -function generate_function( - sys::DiscreteSystem, dvs = unknowns(sys), ps = parameters(sys); wrap_code = identity, kwargs...) - exprs = [eq.rhs for eq in equations(sys)] - generate_custom_function(sys, exprs, dvs, ps; kwargs...) -end - -function shift_u0map_forward(sys::DiscreteSystem, u0map, defs) - iv = get_iv(sys) - updated = AnyDict() - for k in collect(keys(u0map)) - v = u0map[k] - if !((op = operation(k)) isa Shift) - isnothing(getunshifted(k)) && - error("Initial conditions must be for the past state of the unknowns. Instead of providing the condition for $k, provide the condition for $(Shift(iv, -1)(k)).") - - updated[Shift(iv, 1)(k)] = v - elseif op.steps > 0 - error("Initial conditions must be for the past state of the unknowns. Instead of providing the condition for $k, provide the condition for $(Shift(iv, -1)(only(arguments(k)))).") - else - updated[Shift(iv, op.steps + 1)(only(arguments(k)))] = v - end - end - for var in unknowns(sys) - op = operation(var) - root = getunshifted(var) - shift = getshift(var) - isnothing(root) && continue - (haskey(updated, Shift(iv, shift)(root)) || haskey(updated, var)) && continue - haskey(defs, root) || error("Initial condition for $var not provided.") - updated[var] = defs[root] - end - return updated -end - -""" - $(TYPEDSIGNATURES) -Generates an DiscreteProblem from an DiscreteSystem. -""" -function SciMLBase.DiscreteProblem( - sys::DiscreteSystem, u0map = [], tspan = get_tspan(sys), - parammap = SciMLBase.NullParameters(); - eval_module = @__MODULE__, - eval_expression = false, - kwargs... -) - if !iscomplete(sys) - error("A completed `DiscreteSystem` is required. Call `complete` or `structural_simplify` on the system before creating a `DiscreteProblem`") - end - dvs = unknowns(sys) - ps = parameters(sys) - eqs = equations(sys) - iv = get_iv(sys) - - u0map = to_varmap(u0map, dvs) - u0map = shift_u0map_forward(sys, u0map, defaults(sys)) - f, u0, p = process_SciMLProblem( - DiscreteFunction, sys, u0map, parammap; eval_expression, eval_module, build_initializeprob = false) - u0 = f(u0, p, tspan[1]) - DiscreteProblem(f, u0, tspan, p; kwargs...) -end - -function SciMLBase.DiscreteFunction(sys::DiscreteSystem, args...; kwargs...) - DiscreteFunction{true}(sys, args...; kwargs...) -end - -function SciMLBase.DiscreteFunction{true}(sys::DiscreteSystem, args...; kwargs...) - DiscreteFunction{true, SciMLBase.AutoSpecialize}(sys, args...; kwargs...) -end - -function SciMLBase.DiscreteFunction{false}(sys::DiscreteSystem, args...; kwargs...) - DiscreteFunction{false, SciMLBase.FullSpecialize}(sys, args...; kwargs...) -end - -""" -```julia -SciMLBase.DiscreteFunction{iip}(sys::DiscreteSystem, - dvs = unknowns(sys), - ps = parameters(sys); - kwargs...) where {iip} -``` - -Create an `DiscreteFunction` from the [`DiscreteSystem`](@ref). The arguments `dvs` and `ps` -are used to set the order of the dependent variable and parameter vectors, -respectively. -""" -function SciMLBase.DiscreteFunction{iip, specialize}( - sys::DiscreteSystem, - dvs = unknowns(sys), - ps = parameters(sys), - u0 = nothing; - version = nothing, - p = nothing, - t = nothing, - eval_expression = false, - eval_module = @__MODULE__, - analytic = nothing, cse = true, - kwargs...) where {iip, specialize} - if !iscomplete(sys) - error("A completed `DiscreteSystem` is required. Call `complete` or `structural_simplify` on the system before creating a `DiscreteProblem`") - end - f_gen = generate_function(sys, dvs, ps; expression = Val{true}, - expression_module = eval_module, cse, kwargs...) - f_oop, f_iip = eval_or_rgf.(f_gen; eval_expression, eval_module) - f = GeneratedFunctionWrapper{(2, 3, is_split(sys))}(f_oop, f_iip) - - if specialize === SciMLBase.FunctionWrapperSpecialize && iip - if u0 === nothing || p === nothing || t === nothing - error("u0, p, and t must be specified for FunctionWrapperSpecialize on DiscreteFunction.") - end - f = SciMLBase.wrapfun_iip(f, (u0, u0, p, t)) - end - - observedfun = ObservedFunctionCache( - sys; eval_expression, eval_module, checkbounds = get(kwargs, :checkbounds, false), cse) - - DiscreteFunction{iip, specialize}(f; - sys = sys, - observed = observedfun, - analytic = analytic) -end - -""" -```julia -DiscreteFunctionExpr{iip}(sys::DiscreteSystem, dvs = states(sys), - ps = parameters(sys); - version = nothing, - kwargs...) where {iip} -``` - -Create a Julia expression for an `DiscreteFunction` from the [`DiscreteSystem`](@ref). -The arguments `dvs` and `ps` are used to set the order of the dependent -variable and parameter vectors, respectively. -""" -struct DiscreteFunctionExpr{iip} end -struct DiscreteFunctionClosure{O, I} <: Function - f_oop::O - f_iip::I -end -(f::DiscreteFunctionClosure)(u, p, t) = f.f_oop(u, p, t) -(f::DiscreteFunctionClosure)(du, u, p, t) = f.f_iip(du, u, p, t) - -function DiscreteFunctionExpr{iip}(sys::DiscreteSystem, dvs = unknowns(sys), - ps = parameters(sys), u0 = nothing; - version = nothing, p = nothing, - linenumbers = false, - simplify = false, - kwargs...) where {iip} - f_oop, f_iip = generate_function(sys, dvs, ps; expression = Val{true}, kwargs...) - - fsym = gensym(:f) - _f = :($fsym = $DiscreteFunctionClosure($f_oop, $f_iip)) - - ex = quote - $_f - DiscreteFunction{$iip}($fsym) - end - !linenumbers ? Base.remove_linenums!(ex) : ex -end - -function DiscreteFunctionExpr(sys::DiscreteSystem, args...; kwargs...) - DiscreteFunctionExpr{true}(sys, args...; kwargs...) -end - -supports_initialization(::DiscreteSystem) = false From 11e9169f98478902fc561b73e5a4dff23ca9aa25 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Sun, 20 Apr 2025 21:37:21 +0530 Subject: [PATCH 052/185] refactor: remove `implicit_discrete_system.jl` --- src/ModelingToolkit.jl | 4 +- .../implicit_discrete_system.jl | 443 ------------------ 2 files changed, 1 insertion(+), 446 deletions(-) delete mode 100644 src/systems/discrete_system/implicit_discrete_system.jl diff --git a/src/ModelingToolkit.jl b/src/ModelingToolkit.jl index 7da6ff4308..096846d7c4 100644 --- a/src/ModelingToolkit.jl +++ b/src/ModelingToolkit.jl @@ -175,8 +175,6 @@ include("systems/diffeqs/first_order_transform.jl") include("systems/diffeqs/modelingtoolkitize.jl") include("systems/diffeqs/basic_transformations.jl") -include("systems/discrete_system/implicit_discrete_system.jl") - include("systems/jumps/jumpsystem.jl") include("systems/pde/pdesystem.jl") @@ -264,7 +262,7 @@ export DAEFunctionExpr, DAEProblemExpr export SDEFunction, SDEFunctionExpr, SDEProblemExpr export SystemStructure export DiscreteProblem, DiscreteFunction, DiscreteFunctionExpr -export ImplicitDiscreteSystem, ImplicitDiscreteProblem, ImplicitDiscreteFunction, +export ImplicitDiscreteProblem, ImplicitDiscreteFunction, ImplicitDiscreteFunctionExpr export JumpSystem export ODEProblem, SDEProblem diff --git a/src/systems/discrete_system/implicit_discrete_system.jl b/src/systems/discrete_system/implicit_discrete_system.jl deleted file mode 100644 index 3956c089d4..0000000000 --- a/src/systems/discrete_system/implicit_discrete_system.jl +++ /dev/null @@ -1,443 +0,0 @@ -""" -$(TYPEDEF) -An implicit system of difference equations. -# Fields -$(FIELDS) -# Example -``` -using ModelingToolkit -using ModelingToolkit: t_nounits as t -@parameters σ=28.0 ρ=10.0 β=8/3 δt=0.1 -@variables x(t)=1.0 y(t)=0.0 z(t)=0.0 -k = ShiftIndex(t) -eqs = [x ~ σ*(y-x(k-1)), - y ~ x(k-1)*(ρ-z(k-1))-y, - z ~ x(k-1)*y(k-1) - β*z] -@named ide = ImplicitDiscreteSystem(eqs,t,[x,y,z],[σ,ρ,β]; tspan = (0, 1000.0)) -``` -""" -struct ImplicitDiscreteSystem <: AbstractDiscreteSystem - """ - A tag for the system. If two systems have the same tag, then they are - structurally identical. - """ - tag::UInt - """The difference equations defining the discrete system.""" - eqs::Vector{Equation} - """Independent variable.""" - iv::BasicSymbolic{Real} - """Dependent (state) variables. Must not contain the independent variable.""" - unknowns::Vector - """Parameter variables. Must not contain the independent variable.""" - ps::Vector - """Time span.""" - tspan::Union{NTuple{2, Any}, Nothing} - """Array variables.""" - var_to_name::Any - """Observed states.""" - observed::Vector{Equation} - """ - The name of the system - """ - name::Symbol - """ - A description of the system. - """ - description::String - """ - The internal systems. These are required to have unique names. - """ - systems::Vector{ImplicitDiscreteSystem} - """ - The default values to use when initial conditions and/or - parameters are not supplied in `ImplicitDiscreteProblem`. - """ - defaults::Dict - """ - The guesses to use as the initial conditions for the - initialization system. - """ - guesses::Dict - """ - The system for performing the initialization. - """ - initializesystem::Union{Nothing, NonlinearSystem} - """ - Extra equations to be enforced during the initialization sequence. - """ - initialization_eqs::Vector{Equation} - """ - Inject assignment statements before the evaluation of the RHS function. - """ - preface::Any - """ - Type of the system. - """ - connector_type::Any - """ - Topologically sorted parameter dependency equations, where all symbols are parameters and - the LHS is a single parameter. - """ - parameter_dependencies::Vector{Equation} - """ - Metadata for the system, to be used by downstream packages. - """ - metadata::Any - """ - Metadata for MTK GUI. - """ - gui_metadata::Union{Nothing, GUIMetadata} - """ - Cache for intermediate tearing state. - """ - tearing_state::Any - """ - Substitutions generated by tearing. - """ - substitutions::Any - """ - If false, then `sys.x` no longer performs namespacing. - """ - namespacing::Bool - """ - If true, denotes the model will not be modified any further. - """ - complete::Bool - """ - Cached data for fast symbolic indexing. - """ - index_cache::Union{Nothing, IndexCache} - """ - The hierarchical parent system before simplification. - """ - parent::Any - isscheduled::Bool - - function ImplicitDiscreteSystem(tag, discreteEqs, iv, dvs, ps, tspan, var_to_name, - observed, name, description, systems, defaults, guesses, initializesystem, - initialization_eqs, preface, connector_type, parameter_dependencies = Equation[], - metadata = nothing, gui_metadata = nothing, - tearing_state = nothing, substitutions = nothing, namespacing = true, - complete = false, index_cache = nothing, parent = nothing, - isscheduled = false; - checks::Union{Bool, Int} = true) - if checks == true || (checks & CheckComponents) > 0 - check_independent_variables([iv]) - check_variables(dvs, iv) - check_parameters(ps, iv) - check_subsystems(systems) - end - if checks == true || (checks & CheckUnits) > 0 - u = __get_unit_type(dvs, ps, iv) - check_units(u, discreteEqs) - end - new(tag, discreteEqs, iv, dvs, ps, tspan, var_to_name, observed, name, description, - systems, defaults, guesses, initializesystem, initialization_eqs, - preface, connector_type, parameter_dependencies, metadata, gui_metadata, - tearing_state, substitutions, namespacing, complete, index_cache, parent, - isscheduled) - end -end - -""" - $(TYPEDSIGNATURES) - -Constructs a ImplicitDiscreteSystem. -""" -function ImplicitDiscreteSystem(eqs::AbstractVector{<:Equation}, iv, dvs, ps; - observed = Num[], - systems = ImplicitDiscreteSystem[], - tspan = nothing, - name = nothing, - description = "", - default_u0 = Dict(), - default_p = Dict(), - guesses = Dict(), - initializesystem = nothing, - initialization_eqs = Equation[], - defaults = _merge(Dict(default_u0), Dict(default_p)), - preface = nothing, - connector_type = nothing, - parameter_dependencies = Equation[], - metadata = nothing, - gui_metadata = nothing, - kwargs...) - name === nothing && - throw(ArgumentError("The `name` keyword must be provided. Please consider using the `@named` macro")) - iv′ = value(iv) - dvs′ = value.(dvs) - ps′ = value.(ps) - if any(hasderiv, eqs) || any(hashold, eqs) || any(hassample, eqs) || any(hasdiff, eqs) - error("Equations in a `ImplicitDiscreteSystem` can only have `Shift` operators.") - end - if !(isempty(default_u0) && isempty(default_p)) - Base.depwarn( - "`default_u0` and `default_p` are deprecated. Use `defaults` instead.", - :ImplicitDiscreteSystem, force = true) - end - - # Copy equations to canonical form, but do not touch array expressions - eqs = [wrap(eq.lhs) isa Symbolics.Arr ? eq : 0 ~ eq.rhs - eq.lhs for eq in eqs] - defaults = Dict{Any, Any}(todict(defaults)) - guesses = Dict{Any, Any}(todict(guesses)) - var_to_name = Dict() - process_variables!(var_to_name, defaults, guesses, dvs′) - process_variables!(var_to_name, defaults, guesses, ps′) - process_variables!( - var_to_name, defaults, guesses, [eq.lhs for eq in parameter_dependencies]) - process_variables!( - var_to_name, defaults, guesses, [eq.rhs for eq in parameter_dependencies]) - defaults = Dict{Any, Any}(value(k) => value(v) - for (k, v) in pairs(defaults) if v !== nothing) - guesses = Dict{Any, Any}(value(k) => value(v) - for (k, v) in pairs(guesses) if v !== nothing) - - isempty(observed) || collect_var_to_name!(var_to_name, (eq.lhs for eq in observed)) - - sysnames = nameof.(systems) - if length(unique(sysnames)) != length(sysnames) - throw(ArgumentError("System names must be unique.")) - end - ImplicitDiscreteSystem(Threads.atomic_add!(SYSTEM_COUNT, UInt(1)), - eqs, iv′, dvs′, ps′, tspan, var_to_name, observed, name, description, systems, - defaults, guesses, initializesystem, initialization_eqs, preface, connector_type, - parameter_dependencies, metadata, gui_metadata, kwargs...) -end - -function ImplicitDiscreteSystem(eqs, iv; kwargs...) - eqs = collect(eqs) - diffvars = OrderedSet() - allunknowns = OrderedSet() - ps = OrderedSet() - iv = value(iv) - for eq in eqs - collect_vars!(allunknowns, ps, eq, iv; op = Shift) - if iscall(eq.lhs) && operation(eq.lhs) isa Shift - isequal(iv, operation(eq.lhs).t) || - throw(ArgumentError("An ImplicitDiscreteSystem can only have one independent variable.")) - eq.lhs in diffvars && - throw(ArgumentError("The shift variable $(eq.lhs) is not unique in the system of equations.")) - push!(diffvars, eq.lhs) - end - end - for eq in get(kwargs, :parameter_dependencies, Equation[]) - if eq isa Pair - collect_vars!(allunknowns, ps, eq, iv) - else - collect_vars!(allunknowns, ps, eq, iv) - end - end - new_ps = OrderedSet() - for p in ps - if iscall(p) && operation(p) === getindex - par = arguments(p)[begin] - if Symbolics.shape(Symbolics.unwrap(par)) !== Symbolics.Unknown() && - all(par[i] in ps for i in eachindex(par)) - push!(new_ps, par) - else - push!(new_ps, p) - end - else - push!(new_ps, p) - end - end - return ImplicitDiscreteSystem(eqs, iv, - collect(allunknowns), collect(new_ps); kwargs...) -end - -function ImplicitDiscreteSystem(eq::Equation, args...; kwargs...) - ImplicitDiscreteSystem([eq], args...; kwargs...) -end - -function flatten(sys::ImplicitDiscreteSystem, noeqs = false) - systems = get_systems(sys) - if isempty(systems) - return sys - else - return ImplicitDiscreteSystem(noeqs ? Equation[] : equations(sys), - get_iv(sys), - unknowns(sys), - parameters(sys), - observed = observed(sys), - defaults = defaults(sys), - guesses = guesses(sys), - initialization_eqs = initialization_equations(sys), - name = nameof(sys), - description = description(sys), - metadata = get_metadata(sys), - checks = false) - end -end - -function generate_function( - sys::ImplicitDiscreteSystem, dvs = unknowns(sys), ps = parameters(sys); wrap_code = identity, kwargs...) - iv = get_iv(sys) - # Algebraic equations get shifted forward 1, to match with differential equations - exprs = map(equations(sys)) do eq - _iszero(eq.lhs) ? distribute_shift(Shift(iv, 1)(eq.rhs)) : (eq.rhs - eq.lhs) - end - - # Handle observables in algebraic equations, since they are shifted - obs = observed(sys) - shifted_obs = Symbolics.Equation[distribute_shift(Shift(iv, 1)(eq)) for eq in obs] - obsidxs = observed_equations_used_by(sys, exprs; obs = shifted_obs) - extra_assignments = [Assignment(shifted_obs[i].lhs, shifted_obs[i].rhs) - for i in obsidxs] - - u_next = map(Shift(iv, 1), dvs) - u = dvs - build_function_wrapper( - sys, exprs, u_next, u, ps..., iv; p_start = 3, extra_assignments, kwargs...) -end - -function shift_u0map_forward(sys::ImplicitDiscreteSystem, u0map, defs) - iv = get_iv(sys) - updated = AnyDict() - for k in collect(keys(u0map)) - v = u0map[k] - if !((op = operation(k)) isa Shift) - isnothing(getunshifted(k)) && - error("Initial conditions must be for the past state of the unknowns. Instead of providing the condition for $k, provide the condition for $(Shift(iv, -1)(k)).") - - updated[k] = v - elseif op.steps > 0 - error("Initial conditions must be for the past state of the unknowns. Instead of providing the condition for $k, provide the condition for $(Shift(iv, -1)(only(arguments(k)))).") - else - updated[k] = v - end - end - for var in unknowns(sys) - op = operation(var) - root = getunshifted(var) - shift = getshift(var) - isnothing(root) && continue - (haskey(updated, Shift(iv, shift)(root)) || haskey(updated, var)) && continue - haskey(defs, root) || error("Initial condition for $var not provided.") - updated[var] = defs[root] - end - return updated -end - -""" - $(TYPEDSIGNATURES) -Generates an ImplicitDiscreteProblem from an ImplicitDiscreteSystem. -""" -function SciMLBase.ImplicitDiscreteProblem( - sys::ImplicitDiscreteSystem, u0map = [], tspan = get_tspan(sys), - parammap = SciMLBase.NullParameters(); - eval_module = @__MODULE__, - eval_expression = false, - kwargs... -) - if !iscomplete(sys) - error("A completed `ImplicitDiscreteSystem` is required. Call `complete` or `structural_simplify` on the system before creating a `ImplicitDiscreteProblem`.") - end - dvs = unknowns(sys) - ps = parameters(sys) - eqs = equations(sys) - iv = get_iv(sys) - - u0map = to_varmap(u0map, dvs) - u0map = shift_u0map_forward(sys, u0map, defaults(sys)) - f, u0, p = process_SciMLProblem( - ImplicitDiscreteFunction, sys, u0map, parammap; eval_expression, eval_module, kwargs...) - - kwargs = filter_kwargs(kwargs) - ImplicitDiscreteProblem(f, u0, tspan, p; kwargs...) -end - -function SciMLBase.ImplicitDiscreteFunction(sys::ImplicitDiscreteSystem, args...; kwargs...) - ImplicitDiscreteFunction{true}(sys, args...; kwargs...) -end - -function SciMLBase.ImplicitDiscreteFunction{true}( - sys::ImplicitDiscreteSystem, args...; kwargs...) - ImplicitDiscreteFunction{true, SciMLBase.AutoSpecialize}(sys, args...; kwargs...) -end - -function SciMLBase.ImplicitDiscreteFunction{false}( - sys::ImplicitDiscreteSystem, args...; kwargs...) - ImplicitDiscreteFunction{false, SciMLBase.FullSpecialize}(sys, args...; kwargs...) -end -function SciMLBase.ImplicitDiscreteFunction{iip, specialize}( - sys::ImplicitDiscreteSystem, - dvs = unknowns(sys), - ps = parameters(sys), - u0 = nothing; - version = nothing, - p = nothing, - t = nothing, - eval_expression = false, - eval_module = @__MODULE__, - analytic = nothing, cse = true, - kwargs...) where {iip, specialize} - if !iscomplete(sys) - error("A completed `ImplicitDiscreteSystem` is required. Call `complete` or `structural_simplify` on the system before creating a `ImplicitDiscreteProblem`") - end - f_gen = generate_function(sys, dvs, ps; expression = Val{true}, - expression_module = eval_module, cse, kwargs...) - f_oop, f_iip = eval_or_rgf.(f_gen; eval_expression, eval_module) - f(u_next, u, p, t) = f_oop(u_next, u, p, t) - f(resid, u_next, u, p, t) = f_iip(resid, u_next, u, p, t) - - if specialize === SciMLBase.FunctionWrapperSpecialize && iip - if u0 === nothing || p === nothing || t === nothing - error("u0, p, and t must be specified for FunctionWrapperSpecialize on ImplicitDiscreteFunction.") - end - f = SciMLBase.wrapfun_iip(f, (u0, u0, p, t)) - end - - observedfun = ObservedFunctionCache( - sys; eval_expression, eval_module, checkbounds = get(kwargs, :checkbounds, false), cse) - - ImplicitDiscreteFunction{iip, specialize}(f; - sys = sys, - observed = observedfun, - analytic = analytic, - kwargs...) -end - -""" -```julia -ImplicitDiscreteFunctionExpr{iip}(sys::ImplicitDiscreteSystem, dvs = states(sys), - ps = parameters(sys); - version = nothing, - kwargs...) where {iip} -``` - -Create a Julia expression for an `ImplicitDiscreteFunction` from the [`ImplicitDiscreteSystem`](@ref). -The arguments `dvs` and `ps` are used to set the order of the dependent -variable and parameter vectors, respectively. -""" -struct ImplicitDiscreteFunctionExpr{iip} end -struct ImplicitDiscreteFunctionClosure{O, I} <: Function - f_oop::O - f_iip::I -end -(f::ImplicitDiscreteFunctionClosure)(u_next, u, p, t) = f.f_oop(u_next, u, p, t) -function (f::ImplicitDiscreteFunctionClosure)(resid, u_next, u, p, t) - f.f_iip(resid, u_next, u, p, t) -end - -function ImplicitDiscreteFunctionExpr{iip}( - sys::ImplicitDiscreteSystem, dvs = unknowns(sys), - ps = parameters(sys), u0 = nothing; - version = nothing, p = nothing, - linenumbers = false, - simplify = false, - kwargs...) where {iip} - f_oop, f_iip = generate_function(sys, dvs, ps; expression = Val{true}, kwargs...) - - fsym = gensym(:f) - _f = :($fsym = $ImplicitDiscreteFunctionClosure($f_oop, $f_iip)) - - ex = quote - $_f - ImplicitDiscreteFunction{$iip}($fsym) - end - !linenumbers ? Base.remove_linenums!(ex) : ex -end - -function ImplicitDiscreteFunctionExpr(sys::ImplicitDiscreteSystem, args...; kwargs...) - ImplicitDiscreteFunctionExpr{true}(sys, args...; kwargs...) -end From ff4692ace9d8d7e2fb7963bf284632d681e84d5b Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Sun, 20 Apr 2025 21:44:39 +0530 Subject: [PATCH 053/185] refactor: move jumpsystem functions to generalized locations --- src/problems/jumpproblem.jl | 12 ++++++++++++ src/systems/codegen.jl | 25 +++++++++++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/src/problems/jumpproblem.jl b/src/problems/jumpproblem.jl index b4d042e4f8..4cc6c8d249 100644 --- a/src/problems/jumpproblem.jl +++ b/src/problems/jumpproblem.jl @@ -151,6 +151,18 @@ function (ratemap::JumpSysMajParamMapper{U, V, W})(maj::MassActionJump, newparam nothing end +# create the initial parameter vector for use in a MassActionJump +function (ratemap::JumpSysMajParamMapper{ + U, + V, + W +})(params) where {U <: AbstractArray, + V <: AbstractArray, W} + updateparams!(ratemap, params) + [convert(W, value(substitute(paramexpr, ratemap.subdict))) + for paramexpr in ratemap.paramexprs] +end + ##### MTK dispatches for Symbolic jumps ##### eqtype_supports_collect_vars(j::MassActionJump) = true function collect_vars!(unknowns, parameters, j::MassActionJump, iv; depth = 0, diff --git a/src/systems/codegen.jl b/src/systems/codegen.jl index 0e8833f677..eeb5f85de5 100644 --- a/src/systems/codegen.jl +++ b/src/systems/codegen.jl @@ -545,6 +545,31 @@ function assemble_maj(majv::Vector{U}, unknowntoid, pmapper) where {U <: MassAct MassActionJump(rs, ns; param_mapper = pmapper, nocopy = true) end +function numericrstoich(mtrs::Vector{Pair{V, W}}, unknowntoid) where {V, W} + rs = Vector{Pair{Int, W}}() + for (wspec, stoich) in mtrs + spec = value(wspec) + if !iscall(spec) && _iszero(spec) + push!(rs, 0 => stoich) + else + push!(rs, unknowntoid[spec] => stoich) + end + end + sort!(rs) + rs +end + +function numericnstoich(mtrs::Vector{Pair{V, W}}, unknowntoid) where {V, W} + ns = Vector{Pair{Int, W}}() + for (wspec, stoich) in mtrs + spec = value(wspec) + !iscall(spec) && _iszero(spec) && + error("Net stoichiometry can not have a species labelled 0.") + push!(ns, unknowntoid[spec] => stoich) + end + sort!(ns) +end + """ build_explicit_observed_function(sys, ts; kwargs...) -> Function(s) From 72de6b57c35e50b32c9c0a24ac4cc33cf4462b30 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Sun, 20 Apr 2025 21:44:54 +0530 Subject: [PATCH 054/185] refactor: remove `jumpsystem.jl` --- src/ModelingToolkit.jl | 3 - src/systems/jumps/jumpsystem.jl | 531 -------------------------------- 2 files changed, 534 deletions(-) delete mode 100644 src/systems/jumps/jumpsystem.jl diff --git a/src/ModelingToolkit.jl b/src/ModelingToolkit.jl index 096846d7c4..2e9a4503fb 100644 --- a/src/ModelingToolkit.jl +++ b/src/ModelingToolkit.jl @@ -175,8 +175,6 @@ include("systems/diffeqs/first_order_transform.jl") include("systems/diffeqs/modelingtoolkitize.jl") include("systems/diffeqs/basic_transformations.jl") -include("systems/jumps/jumpsystem.jl") - include("systems/pde/pdesystem.jl") include("systems/sparsematrixclil.jl") @@ -264,7 +262,6 @@ export SystemStructure export DiscreteProblem, DiscreteFunction, DiscreteFunctionExpr export ImplicitDiscreteProblem, ImplicitDiscreteFunction, ImplicitDiscreteFunctionExpr -export JumpSystem export ODEProblem, SDEProblem export NonlinearFunction, NonlinearFunctionExpr export NonlinearProblem, NonlinearProblemExpr diff --git a/src/systems/jumps/jumpsystem.jl b/src/systems/jumps/jumpsystem.jl deleted file mode 100644 index ae44a4a929..0000000000 --- a/src/systems/jumps/jumpsystem.jl +++ /dev/null @@ -1,531 +0,0 @@ -const JumpType = Union{VariableRateJump, ConstantRateJump, MassActionJump} - -""" -$(TYPEDEF) - -A system of jump processes. - -# Fields -$(FIELDS) - -# Example - -```julia -using ModelingToolkit, JumpProcesses -using ModelingToolkit: t_nounits as t - -@parameters β γ -@variables S(t) I(t) R(t) -rate₁ = β*S*I -affect₁ = [S ~ S - 1, I ~ I + 1] -rate₂ = γ*I -affect₂ = [I ~ I - 1, R ~ R + 1] -j₁ = ConstantRateJump(rate₁,affect₁) -j₂ = ConstantRateJump(rate₂,affect₂) -j₃ = MassActionJump(2*β+γ, [R => 1], [S => 1, R => -1]) -@named js = JumpSystem([j₁,j₂,j₃], t, [S,I,R], [β,γ]) -``` -""" -struct JumpSystem{U <: ArrayPartition} <: AbstractTimeDependentSystem - """ - A tag for the system. If two systems have the same tag, then they are - structurally identical. - """ - tag::UInt - """ - The jumps of the system. Allowable types are `ConstantRateJump`, - `VariableRateJump`, `MassActionJump`. - """ - eqs::U - """The independent variable, usually time.""" - iv::Any - """The dependent variables, representing the state of the system. Must not contain the independent variable.""" - unknowns::Vector - """The parameters of the system. Must not contain the independent variable.""" - ps::Vector - """Array variables.""" - var_to_name::Any - """Observed equations.""" - observed::Vector{Equation} - """The name of the system.""" - name::Symbol - """A description of the system.""" - description::String - """The internal systems. These are required to have unique names.""" - systems::Vector{JumpSystem} - """ - The default values to use when initial conditions and/or - parameters are not supplied in `ODEProblem`. - """ - defaults::Dict - """ - The guesses to use as the initial conditions for the - initialization system. - """ - guesses::Dict - """ - The system for performing the initialization. - """ - initializesystem::Union{Nothing, NonlinearSystem} - """ - Extra equations to be enforced during the initialization sequence. - """ - initialization_eqs::Vector{Equation} - """ - Type of the system. - """ - connector_type::Any - """ - A `Vector{SymbolicContinuousCallback}` that model events. - The integrator will use root finding to guarantee that it steps at each zero crossing. - """ - continuous_events::Vector{SymbolicContinuousCallback} - """ - A `Vector{SymbolicDiscreteCallback}` that models events. Symbolic - analog to `SciMLBase.DiscreteCallback` that executes an affect when a given condition is - true at the end of an integration step. Note, one must make sure to call - `reset_aggregated_jumps!(integrator)` if using a custom affect function that changes any - unknown value or parameter. - """ - discrete_events::Vector{SymbolicDiscreteCallback} - """ - Topologically sorted parameter dependency equations, where all symbols are parameters and - the LHS is a single parameter. - """ - parameter_dependencies::Vector{Equation} - """ - Metadata for the system, to be used by downstream packages. - """ - metadata::Any - """ - Metadata for MTK GUI. - """ - gui_metadata::Union{Nothing, GUIMetadata} - """ - If false, then `sys.x` no longer performs namespacing. - """ - namespacing::Bool - """ - If true, denotes the model will not be modified any further. - """ - complete::Bool - """ - Cached data for fast symbolic indexing. - """ - index_cache::Union{Nothing, IndexCache} - isscheduled::Bool - - function JumpSystem{U}( - tag, ap::U, iv, unknowns, ps, var_to_name, observed, name, description, - systems, defaults, guesses, initializesystem, initialization_eqs, connector_type, - cevents, devents, - parameter_dependencies, metadata = nothing, gui_metadata = nothing, - namespacing = true, complete = false, index_cache = nothing, isscheduled = false; - checks::Union{Bool, Int} = true) where {U <: ArrayPartition} - if checks == true || (checks & CheckComponents) > 0 - check_independent_variables([iv]) - check_variables(unknowns, iv) - check_parameters(ps, iv) - check_subsystems(systems) - end - if checks == true || (checks & CheckUnits) > 0 - u = __get_unit_type(unknowns, ps, iv) - check_units(u, ap, iv) - end - new{U}(tag, ap, iv, unknowns, ps, var_to_name, - observed, name, description, systems, defaults, guesses, initializesystem, - initialization_eqs, - connector_type, cevents, devents, parameter_dependencies, metadata, - gui_metadata, namespacing, complete, index_cache, isscheduled) - end -end -function JumpSystem(tag, ap, iv, states, ps, var_to_name, args...; kwargs...) - JumpSystem{typeof(ap)}(tag, ap, iv, states, ps, var_to_name, args...; kwargs...) -end - -function JumpSystem(eqs, iv, unknowns, ps; - observed = Equation[], - systems = JumpSystem[], - default_u0 = Dict(), - default_p = Dict(), - defaults = _merge(Dict(default_u0), Dict(default_p)), - guesses = Dict(), - initializesystem = nothing, - initialization_eqs = Equation[], - name = nothing, - description = "", - connector_type = nothing, - checks = true, - continuous_events = nothing, - discrete_events = nothing, - parameter_dependencies = Equation[], - metadata = nothing, - gui_metadata = nothing, - kwargs...) - - # variable processing, similar to ODESystem - name === nothing && - throw(ArgumentError("The `name` keyword must be provided. Please consider using the `@named` macro")) - iv′ = value(iv) - us′ = value.(unknowns) - ps′ = value.(ps) - parameter_dependencies, ps′ = process_parameter_dependencies( - parameter_dependencies, ps′) - if !(isempty(default_u0) && isempty(default_p)) - Base.depwarn( - "`default_u0` and `default_p` are deprecated. Use `defaults` instead.", - :JumpSystem, force = true) - end - defaults = Dict{Any, Any}(todict(defaults)) - guesses = Dict{Any, Any}(todict(guesses)) - var_to_name = Dict() - process_variables!(var_to_name, defaults, guesses, us′) - process_variables!(var_to_name, defaults, guesses, ps′) - process_variables!( - var_to_name, defaults, guesses, [eq.lhs for eq in parameter_dependencies]) - process_variables!( - var_to_name, defaults, guesses, [eq.rhs for eq in parameter_dependencies]) - #! format: off - defaults = Dict{Any, Any}(value(k) => value(v) for (k, v) in pairs(defaults) if value(v) !== nothing) - guesses = Dict{Any, Any}(value(k) => value(v) for (k, v) in pairs(guesses) if v !== nothing) - #! format: on - isempty(observed) || collect_var_to_name!(var_to_name, (eq.lhs for eq in observed)) - - sysnames = nameof.(systems) - if length(unique(sysnames)) != length(sysnames) - throw(ArgumentError("System names must be unique.")) - end - - # equation processing - # this and the treatment of continuous events are the only part - # unique to JumpSystems - eqs = scalarize.(eqs) - ap = ArrayPartition( - MassActionJump[], ConstantRateJump[], VariableRateJump[], Equation[]) - for eq in eqs - if eq isa MassActionJump - push!(ap.x[1], eq) - elseif eq isa ConstantRateJump - push!(ap.x[2], eq) - elseif eq isa VariableRateJump - push!(ap.x[3], eq) - elseif eq isa Equation - push!(ap.x[4], eq) - else - error("JumpSystem equations must contain MassActionJumps, ConstantRateJumps, VariableRateJumps, or Equations.") - end - end - - cont_callbacks = SymbolicContinuousCallbacks(continuous_events) - disc_callbacks = SymbolicDiscreteCallbacks(discrete_events) - - JumpSystem{typeof(ap)}(Threads.atomic_add!(SYSTEM_COUNT, UInt(1)), - ap, iv′, us′, ps′, var_to_name, observed, name, description, systems, - defaults, guesses, initializesystem, initialization_eqs, connector_type, - cont_callbacks, disc_callbacks, - parameter_dependencies, metadata, gui_metadata, checks = checks) -end - -########################################## - -has_massactionjumps(js::JumpSystem) = !isempty(equations(js).x[1]) -has_constantratejumps(js::JumpSystem) = !isempty(equations(js).x[2]) -has_variableratejumps(js::JumpSystem) = !isempty(equations(js).x[3]) -has_equations(js::JumpSystem) = !isempty(equations(js).x[4]) - -function generate_rate_function(js::JumpSystem, rate) - consts = collect_constants(rate) - if !isempty(consts) # The SymbolicUtils._build_function method of this case doesn't support postprocess_fbody - csubs = Dict(c => getdefault(c) for c in consts) - rate = substitute(rate, csubs) - end - p = reorder_parameters(js) - build_function_wrapper(js, rate, unknowns(js), p..., - get_iv(js), - expression = Val{true}) -end - -function generate_affect_function(js::JumpSystem, affect, outputidxs) - consts = collect_constants(affect) - if !isempty(consts) # The SymbolicUtils._build_function method of this case doesn't support postprocess_fbody - csubs = Dict(c => getdefault(c) for c in consts) - affect = substitute(affect, csubs) - end - compile_affect( - affect, nothing, js, unknowns(js), parameters(js); outputidxs = outputidxs, - expression = Val{true}, checkvars = false) -end - -function assemble_vrj_expr(js, vrj, unknowntoid) - rate = generate_rate_function(js, vrj.rate) - outputvars = (value(affect.lhs) for affect in vrj.affect!) - outputidxs = ((unknowntoid[var] for var in outputvars)...,) - affect = generate_affect_function(js, vrj.affect!, outputidxs) - quote - rate = $rate - - affect = $affect - VariableRateJump(rate, affect) - end -end - -function assemble_crj_expr(js, crj, unknowntoid) - rate = generate_rate_function(js, crj.rate) - outputvars = (value(affect.lhs) for affect in crj.affect!) - outputidxs = ((unknowntoid[var] for var in outputvars)...,) - affect = generate_affect_function(js, crj.affect!, outputidxs) - quote - rate = $rate - - affect = $affect - ConstantRateJump(rate, affect) - end -end - -function numericrstoich(mtrs::Vector{Pair{V, W}}, unknowntoid) where {V, W} - rs = Vector{Pair{Int, W}}() - for (wspec, stoich) in mtrs - spec = value(wspec) - if !iscall(spec) && _iszero(spec) - push!(rs, 0 => stoich) - else - push!(rs, unknowntoid[spec] => stoich) - end - end - sort!(rs) - rs -end - -function numericnstoich(mtrs::Vector{Pair{V, W}}, unknowntoid) where {V, W} - ns = Vector{Pair{Int, W}}() - for (wspec, stoich) in mtrs - spec = value(wspec) - !iscall(spec) && _iszero(spec) && - error("Net stoichiometry can not have a species labelled 0.") - push!(ns, unknowntoid[spec] => stoich) - end - sort!(ns) -end - -""" -```julia -DiffEqBase.DiscreteProblem(sys::JumpSystem, u0map, tspan, - parammap = DiffEqBase.NullParameters; - kwargs...) -``` - -Generates a blank DiscreteProblem for a pure jump JumpSystem to utilize as -its `prob.prob`. This is used in the case where there are no ODEs -and no SDEs associated with the system. - -Continuing the example from the [`JumpSystem`](@ref) definition: - -```julia -using DiffEqBase, JumpProcesses -u₀map = [S => 999, I => 1, R => 0] -parammap = [β => 0.1 / 1000, γ => 0.01] -tspan = (0.0, 250.0) -dprob = DiscreteProblem(complete(js), u₀map, tspan, parammap) -``` -""" -function DiffEqBase.DiscreteProblem(sys::JumpSystem, u0map, tspan::Union{Tuple, Nothing}, - parammap = DiffEqBase.NullParameters(); - eval_expression = false, - eval_module = @__MODULE__, - cse = true, - kwargs...) - if !iscomplete(sys) - error("A completed `JumpSystem` is required. Call `complete` or `structural_simplify` on the system before creating a `DiscreteProblem`") - end - - if has_equations(sys) || (!isempty(continuous_events(sys))) - error("The passed in JumpSystem contains `Equation`s or continuous events, please use a problem type that supports these features, such as ODEProblem.") - end - - _f, u0, p = process_SciMLProblem(EmptySciMLFunction, sys, u0map, parammap; - t = tspan === nothing ? nothing : tspan[1], tofloat = false, check_length = false, build_initializeprob = false, cse) - f = DiffEqBase.DISCRETE_INPLACE_DEFAULT - - observedfun = ObservedFunctionCache( - sys; eval_expression, eval_module, checkbounds = get(kwargs, :checkbounds, false), cse) - - df = DiscreteFunction{true, true}(f; sys = sys, observed = observedfun, - initialization_data = get(_f.kwargs, :initialization_data, nothing)) - DiscreteProblem(df, u0, tspan, p; kwargs...) -end - -""" -```julia -DiffEqBase.DiscreteProblemExpr(sys::JumpSystem, u0map, tspan, - parammap = DiffEqBase.NullParameters; kwargs...) -``` - -Generates a blank DiscreteProblem for a JumpSystem to utilize as its -solving `prob.prob`. This is used in the case where there are no ODEs -and no SDEs associated with the system. - -Continuing the example from the [`JumpSystem`](@ref) definition: - -```julia -using DiffEqBase, JumpProcesses -u₀map = [S => 999, I => 1, R => 0] -parammap = [β => 0.1 / 1000, γ => 0.01] -tspan = (0.0, 250.0) -dprob = DiscreteProblem(complete(js), u₀map, tspan, parammap) -``` -""" -struct DiscreteProblemExpr{iip} end - -function DiscreteProblemExpr{iip}(sys::JumpSystem, u0map, tspan::Union{Tuple, Nothing}, - parammap = DiffEqBase.NullParameters(); - kwargs...) where {iip} - if !iscomplete(sys) - error("A completed `JumpSystem` is required. Call `complete` or `structural_simplify` on the system before creating a `DiscreteProblemExpr`") - end - - _, u0, p = process_SciMLProblem(EmptySciMLFunction, sys, u0map, parammap; - t = tspan === nothing ? nothing : tspan[1], tofloat = false, check_length = false) - # identity function to make syms works - quote - f = DiffEqBase.DISCRETE_INPLACE_DEFAULT - u0 = $u0 - p = $p - sys = $sys - tspan = $tspan - df = DiscreteFunction{true, true}(f; sys = sys) - DiscreteProblem(df, u0, tspan, p) - end -end - -""" -```julia -DiffEqBase.ODEProblem(sys::JumpSystem, u0map, tspan, - parammap = DiffEqBase.NullParameters; - kwargs...) -``` - -Generates a blank ODEProblem for a pure jump JumpSystem to utilize as its `prob.prob`. This -is used in the case where there are no ODEs and no SDEs associated with the system but there -are jumps with an explicit time dependency (i.e. `VariableRateJump`s). If no jumps have an -explicit time dependence, i.e. all are `ConstantRateJump`s or `MassActionJump`s then -`DiscreteProblem` should be preferred for performance reasons. - -Continuing the example from the [`JumpSystem`](@ref) definition: - -```julia -using DiffEqBase, JumpProcesses -u₀map = [S => 999, I => 1, R => 0] -parammap = [β => 0.1 / 1000, γ => 0.01] -tspan = (0.0, 250.0) -oprob = ODEProblem(complete(js), u₀map, tspan, parammap) -``` -""" -function DiffEqBase.ODEProblem(sys::JumpSystem, u0map, tspan::Union{Tuple, Nothing}, - parammap = DiffEqBase.NullParameters(); - eval_expression = false, - eval_module = @__MODULE__, cse = true, - kwargs...) - if !iscomplete(sys) - error("A completed `JumpSystem` is required. Call `complete` or `structural_simplify` on the system before creating a `DiscreteProblem`") - end - - # forward everything to be an ODESystem but the jumps and discrete events - if has_equations(sys) - osys = ODESystem(equations(sys).x[4], get_iv(sys), unknowns(sys), parameters(sys); - observed = observed(sys), name = nameof(sys), description = description(sys), - systems = get_systems(sys), defaults = defaults(sys), guesses = guesses(sys), - parameter_dependencies = parameter_dependencies(sys), - metadata = get_metadata(sys), gui_metadata = get_gui_metadata(sys)) - osys = complete(osys; add_initial_parameters = false) - return ODEProblem(osys, u0map, tspan, parammap; check_length = false, - build_initializeprob = false, kwargs...) - else - _, u0, p = process_SciMLProblem(EmptySciMLFunction, sys, u0map, parammap; - t = tspan === nothing ? nothing : tspan[1], tofloat = false, - check_length = false, build_initializeprob = false, cse) - f = (du, u, p, t) -> (du .= 0; nothing) - observedfun = ObservedFunctionCache(sys; eval_expression, eval_module, - checkbounds = get(kwargs, :checkbounds, false), cse) - df = ODEFunction(f; sys, observed = observedfun) - return ODEProblem(df, u0, tspan, p; kwargs...) - end -end - -""" -```julia -DiffEqBase.JumpProblem(js::JumpSystem, prob, aggregator; kwargs...) -``` - -Generates a JumpProblem from a JumpSystem. - -Continuing the example from the [`DiscreteProblem`](@ref) definition: - -```julia -jprob = JumpProblem(complete(js), dprob, Direct()) -sol = solve(jprob, SSAStepper()) -``` -""" -function JumpProcesses.JumpProblem(js::JumpSystem, prob, - aggregator = JumpProcesses.NullAggregator(); callback = nothing, - eval_expression = false, eval_module = @__MODULE__, kwargs...) - if !iscomplete(js) - error("A completed `JumpSystem` is required. Call `complete` or `structural_simplify` on the system before creating a `JumpProblem`") - end - unknowntoid = Dict(value(unknown) => i for (i, unknown) in enumerate(unknowns(js))) - eqs = equations(js) - invttype = prob.tspan[1] === nothing ? Float64 : typeof(1 / prob.tspan[2]) - - # handling parameter substitution and empty param vecs - p = (prob.p isa DiffEqBase.NullParameters || prob.p === nothing) ? Num[] : prob.p - - majpmapper = JumpSysMajParamMapper(js, p; jseqs = eqs, rateconsttype = invttype) - majs = isempty(eqs.x[1]) ? nothing : assemble_maj(eqs.x[1], unknowntoid, majpmapper) - crjs = ConstantRateJump[assemble_crj(js, j, unknowntoid; eval_expression, eval_module) - for j in eqs.x[2]] - vrjs = VariableRateJump[assemble_vrj(js, j, unknowntoid; eval_expression, eval_module) - for j in eqs.x[3]] - if prob isa DiscreteProblem - if (!isempty(vrjs) || has_equations(js) || !isempty(continuous_events(js))) - error("Use continuous problems such as an ODEProblem or a SDEProblem with VariableRateJumps, coupled differential equations, or continuous events.") - end - end - jset = JumpSet(Tuple(vrjs), Tuple(crjs), nothing, majs) - - # dep graphs are only for constant rate jumps - nonvrjs = ArrayPartition(eqs.x[1], eqs.x[2]) - if needs_vartojumps_map(aggregator) || needs_depgraph(aggregator) || - (aggregator isa JumpProcesses.NullAggregator) - jdeps = asgraph(js; eqs = nonvrjs) - vdeps = variable_dependencies(js; eqs = nonvrjs) - vtoj = jdeps.badjlist - jtov = vdeps.badjlist - jtoj = needs_depgraph(aggregator) ? eqeq_dependencies(jdeps, vdeps).fadjlist : - nothing - else - vtoj = nothing - jtov = nothing - jtoj = nothing - end - - # handle events, making sure to reset aggregators in the generated affect functions - cbs = process_events(js; callback, eval_expression, eval_module, - postprocess_affect_expr! = _reset_aggregator!) - - JumpProblem(prob, aggregator, jset; dep_graph = jtoj, vartojumps_map = vtoj, - jumptovars_map = jtov, scale_rates = false, nocopy = true, - callback = cbs, kwargs...) -end - -# create the initial parameter vector for use in a MassActionJump -function (ratemap::JumpSysMajParamMapper{ - U, - V, - W -})(params) where {U <: AbstractArray, - V <: AbstractArray, W} - updateparams!(ratemap, params) - [convert(W, value(substitute(paramexpr, ratemap.subdict))) - for paramexpr in ratemap.paramexprs] -end - -supports_initialization(::JumpSystem) = false From c41b0cc23e1aa14df60c56138b2d21fc9806a24a Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Sun, 20 Apr 2025 21:50:27 +0530 Subject: [PATCH 055/185] feat: implement `NonlinearLeastSquaresProblem` for `System` --- src/problems/nonlinearproblem.jl | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/src/problems/nonlinearproblem.jl b/src/problems/nonlinearproblem.jl index 6c29d5e404..646322816c 100644 --- a/src/problems/nonlinearproblem.jl +++ b/src/problems/nonlinearproblem.jl @@ -67,8 +67,23 @@ end f, u0, p, StandardNonlinearProblem(); kwargs...)) end +@fallback_iip_specialize function SciMLBase.NonlinearLeastSquaresProblem{iip, spec}( + sys::System, u0map, parammap = DiffEqBase.NullParameters(); check_length = false, + check_compatibility = true, kwargs...) where {iip, spec} + check_complete(sys, NonlinearLeastSquaresProblem) + check_compatibility && check_compatible_system(NonlinearLeastSquaresProblem, sys) + + f, u0, p = process_SciMLProblem(NonlinearFunction{iip}, sys, u0map, parammap; + check_length, kwargs...) + + kwargs = process_kwargs(sys; kwargs...) + # Call `remake` so it runs initialization if it is trivial + return remake(NonlinearLeastSquaresProblem{iip}(f, u0, p; kwargs...)) +end + function check_compatible_system( - T::Union{Type{NonlinearFunction}, Type{NonlinearProblem}}, sys::System) + T::Union{Type{NonlinearFunction}, Type{NonlinearProblem}, + Type{NonlinearLeastSquaresProblem}}, sys::System) check_time_independent(sys, T) check_not_dde(sys) check_no_cost(sys, T) @@ -76,3 +91,13 @@ function check_compatible_system( check_no_jumps(sys, T) check_no_noise(sys, T) end + +function calculate_resid_prototype(N, u0, p) + u0ElType = u0 === nothing ? Float64 : eltype(u0) + if SciMLStructures.isscimlstructure(p) + u0ElType = promote_type( + eltype(SciMLStructures.canonicalize(SciMLStructures.Tunable(), p)[1]), + u0ElType) + end + return zeros(u0ElType, N) +end From 81bbe70db4e7274cfd7acd1bb864f3c824877f99 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Sun, 20 Apr 2025 21:52:29 +0530 Subject: [PATCH 056/185] refactor: port `SCCNonlinearProblem` to separate file --- src/problems/sccnonlinearproblem.jl | 246 ++++++++++++++++++++++++++++ 1 file changed, 246 insertions(+) create mode 100644 src/problems/sccnonlinearproblem.jl diff --git a/src/problems/sccnonlinearproblem.jl b/src/problems/sccnonlinearproblem.jl new file mode 100644 index 0000000000..864dcb71ee --- /dev/null +++ b/src/problems/sccnonlinearproblem.jl @@ -0,0 +1,246 @@ +const TypeT = Union{DataType, UnionAll} + +struct CacheWriter{F} + fn::F +end + +function (cw::CacheWriter)(p, sols) + cw.fn(p.caches, sols, p) +end + +function CacheWriter(sys::AbstractSystem, buffer_types::Vector{TypeT}, + exprs::Dict{TypeT, Vector{Any}}, solsyms, obseqs::Vector{Equation}; + eval_expression = false, eval_module = @__MODULE__, cse = true) + ps = parameters(sys; initial_parameters = true) + rps = reorder_parameters(sys, ps) + obs_assigns = [eq.lhs ← eq.rhs for eq in obseqs] + body = map(eachindex(buffer_types), buffer_types) do i, T + Symbol(:tmp, i) ← SetArray(true, :(out[$i]), get(exprs, T, [])) + end + + function argument_name(i::Int) + if i <= length(solsyms) + return :($(generated_argument_name(1))[$i]) + end + return generated_argument_name(i - length(solsyms)) + end + array_assignments = array_variable_assignments(solsyms...; argument_name) + fn = build_function_wrapper( + sys, nothing, :out, + DestructuredArgs(DestructuredArgs.(solsyms), generated_argument_name(1)), + rps...; p_start = 3, p_end = length(rps) + 2, + expression = Val{true}, add_observed = false, cse, + extra_assignments = [array_assignments; obs_assigns; body]) + fn = eval_or_rgf(fn; eval_expression, eval_module) + fn = GeneratedFunctionWrapper{(3, 3, is_split(sys))}(fn, nothing) + return CacheWriter(fn) +end + +struct SCCNonlinearFunction{iip} end + +function SCCNonlinearFunction{iip}( + sys::System, _eqs, _dvs, _obs, cachesyms; eval_expression = false, + eval_module = @__MODULE__, cse = true, kwargs...) where {iip} + ps = parameters(sys; initial_parameters = true) + rps = reorder_parameters(sys, ps) + + obs_assignments = [eq.lhs ← eq.rhs for eq in _obs] + + rhss = [eq.rhs - eq.lhs for eq in _eqs] + f_gen = build_function_wrapper(sys, + rhss, _dvs, rps..., cachesyms...; p_start = 2, + p_end = length(rps) + length(cachesyms) + 1, add_observed = false, + extra_assignments = obs_assignments, expression = Val{true}, cse) + f_oop, f_iip = eval_or_rgf.(f_gen; eval_expression, eval_module) + f = GeneratedFunctionWrapper{(2, 2, is_split(sys))}(f_oop, f_iip) + + subsys = NonlinearSystem(_eqs, _dvs, ps; observed = _obs, + parameter_dependencies = parameter_dependencies(sys), name = nameof(sys)) + if get_index_cache(sys) !== nothing + @set! subsys.index_cache = subset_unknowns_observed( + get_index_cache(sys), sys, _dvs, getproperty.(_obs, (:lhs,))) + @set! subsys.complete = true + end + + return NonlinearFunction{iip}(f; sys = subsys) +end + +function SciMLBase.SCCNonlinearProblem(sys::NonlinearSystem, args...; kwargs...) + SCCNonlinearProblem{true}(sys, args...; kwargs...) +end + +function SciMLBase.SCCNonlinearProblem{iip}(sys::NonlinearSystem, u0map, + parammap = SciMLBase.NullParameters(); eval_expression = false, eval_module = @__MODULE__, + cse = true, kwargs...) where {iip} + if !iscomplete(sys) || get_tearing_state(sys) === nothing + error("A simplified `NonlinearSystem` is required. Call `structural_simplify` on the system before creating an `SCCNonlinearProblem`.") + end + + if !is_split(sys) + error("The system has been simplified with `split = false`. `SCCNonlinearProblem` is not compatible with this system. Pass `split = true` to `structural_simplify` to use `SCCNonlinearProblem`.") + end + + ts = get_tearing_state(sys) + var_eq_matching, var_sccs = StructuralTransformations.algebraic_variables_scc(ts) + + if length(var_sccs) == 1 + return NonlinearProblem{iip}( + sys, u0map, parammap; eval_expression, eval_module, kwargs...) + end + + condensed_graph = MatchedCondensationGraph( + DiCMOBiGraph{true}(complete(ts.structure.graph), + complete(var_eq_matching)), + var_sccs) + toporder = topological_sort_by_dfs(condensed_graph) + var_sccs = var_sccs[toporder] + eq_sccs = map(Base.Fix1(getindex, var_eq_matching), var_sccs) + + dvs = unknowns(sys) + ps = parameters(sys) + eqs = equations(sys) + obs = observed(sys) + + _, u0, p = process_SciMLProblem( + EmptySciMLFunction, sys, u0map, parammap; eval_expression, eval_module, kwargs...) + + explicitfuns = [] + nlfuns = [] + prevobsidxs = BlockArray(undef_blocks, Vector{Int}, Int[]) + # Cache buffer types and corresponding sizes. Stored as a pair of arrays instead of a + # dict to maintain a consistent order of buffers across SCCs + cachetypes = TypeT[] + cachesizes = Int[] + # explicitfun! related information for each SCC + # We need to compute buffer sizes before doing any codegen + scc_cachevars = Dict{TypeT, Vector{Any}}[] + scc_cacheexprs = Dict{TypeT, Vector{Any}}[] + scc_eqs = Vector{Equation}[] + scc_obs = Vector{Equation}[] + # variables solved in previous SCCs + available_vars = Set() + for (i, (escc, vscc)) in enumerate(zip(eq_sccs, var_sccs)) + # subset unknowns and equations + _dvs = dvs[vscc] + _eqs = eqs[escc] + # get observed equations required by this SCC + union!(available_vars, _dvs) + obsidxs = observed_equations_used_by(sys, _eqs; available_vars) + # the ones used by previous SCCs can be precomputed into the cache + setdiff!(obsidxs, prevobsidxs) + _obs = obs[obsidxs] + union!(available_vars, getproperty.(_obs, (:lhs,))) + + # get all subexpressions in the RHS which we can precompute in the cache + # precomputed subexpressions should not contain `banned_vars` + banned_vars = Set{Any}(vcat(_dvs, getproperty.(_obs, (:lhs,)))) + state = Dict() + for i in eachindex(_obs) + _obs[i] = _obs[i].lhs ~ subexpressions_not_involving_vars!( + _obs[i].rhs, banned_vars, state) + end + for i in eachindex(_eqs) + _eqs[i] = _eqs[i].lhs ~ subexpressions_not_involving_vars!( + _eqs[i].rhs, banned_vars, state) + end + + # map from symtype to cached variables and their expressions + cachevars = Dict{Union{DataType, UnionAll}, Vector{Any}}() + cacheexprs = Dict{Union{DataType, UnionAll}, Vector{Any}}() + # observed of previous SCCs are in the cache + # NOTE: When we get proper CSE, we can substitute these + # and then use `subexpressions_not_involving_vars!` + for i in prevobsidxs + T = symtype(obs[i].lhs) + buf = get!(() -> Any[], cachevars, T) + push!(buf, obs[i].lhs) + + buf = get!(() -> Any[], cacheexprs, T) + push!(buf, obs[i].lhs) + end + + for (k, v) in state + k = unwrap(k) + v = unwrap(v) + T = symtype(k) + buf = get!(() -> Any[], cachevars, T) + push!(buf, v) + buf = get!(() -> Any[], cacheexprs, T) + push!(buf, k) + end + + # update the sizes of cache buffers + for (T, buf) in cachevars + idx = findfirst(isequal(T), cachetypes) + if idx === nothing + push!(cachetypes, T) + push!(cachesizes, 0) + idx = lastindex(cachetypes) + end + cachesizes[idx] = max(cachesizes[idx], length(buf)) + end + + push!(scc_cachevars, cachevars) + push!(scc_cacheexprs, cacheexprs) + push!(scc_eqs, _eqs) + push!(scc_obs, _obs) + blockpush!(prevobsidxs, obsidxs) + end + + for (i, (escc, vscc)) in enumerate(zip(eq_sccs, var_sccs)) + _dvs = dvs[vscc] + _eqs = scc_eqs[i] + _prevobsidxs = reduce(vcat, blocks(prevobsidxs)[1:(i - 1)]; init = Int[]) + _obs = scc_obs[i] + cachevars = scc_cachevars[i] + cacheexprs = scc_cacheexprs[i] + available_vars = [dvs[reduce(vcat, var_sccs[1:(i - 1)]; init = Int[])]; + getproperty.( + reduce(vcat, scc_obs[1:(i - 1)]; init = []), (:lhs,))] + _prevobsidxs = vcat(_prevobsidxs, + observed_equations_used_by( + sys, reduce(vcat, values(cacheexprs); init = []); available_vars)) + if isempty(cachevars) + push!(explicitfuns, Returns(nothing)) + else + solsyms = getindex.((dvs,), view(var_sccs, 1:(i - 1))) + push!(explicitfuns, + CacheWriter(sys, cachetypes, cacheexprs, solsyms, obs[_prevobsidxs]; + eval_expression, eval_module, cse)) + end + + cachebufsyms = Tuple(map(cachetypes) do T + get(cachevars, T, []) + end) + f = SCCNonlinearFunction{iip}( + sys, _eqs, _dvs, _obs, cachebufsyms; eval_expression, eval_module, cse, kwargs...) + push!(nlfuns, f) + end + + if !isempty(cachetypes) + templates = map(cachetypes, cachesizes) do T, n + # Real refers to `eltype(u0)` + if T == Real + T = eltype(u0) + elseif T <: Array && eltype(T) == Real + T = Array{eltype(u0), ndims(T)} + end + BufferTemplate(T, n) + end + p = rebuild_with_caches(p, templates...) + end + + subprobs = [] + for (f, vscc) in zip(nlfuns, var_sccs) + prob = NonlinearProblem(f, u0[vscc], p) + push!(subprobs, prob) + end + + new_dvs = dvs[reduce(vcat, var_sccs)] + new_eqs = eqs[reduce(vcat, eq_sccs)] + @set! sys.unknowns = new_dvs + @set! sys.eqs = new_eqs + @set! sys.index_cache = subset_unknowns_observed( + get_index_cache(sys), sys, new_dvs, getproperty.(obs, (:lhs,))) + return SCCNonlinearProblem(subprobs, explicitfuns, p, true; sys) +end From 83a82c630030d93d447f77ee6422b05b69634d0b Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Sun, 20 Apr 2025 21:52:37 +0530 Subject: [PATCH 057/185] refactor: remove `nonlinearsystem.jl` --- src/ModelingToolkit.jl | 3 +- src/systems/nonlinear/nonlinearsystem.jl | 1004 ---------------------- src/systems/system.jl | 16 +- 3 files changed, 14 insertions(+), 1009 deletions(-) delete mode 100644 src/systems/nonlinear/nonlinearsystem.jl diff --git a/src/ModelingToolkit.jl b/src/ModelingToolkit.jl index 2e9a4503fb..b8dfe2e882 100644 --- a/src/ModelingToolkit.jl +++ b/src/ModelingToolkit.jl @@ -166,7 +166,6 @@ include("systems/optimization/constraints_system.jl") include("systems/optimization/optimizationsystem.jl") include("systems/optimization/modelingtoolkitize.jl") -include("systems/nonlinear/nonlinearsystem.jl") include("systems/nonlinear/homotopy_continuation.jl") include("systems/diffeqs/abstractodesystem.jl") include("systems/nonlinear/modelingtoolkitize.jl") @@ -270,7 +269,7 @@ export IntervalNonlinearProblem, IntervalNonlinearProblemExpr export OptimizationProblem, OptimizationProblemExpr, constraints export SteadyStateProblem, SteadyStateProblemExpr export JumpProblem -export NonlinearSystem, OptimizationSystem, ConstraintsSystem +export OptimizationSystem, ConstraintsSystem export alias_elimination, flatten export connect, domain_connect, @connector, Connection, AnalysisPoint, Flow, Stream, instream diff --git a/src/systems/nonlinear/nonlinearsystem.jl b/src/systems/nonlinear/nonlinearsystem.jl deleted file mode 100644 index 7146fb6b5e..0000000000 --- a/src/systems/nonlinear/nonlinearsystem.jl +++ /dev/null @@ -1,1004 +0,0 @@ -""" -$(TYPEDEF) - -A nonlinear system of equations. - -# Fields -$(FIELDS) - -# Examples - -```julia -@variables x y z -@parameters σ ρ β - -eqs = [0 ~ σ*(y-x), - 0 ~ x*(ρ-z)-y, - 0 ~ x*y - β*z] -@named ns = NonlinearSystem(eqs, [x,y,z],[σ,ρ,β]) -``` -""" -struct NonlinearSystem <: AbstractTimeIndependentSystem - """ - A tag for the system. If two systems have the same tag, then they are - structurally identical. - """ - tag::UInt - """Vector of equations defining the system.""" - eqs::Vector{Equation} - """Unknown variables.""" - unknowns::Vector - """Parameters.""" - ps::Vector - """Array variables.""" - var_to_name::Any - """Observed equations.""" - observed::Vector{Equation} - """ - Jacobian matrix. Note: this field will not be defined until - [`calculate_jacobian`](@ref) is called on the system. - """ - jac::RefValue{Any} - """ - The name of the system. - """ - name::Symbol - """ - A description of the system. - """ - description::String - """ - The internal systems. These are required to have unique names. - """ - systems::Vector{NonlinearSystem} - """ - The default values to use when initial conditions and/or - parameters are not supplied in `ODEProblem`. - """ - defaults::Dict - """ - The guesses to use as the initial conditions for the - initialization system. - """ - guesses::Dict - """ - The system for performing the initialization. - """ - initializesystem::Union{Nothing, NonlinearSystem} - """ - Extra equations to be enforced during the initialization sequence. - """ - initialization_eqs::Vector{Equation} - """ - Type of the system. - """ - connector_type::Any - """ - Topologically sorted parameter dependency equations, where all symbols are parameters and - the LHS is a single parameter. - """ - parameter_dependencies::Vector{Equation} - """ - Metadata for the system, to be used by downstream packages. - """ - metadata::Any - """ - Metadata for MTK GUI. - """ - gui_metadata::Union{Nothing, GUIMetadata} - """ - Whether this is an initialization system. - """ - is_initializesystem::Bool - """ - Cache for intermediate tearing state. - """ - tearing_state::Any - """ - Substitutions generated by tearing. - """ - substitutions::Any - """ - If false, then `sys.x` no longer performs namespacing. - """ - namespacing::Bool - """ - If true, denotes the model will not be modified any further. - """ - complete::Bool - """ - Cached data for fast symbolic indexing. - """ - index_cache::Union{Nothing, IndexCache} - """ - The hierarchical parent system before simplification. - """ - parent::Any - isscheduled::Bool - - function NonlinearSystem( - tag, eqs, unknowns, ps, var_to_name, observed, jac, name, description, - systems, defaults, guesses, initializesystem, initialization_eqs, connector_type, - parameter_dependencies = Equation[], metadata = nothing, gui_metadata = nothing, - is_initializesystem = false, - tearing_state = nothing, substitutions = nothing, namespacing = true, - complete = false, index_cache = nothing, parent = nothing, - isscheduled = false; checks::Union{Bool, Int} = true) - if checks == true || (checks & CheckUnits) > 0 - u = __get_unit_type(unknowns, ps) - check_units(u, eqs) - check_subsystems(systems) - end - new(tag, eqs, unknowns, ps, var_to_name, observed, jac, name, description, - systems, defaults, guesses, initializesystem, initialization_eqs, - connector_type, parameter_dependencies, metadata, gui_metadata, - is_initializesystem, tearing_state, - substitutions, namespacing, complete, index_cache, parent, isscheduled) - end -end - -function NonlinearSystem(eqs, unknowns, ps; - observed = [], - name = nothing, - description = "", - default_u0 = Dict(), - default_p = Dict(), - defaults = _merge(Dict(default_u0), Dict(default_p)), - guesses = Dict(), - initializesystem = nothing, - initialization_eqs = Equation[], - systems = NonlinearSystem[], - connector_type = nothing, - continuous_events = nothing, # this argument is only required for ODESystems, but is added here for the constructor to accept it without error - discrete_events = nothing, # this argument is only required for ODESystems, but is added here for the constructor to accept it without error - checks = true, - parameter_dependencies = Equation[], - metadata = nothing, - gui_metadata = nothing, - is_initializesystem = false) - continuous_events === nothing || isempty(continuous_events) || - throw(ArgumentError("NonlinearSystem does not accept `continuous_events`, you provided $continuous_events")) - discrete_events === nothing || isempty(discrete_events) || - throw(ArgumentError("NonlinearSystem does not accept `discrete_events`, you provided $discrete_events")) - name === nothing && - throw(ArgumentError("The `name` keyword must be provided. Please consider using the `@named` macro")) - length(unique(nameof.(systems))) == length(systems) || - throw(ArgumentError("System names must be unique.")) - (isempty(default_u0) && isempty(default_p)) || - Base.depwarn( - "`default_u0` and `default_p` are deprecated. Use `defaults` instead.", - :NonlinearSystem, force = true) - - # Accept a single (scalar/vector) equation, but make array for consistent internal handling - if !(eqs isa AbstractArray) - eqs = [eqs] - end - - # Copy equations to canonical form, but do not touch array expressions - eqs = [wrap(eq.lhs) isa Symbolics.Arr ? eq : 0 ~ eq.rhs - eq.lhs for eq in eqs] - - jac = RefValue{Any}(EMPTY_JAC) - - ps′ = value.(ps) - dvs′ = value.(unknowns) - parameter_dependencies, ps′ = process_parameter_dependencies( - parameter_dependencies, ps′) - - defaults = Dict{Any, Any}(todict(defaults)) - guesses = Dict{Any, Any}(todict(guesses)) - var_to_name = Dict() - process_variables!(var_to_name, defaults, guesses, dvs′) - process_variables!(var_to_name, defaults, guesses, ps′) - process_variables!( - var_to_name, defaults, guesses, [eq.lhs for eq in parameter_dependencies]) - process_variables!( - var_to_name, defaults, guesses, [eq.rhs for eq in parameter_dependencies]) - defaults = Dict{Any, Any}(value(k) => value(v) - for (k, v) in pairs(defaults) if v !== nothing) - guesses = Dict{Any, Any}(value(k) => value(v) - for (k, v) in pairs(guesses) if v !== nothing) - - isempty(observed) || collect_var_to_name!(var_to_name, (eq.lhs for eq in observed)) - - NonlinearSystem(Threads.atomic_add!(SYSTEM_COUNT, UInt(1)), - eqs, dvs′, ps′, var_to_name, observed, jac, name, description, systems, defaults, - guesses, initializesystem, initialization_eqs, connector_type, parameter_dependencies, - metadata, gui_metadata, is_initializesystem, checks = checks) -end - -function NonlinearSystem(eqs; kwargs...) - eqs = collect(eqs) - allunknowns = OrderedSet() - ps = OrderedSet() - for eq in eqs - collect_vars!(allunknowns, ps, eq, nothing) - end - for eq in get(kwargs, :parameter_dependencies, Equation[]) - if eq isa Pair - collect_vars!(allunknowns, ps, eq, nothing) - else - collect_vars!(allunknowns, ps, eq, nothing) - end - end - new_ps = OrderedSet() - for p in ps - if iscall(p) && operation(p) === getindex - par = arguments(p)[begin] - if Symbolics.shape(Symbolics.unwrap(par)) !== Symbolics.Unknown() && - all(par[i] in ps for i in eachindex(par)) - push!(new_ps, par) - else - push!(new_ps, p) - end - else - if symbolic_type(p) == ArraySymbolic() && - Symbolics.shape(unwrap(p)) != Symbolics.Unknown() - for i in eachindex(p) - delete!(new_ps, p[i]) - end - end - push!(new_ps, p) - end - end - - return NonlinearSystem(eqs, collect(allunknowns), collect(new_ps); kwargs...) -end - -""" - $(TYPEDSIGNATURES) - -Convert an `ODESystem` to a `NonlinearSystem` solving for its steady state (where derivatives are zero). -Any differential variable `D(x) ~ f(...)` will be turned into `0 ~ f(...)`. The returned system is not -simplified. If the input system is `complete`d, then so will the returned system. -""" -function NonlinearSystem(sys::AbstractODESystem) - eqs = equations(sys) - obs = observed(sys) - subrules = Dict(D(x) => 0.0 for x in unknowns(sys)) - eqs = map(eqs) do eq - fast_substitute(eq, subrules) - end - - nsys = NonlinearSystem(eqs, unknowns(sys), [parameters(sys); get_iv(sys)]; - parameter_dependencies = parameter_dependencies(sys), - defaults = merge(defaults(sys), Dict(get_iv(sys) => Inf)), guesses = guesses(sys), - initialization_eqs = initialization_equations(sys), name = nameof(sys), - observed = obs) - if iscomplete(sys) - nsys = complete(nsys; split = is_split(sys)) - end - return nsys -end - -function calculate_jacobian(sys::NonlinearSystem; sparse = false, simplify = false) - cache = get_jac(sys)[] - if cache isa Tuple && cache[2] == (sparse, simplify) - return cache[1] - end - - # observed equations may depend on unknowns, so substitute them in first - # TODO: rather keep observed derivatives unexpanded, like "Differential(obs)(expr)"? - obs = Dict(eq.lhs => eq.rhs for eq in observed(sys)) - rhs = map(eq -> fixpoint_sub(eq.rhs, obs), equations(sys)) - vals = [dv for dv in unknowns(sys)] - - if sparse - jac = sparsejacobian(rhs, vals, simplify = simplify) - else - jac = jacobian(rhs, vals, simplify = simplify) - end - get_jac(sys)[] = jac, (sparse, simplify) - return jac -end - -function generate_jacobian( - sys::NonlinearSystem, vs = unknowns(sys), ps = parameters( - sys; initial_parameters = true); - sparse = false, simplify = false, kwargs...) - jac = calculate_jacobian(sys, sparse = sparse, simplify = simplify) - p = reorder_parameters(sys, ps) - return build_function_wrapper(sys, jac, vs, p...; kwargs...) -end - -function calculate_hessian(sys::NonlinearSystem; sparse = false, simplify = false) - obs = Dict(eq.lhs => eq.rhs for eq in observed(sys)) - rhs = map(eq -> fixpoint_sub(eq.rhs, obs), equations(sys)) - vals = [dv for dv in unknowns(sys)] - if sparse - hess = [sparsehessian(rhs[i], vals, simplify = simplify) for i in 1:length(rhs)] - else - hess = [hessian(rhs[i], vals, simplify = simplify) for i in 1:length(rhs)] - end - return hess -end - -function generate_hessian( - sys::NonlinearSystem, vs = unknowns(sys), ps = parameters( - sys; initial_parameters = true); - sparse = false, simplify = false, kwargs...) - hess = calculate_hessian(sys, sparse = sparse, simplify = simplify) - p = reorder_parameters(sys, ps) - return build_function_wrapper(sys, hess, vs, p...; kwargs...) -end - -function generate_function( - sys::NonlinearSystem, dvs = unknowns(sys), ps = parameters( - sys; initial_parameters = true); - scalar = false, kwargs...) - rhss = [deq.rhs for deq in equations(sys)] - dvs′ = value.(dvs) - if scalar - rhss = only(rhss) - dvs′ = only(dvs) - end - p = reorder_parameters(sys, value.(ps)) - return build_function_wrapper(sys, rhss, dvs′, p...; kwargs...) -end - -function jacobian_sparsity(sys::NonlinearSystem) - jacobian_sparsity([eq.rhs for eq in equations(sys)], - unknowns(sys)) -end - -function hessian_sparsity(sys::NonlinearSystem) - [hessian_sparsity(eq.rhs, - unknowns(sys)) for eq in equations(sys)] -end - -function calculate_resid_prototype(N, u0, p) - u0ElType = u0 === nothing ? Float64 : eltype(u0) - if SciMLStructures.isscimlstructure(p) - u0ElType = promote_type( - eltype(SciMLStructures.canonicalize(SciMLStructures.Tunable(), p)[1]), - u0ElType) - end - return zeros(u0ElType, N) -end - -""" -```julia -SciMLBase.NonlinearFunction{iip}(sys::NonlinearSystem, dvs = unknowns(sys), - ps = parameters(sys); - version = nothing, - jac = false, - sparse = false, - kwargs...) where {iip} -``` - -Create a `NonlinearFunction` from the [`NonlinearSystem`](@ref). The arguments -`dvs` and `ps` are used to set the order of the dependent variable and parameter -vectors, respectively. -""" -function SciMLBase.NonlinearFunction(sys::NonlinearSystem, args...; kwargs...) - NonlinearFunction{true}(sys, args...; kwargs...) -end - -function SciMLBase.NonlinearFunction{iip}(sys::NonlinearSystem, dvs = unknowns(sys), - ps = parameters(sys), u0 = nothing; p = nothing, - version = nothing, - jac = false, - eval_expression = false, - eval_module = @__MODULE__, - sparse = false, simplify = false, - initialization_data = nothing, cse = true, - kwargs...) where {iip} - if !iscomplete(sys) - error("A completed `NonlinearSystem` is required. Call `complete` or `structural_simplify` on the system before creating a `NonlinearFunction`") - end - f_gen = generate_function(sys, dvs, ps; expression = Val{true}, cse, kwargs...) - f_oop, f_iip = eval_or_rgf.(f_gen; eval_expression, eval_module) - f = GeneratedFunctionWrapper{(2, 2, is_split(sys))}(f_oop, f_iip) - - if jac - jac_gen = generate_jacobian(sys, dvs, ps; - simplify = simplify, sparse = sparse, - expression = Val{true}, cse, kwargs...) - jac_oop, jac_iip = eval_or_rgf.(jac_gen; eval_expression, eval_module) - _jac = GeneratedFunctionWrapper{(2, 2, is_split(sys))}(jac_oop, jac_iip) - else - _jac = nothing - end - - observedfun = ObservedFunctionCache( - sys; eval_expression, eval_module, checkbounds = get(kwargs, :checkbounds, false), cse) - - if length(dvs) == length(equations(sys)) - resid_prototype = nothing - else - resid_prototype = calculate_resid_prototype(length(equations(sys)), u0, p) - end - - NonlinearFunction{iip}(f; - sys = sys, - jac = _jac === nothing ? nothing : _jac, - resid_prototype = resid_prototype, - jac_prototype = sparse ? - similar(calculate_jacobian(sys, sparse = sparse), - Float64) : nothing, - observed = observedfun, initialization_data) -end - -""" -$(TYPEDSIGNATURES) - -Create an `IntervalNonlinearFunction` from the [`NonlinearSystem`](@ref). The arguments -`dvs` and `ps` are used to set the order of the dependent variable and parameter vectors, -respectively. -""" -function SciMLBase.IntervalNonlinearFunction( - sys::NonlinearSystem, dvs = unknowns(sys), ps = parameters(sys), u0 = nothing; - p = nothing, eval_expression = false, eval_module = @__MODULE__, - initialization_data = nothing, kwargs...) - if !iscomplete(sys) - error("A completed `NonlinearSystem` is required. Call `complete` or `structural_simplify` on the system before creating a `IntervalNonlinearFunction`") - end - if !isone(length(dvs)) || !isone(length(equations(sys))) - error("`IntervalNonlinearFunction` only supports systems with a single equation and a single unknown.") - end - - f_gen = generate_function( - sys, dvs, ps; expression = Val{true}, scalar = true, kwargs...) - f = eval_or_rgf(f_gen; eval_expression, eval_module) - f = GeneratedFunctionWrapper{(2, 2, is_split(sys))}(f, nothing) - - observedfun = ObservedFunctionCache( - sys; eval_expression, eval_module, checkbounds = get(kwargs, :checkbounds, false)) - - IntervalNonlinearFunction{false}( - f; observed = observedfun, sys = sys, initialization_data) -end - -""" -```julia -SciMLBase.NonlinearFunctionExpr{iip}(sys::NonlinearSystem, dvs = unknowns(sys), - ps = parameters(sys); - version = nothing, - jac = false, - sparse = false, - kwargs...) where {iip} -``` - -Create a Julia expression for a `NonlinearFunction` from the [`NonlinearSystem`](@ref). -The arguments `dvs` and `ps` are used to set the order of the dependent -variable and parameter vectors, respectively. -""" -struct NonlinearFunctionExpr{iip} end - -function NonlinearFunctionExpr{iip}(sys::NonlinearSystem, dvs = unknowns(sys), - ps = parameters(sys), u0 = nothing; p = nothing, - version = nothing, tgrad = false, - jac = false, - linenumbers = false, - sparse = false, simplify = false, - kwargs...) where {iip} - if !iscomplete(sys) - error("A completed `NonlinearSystem` is required. Call `complete` or `structural_simplify` on the system before creating a `NonlinearFunctionExpr`") - end - f_oop, f_iip = generate_function(sys, dvs, ps; expression = Val{true}, kwargs...) - f = :($(GeneratedFunctionWrapper{(2, 2, is_split(sys))})($f_oop, $f_iip)) - - if jac - jac_oop, jac_iip = generate_jacobian(sys, dvs, ps; - sparse = sparse, simplify = simplify, - expression = Val{true}, kwargs...) - _jac = :($(GeneratedFunctionWrapper{(2, 2, is_split(sys))})($jac_oop, $jac_iip)) - else - _jac = :nothing - end - - jp_expr = sparse ? :(similar($(get_jac(sys)[]), Float64)) : :nothing - if length(dvs) == length(equations(sys)) - resid_expr = :nothing - else - u0ElType = u0 === nothing ? Float64 : eltype(u0) - if SciMLStructures.isscimlstructure(p) - u0ElType = promote_type( - eltype(SciMLStructures.canonicalize(SciMLStructures.Tunable(), p)[1]), - u0ElType) - end - - resid_expr = :(zeros($u0ElType, $(length(equations(sys))))) - end - ex = quote - f = $f - jac = $_jac - NonlinearFunction{$iip}(f, - jac = jac, - resid_prototype = resid_expr, - jac_prototype = $jp_expr) - end - !linenumbers ? Base.remove_linenums!(ex) : ex -end - -""" -$(TYPEDSIGNATURES) - -Create a Julia expression for an `IntervalNonlinearFunction` from the -[`NonlinearSystem`](@ref). The arguments `dvs` and `ps` are used to set the order of the -dependent variable and parameter vectors, respectively. -""" -function IntervalNonlinearFunctionExpr( - sys::NonlinearSystem, dvs = unknowns(sys), ps = parameters(sys), - u0 = nothing; p = nothing, linenumbers = false, kwargs...) - if !iscomplete(sys) - error("A completed `NonlinearSystem` is required. Call `complete` or `structural_simplify` on the system before creating a `IntervalNonlinearFunctionExpr`") - end - if !isone(length(dvs)) || !isone(length(equations(sys))) - error("`IntervalNonlinearFunctionExpr` only supports systems with a single equation and a single unknown.") - end - - f = generate_function(sys, dvs, ps; expression = Val{true}, scalar = true, kwargs...) - f = :($(GeneratedFunctionWrapper{2, 2, is_split(sys)})($f, nothing)) - - ex = quote - f = $f - NonlinearFunction{false}(f) - end - !linenumbers ? Base.remove_linenums!(ex) : ex -end - -""" -```julia -DiffEqBase.NonlinearProblem{iip}(sys::NonlinearSystem, u0map, - parammap = DiffEqBase.NullParameters(); - jac = false, sparse = false, - checkbounds = false, - linenumbers = true, parallel = SerialForm(), - kwargs...) where {iip} -``` - -Generates an NonlinearProblem from a NonlinearSystem and allows for automatically -symbolically calculating numerical enhancements. -""" -function DiffEqBase.NonlinearProblem(sys::NonlinearSystem, args...; kwargs...) - NonlinearProblem{true}(sys, args...; kwargs...) -end - -function DiffEqBase.NonlinearProblem{iip}(sys::NonlinearSystem, u0map, - parammap = DiffEqBase.NullParameters(); - check_length = true, kwargs...) where {iip} - if !iscomplete(sys) - error("A completed `NonlinearSystem` is required. Call `complete` or `structural_simplify` on the system before creating a `NonlinearProblem`") - end - f, u0, p = process_SciMLProblem(NonlinearFunction{iip}, sys, u0map, parammap; - check_length, kwargs...) - pt = something(get_metadata(sys), StandardNonlinearProblem()) - # Call `remake` so it runs initialization if it is trivial - return remake(NonlinearProblem{iip}(f, u0, p, pt; filter_kwargs(kwargs)...)) -end - -function DiffEqBase.NonlinearProblem(sys::AbstractODESystem, args...; kwargs...) - NonlinearProblem(NonlinearSystem(sys), args...; kwargs...) -end - -""" -```julia -DiffEqBase.NonlinearLeastSquaresProblem{iip}(sys::NonlinearSystem, u0map, - parammap = DiffEqBase.NullParameters(); - jac = false, sparse = false, - checkbounds = false, - linenumbers = true, parallel = SerialForm(), - kwargs...) where {iip} -``` - -Generates an NonlinearProblem from a NonlinearSystem and allows for automatically -symbolically calculating numerical enhancements. -""" -function DiffEqBase.NonlinearLeastSquaresProblem(sys::NonlinearSystem, args...; kwargs...) - NonlinearLeastSquaresProblem{true}(sys, args...; kwargs...) -end - -function DiffEqBase.NonlinearLeastSquaresProblem{iip}(sys::NonlinearSystem, u0map, - parammap = DiffEqBase.NullParameters(); - check_length = false, kwargs...) where {iip} - if !iscomplete(sys) - error("A completed `NonlinearSystem` is required. Call `complete` or `structural_simplify` on the system before creating a `NonlinearLeastSquaresProblem`") - end - f, u0, p = process_SciMLProblem(NonlinearFunction{iip}, sys, u0map, parammap; - check_length, kwargs...) - pt = something(get_metadata(sys), StandardNonlinearProblem()) - # Call `remake` so it runs initialization if it is trivial - return remake(NonlinearLeastSquaresProblem{iip}(f, u0, p; filter_kwargs(kwargs)...)) -end - -const TypeT = Union{DataType, UnionAll} - -struct CacheWriter{F} - fn::F -end - -function (cw::CacheWriter)(p, sols) - cw.fn(p.caches, sols, p) -end - -function CacheWriter(sys::AbstractSystem, buffer_types::Vector{TypeT}, - exprs::Dict{TypeT, Vector{Any}}, solsyms, obseqs::Vector{Equation}; - eval_expression = false, eval_module = @__MODULE__, cse = true) - ps = parameters(sys; initial_parameters = true) - rps = reorder_parameters(sys, ps) - obs_assigns = [eq.lhs ← eq.rhs for eq in obseqs] - body = map(eachindex(buffer_types), buffer_types) do i, T - Symbol(:tmp, i) ← SetArray(true, :(out[$i]), get(exprs, T, [])) - end - - function argument_name(i::Int) - if i <= length(solsyms) - return :($(generated_argument_name(1))[$i]) - end - return generated_argument_name(i - length(solsyms)) - end - array_assignments = array_variable_assignments(solsyms...; argument_name) - fn = build_function_wrapper( - sys, nothing, :out, - DestructuredArgs(DestructuredArgs.(solsyms), generated_argument_name(1)), - rps...; p_start = 3, p_end = length(rps) + 2, - expression = Val{true}, add_observed = false, cse, - extra_assignments = [array_assignments; obs_assigns; body]) - fn = eval_or_rgf(fn; eval_expression, eval_module) - fn = GeneratedFunctionWrapper{(3, 3, is_split(sys))}(fn, nothing) - return CacheWriter(fn) -end - -struct SCCNonlinearFunction{iip} end - -function SCCNonlinearFunction{iip}( - sys::NonlinearSystem, _eqs, _dvs, _obs, cachesyms; eval_expression = false, - eval_module = @__MODULE__, cse = true, kwargs...) where {iip} - ps = parameters(sys; initial_parameters = true) - rps = reorder_parameters(sys, ps) - - obs_assignments = [eq.lhs ← eq.rhs for eq in _obs] - - rhss = [eq.rhs - eq.lhs for eq in _eqs] - f_gen = build_function_wrapper(sys, - rhss, _dvs, rps..., cachesyms...; p_start = 2, - p_end = length(rps) + length(cachesyms) + 1, add_observed = false, - extra_assignments = obs_assignments, expression = Val{true}, cse) - f_oop, f_iip = eval_or_rgf.(f_gen; eval_expression, eval_module) - f = GeneratedFunctionWrapper{(2, 2, is_split(sys))}(f_oop, f_iip) - - subsys = NonlinearSystem(_eqs, _dvs, ps; observed = _obs, - parameter_dependencies = parameter_dependencies(sys), name = nameof(sys)) - if get_index_cache(sys) !== nothing - @set! subsys.index_cache = subset_unknowns_observed( - get_index_cache(sys), sys, _dvs, getproperty.(_obs, (:lhs,))) - @set! subsys.complete = true - end - - return NonlinearFunction{iip}(f; sys = subsys) -end - -function SciMLBase.SCCNonlinearProblem(sys::NonlinearSystem, args...; kwargs...) - SCCNonlinearProblem{true}(sys, args...; kwargs...) -end - -function SciMLBase.SCCNonlinearProblem{iip}(sys::NonlinearSystem, u0map, - parammap = SciMLBase.NullParameters(); eval_expression = false, eval_module = @__MODULE__, - cse = true, kwargs...) where {iip} - if !iscomplete(sys) || get_tearing_state(sys) === nothing - error("A simplified `NonlinearSystem` is required. Call `structural_simplify` on the system before creating an `SCCNonlinearProblem`.") - end - - if !is_split(sys) - error("The system has been simplified with `split = false`. `SCCNonlinearProblem` is not compatible with this system. Pass `split = true` to `structural_simplify` to use `SCCNonlinearProblem`.") - end - - ts = get_tearing_state(sys) - var_eq_matching, var_sccs = StructuralTransformations.algebraic_variables_scc(ts) - - if length(var_sccs) == 1 - return NonlinearProblem{iip}( - sys, u0map, parammap; eval_expression, eval_module, kwargs...) - end - - condensed_graph = MatchedCondensationGraph( - DiCMOBiGraph{true}(complete(ts.structure.graph), - complete(var_eq_matching)), - var_sccs) - toporder = topological_sort_by_dfs(condensed_graph) - var_sccs = var_sccs[toporder] - eq_sccs = map(Base.Fix1(getindex, var_eq_matching), var_sccs) - - dvs = unknowns(sys) - ps = parameters(sys) - eqs = equations(sys) - obs = observed(sys) - - _, u0, p = process_SciMLProblem( - EmptySciMLFunction, sys, u0map, parammap; eval_expression, eval_module, kwargs...) - - explicitfuns = [] - nlfuns = [] - prevobsidxs = BlockArray(undef_blocks, Vector{Int}, Int[]) - # Cache buffer types and corresponding sizes. Stored as a pair of arrays instead of a - # dict to maintain a consistent order of buffers across SCCs - cachetypes = TypeT[] - cachesizes = Int[] - # explicitfun! related information for each SCC - # We need to compute buffer sizes before doing any codegen - scc_cachevars = Dict{TypeT, Vector{Any}}[] - scc_cacheexprs = Dict{TypeT, Vector{Any}}[] - scc_eqs = Vector{Equation}[] - scc_obs = Vector{Equation}[] - # variables solved in previous SCCs - available_vars = Set() - for (i, (escc, vscc)) in enumerate(zip(eq_sccs, var_sccs)) - # subset unknowns and equations - _dvs = dvs[vscc] - _eqs = eqs[escc] - # get observed equations required by this SCC - union!(available_vars, _dvs) - obsidxs = observed_equations_used_by(sys, _eqs; available_vars) - # the ones used by previous SCCs can be precomputed into the cache - setdiff!(obsidxs, prevobsidxs) - _obs = obs[obsidxs] - union!(available_vars, getproperty.(_obs, (:lhs,))) - - # get all subexpressions in the RHS which we can precompute in the cache - # precomputed subexpressions should not contain `banned_vars` - banned_vars = Set{Any}(vcat(_dvs, getproperty.(_obs, (:lhs,)))) - state = Dict() - for i in eachindex(_obs) - _obs[i] = _obs[i].lhs ~ subexpressions_not_involving_vars!( - _obs[i].rhs, banned_vars, state) - end - for i in eachindex(_eqs) - _eqs[i] = _eqs[i].lhs ~ subexpressions_not_involving_vars!( - _eqs[i].rhs, banned_vars, state) - end - - # map from symtype to cached variables and their expressions - cachevars = Dict{Union{DataType, UnionAll}, Vector{Any}}() - cacheexprs = Dict{Union{DataType, UnionAll}, Vector{Any}}() - # observed of previous SCCs are in the cache - # NOTE: When we get proper CSE, we can substitute these - # and then use `subexpressions_not_involving_vars!` - for i in prevobsidxs - T = symtype(obs[i].lhs) - buf = get!(() -> Any[], cachevars, T) - push!(buf, obs[i].lhs) - - buf = get!(() -> Any[], cacheexprs, T) - push!(buf, obs[i].lhs) - end - - for (k, v) in state - k = unwrap(k) - v = unwrap(v) - T = symtype(k) - buf = get!(() -> Any[], cachevars, T) - push!(buf, v) - buf = get!(() -> Any[], cacheexprs, T) - push!(buf, k) - end - - # update the sizes of cache buffers - for (T, buf) in cachevars - idx = findfirst(isequal(T), cachetypes) - if idx === nothing - push!(cachetypes, T) - push!(cachesizes, 0) - idx = lastindex(cachetypes) - end - cachesizes[idx] = max(cachesizes[idx], length(buf)) - end - - push!(scc_cachevars, cachevars) - push!(scc_cacheexprs, cacheexprs) - push!(scc_eqs, _eqs) - push!(scc_obs, _obs) - blockpush!(prevobsidxs, obsidxs) - end - - for (i, (escc, vscc)) in enumerate(zip(eq_sccs, var_sccs)) - _dvs = dvs[vscc] - _eqs = scc_eqs[i] - _prevobsidxs = reduce(vcat, blocks(prevobsidxs)[1:(i - 1)]; init = Int[]) - _obs = scc_obs[i] - cachevars = scc_cachevars[i] - cacheexprs = scc_cacheexprs[i] - available_vars = [dvs[reduce(vcat, var_sccs[1:(i - 1)]; init = Int[])]; - getproperty.( - reduce(vcat, scc_obs[1:(i - 1)]; init = []), (:lhs,))] - _prevobsidxs = vcat(_prevobsidxs, - observed_equations_used_by( - sys, reduce(vcat, values(cacheexprs); init = []); available_vars)) - if isempty(cachevars) - push!(explicitfuns, Returns(nothing)) - else - solsyms = getindex.((dvs,), view(var_sccs, 1:(i - 1))) - push!(explicitfuns, - CacheWriter(sys, cachetypes, cacheexprs, solsyms, obs[_prevobsidxs]; - eval_expression, eval_module, cse)) - end - - cachebufsyms = Tuple(map(cachetypes) do T - get(cachevars, T, []) - end) - f = SCCNonlinearFunction{iip}( - sys, _eqs, _dvs, _obs, cachebufsyms; eval_expression, eval_module, cse, kwargs...) - push!(nlfuns, f) - end - - if !isempty(cachetypes) - templates = map(cachetypes, cachesizes) do T, n - # Real refers to `eltype(u0)` - if T == Real - T = eltype(u0) - elseif T <: Array && eltype(T) == Real - T = Array{eltype(u0), ndims(T)} - end - BufferTemplate(T, n) - end - p = rebuild_with_caches(p, templates...) - end - - subprobs = [] - for (f, vscc) in zip(nlfuns, var_sccs) - prob = NonlinearProblem(f, u0[vscc], p) - push!(subprobs, prob) - end - - new_dvs = dvs[reduce(vcat, var_sccs)] - new_eqs = eqs[reduce(vcat, eq_sccs)] - @set! sys.unknowns = new_dvs - @set! sys.eqs = new_eqs - @set! sys.index_cache = subset_unknowns_observed( - get_index_cache(sys), sys, new_dvs, getproperty.(obs, (:lhs,))) - return SCCNonlinearProblem(subprobs, explicitfuns, p, true; sys) -end - -""" -$(TYPEDSIGNATURES) - -Generate an `IntervalNonlinearProblem` from a `NonlinearSystem` and allow for automatically -symbolically calculating numerical enhancements. -""" -function DiffEqBase.IntervalNonlinearProblem(sys::NonlinearSystem, uspan::NTuple{2}, - parammap = SciMLBase.NullParameters(); kwargs...) - if !iscomplete(sys) - error("A completed `NonlinearSystem` is required. Call `complete` or `structural_simplify` on the system before creating a `IntervalNonlinearProblem`") - end - if !isone(length(unknowns(sys))) || !isone(length(equations(sys))) - error("`IntervalNonlinearProblem` only supports with a single equation and a single unknown.") - end - f, u0, p = process_SciMLProblem( - IntervalNonlinearFunction, sys, unknowns(sys) .=> uspan[1], parammap; kwargs...) - - return IntervalNonlinearProblem(f, uspan, p; filter_kwargs(kwargs)...) -end - -""" -```julia -DiffEqBase.NonlinearProblemExpr{iip}(sys::NonlinearSystem, u0map, - parammap = DiffEqBase.NullParameters(); - jac = false, sparse = false, - checkbounds = false, - linenumbers = true, parallel = SerialForm(), - kwargs...) where {iip} -``` - -Generates a Julia expression for a NonlinearProblem from a -NonlinearSystem and allows for automatically symbolically calculating -numerical enhancements. -""" -struct NonlinearProblemExpr{iip} end - -function NonlinearProblemExpr(sys::NonlinearSystem, args...; kwargs...) - NonlinearProblemExpr{true}(sys, args...; kwargs...) -end - -function NonlinearProblemExpr{iip}(sys::NonlinearSystem, u0map, - parammap = DiffEqBase.NullParameters(); - check_length = true, - kwargs...) where {iip} - if !iscomplete(sys) - error("A completed `NonlinearSystem` is required. Call `complete` or `structural_simplify` on the system before creating a `NonlinearProblemExpr`") - end - f, u0, p = process_SciMLProblem(NonlinearFunctionExpr{iip}, sys, u0map, parammap; - check_length, kwargs...) - linenumbers = get(kwargs, :linenumbers, true) - - ex = quote - f = $f - u0 = $u0 - p = $p - NonlinearProblem(f, u0, p; $(filter_kwargs(kwargs)...)) - end - !linenumbers ? Base.remove_linenums!(ex) : ex -end - -""" -```julia -DiffEqBase.NonlinearLeastSquaresProblemExpr{iip}(sys::NonlinearSystem, u0map, - parammap = DiffEqBase.NullParameters(); - jac = false, sparse = false, - checkbounds = false, - linenumbers = true, parallel = SerialForm(), - kwargs...) where {iip} -``` - -Generates a Julia expression for a NonlinearProblem from a -NonlinearSystem and allows for automatically symbolically calculating -numerical enhancements. -""" -struct NonlinearLeastSquaresProblemExpr{iip} end - -function NonlinearLeastSquaresProblemExpr(sys::NonlinearSystem, args...; kwargs...) - NonlinearLeastSquaresProblemExpr{true}(sys, args...; kwargs...) -end - -function NonlinearLeastSquaresProblemExpr{iip}(sys::NonlinearSystem, u0map, - parammap = DiffEqBase.NullParameters(); - check_length = false, - kwargs...) where {iip} - if !iscomplete(sys) - error("A completed `NonlinearSystem` is required. Call `complete` or `structural_simplify` on the system before creating a `NonlinearProblemExpr`") - end - f, u0, p = process_SciMLProblem(NonlinearFunctionExpr{iip}, sys, u0map, parammap; - check_length, kwargs...) - linenumbers = get(kwargs, :linenumbers, true) - - ex = quote - f = $f - u0 = $u0 - p = $p - NonlinearLeastSquaresProblem(f, u0, p; $(filter_kwargs(kwargs)...)) - end - !linenumbers ? Base.remove_linenums!(ex) : ex -end - -""" -$(TYPEDSIGNATURES) - -Generates a Julia expression for an IntervalNonlinearProblem from a -NonlinearSystem and allows for automatically symbolically calculating -numerical enhancements. -""" -function IntervalNonlinearProblemExpr(sys::NonlinearSystem, uspan::NTuple{2}, - parammap = SciMLBase.NullParameters(); kwargs...) - if !iscomplete(sys) - error("A completed `NonlinearSystem` is required. Call `complete` or `structural_simplify` on the system before creating a `IntervalNonlinearProblemExpr`") - end - if !isone(length(unknowns(sys))) || !isone(length(equations(sys))) - error("`IntervalNonlinearProblemExpr` only supports with a single equation and a single unknown.") - end - f, u0, p = process_SciMLProblem( - IntervalNonlinearFunctionExpr, sys, unknowns(sys) .=> uspan[1], parammap; kwargs...) - linenumbers = get(kwargs, :linenumbers, true) - - ex = quote - f = $f - uspan = $uspan - p = $p - IntervalNonlinearProblem(f, uspan, p; $(filter_kwargs(kwargs)...)) - end - !linenumbers ? Base.remove_linenums!(ex) : ex -end - -function flatten(sys::NonlinearSystem, noeqs = false) - systems = get_systems(sys) - if isempty(systems) - return sys - else - return NonlinearSystem(noeqs ? Equation[] : equations(sys), - unknowns(sys), - parameters(sys), - observed = observed(sys), - defaults = defaults(sys), - guesses = guesses(sys), - initialization_eqs = initialization_equations(sys), - name = nameof(sys), - description = description(sys), - metadata = get_metadata(sys), - checks = false) - end -end - -function Base.:(==)(sys1::NonlinearSystem, sys2::NonlinearSystem) - isequal(nameof(sys1), nameof(sys2)) && - _eq_unordered(get_eqs(sys1), get_eqs(sys2)) && - _eq_unordered(get_unknowns(sys1), get_unknowns(sys2)) && - _eq_unordered(get_ps(sys1), get_ps(sys2)) && - all(s1 == s2 for (s1, s2) in zip(get_systems(sys1), get_systems(sys2))) -end diff --git a/src/systems/system.jl b/src/systems/system.jl index f9f86757bf..c89a46ccaf 100644 --- a/src/systems/system.jl +++ b/src/systems/system.jl @@ -38,6 +38,7 @@ struct System <: AbstractSystem ignored_connections::Union{ Nothing, Tuple{Vector{IgnoredAnalysisPoint}, Vector{IgnoredAnalysisPoint}}} parent::Union{Nothing, System} + is_initializesystem::Bool isscheduled::Bool function System( @@ -48,7 +49,15 @@ struct System <: AbstractSystem metadata = nothing, gui_metadata = nothing, is_dde = false, tstops = [], tearing_state = nothing, namespacing = true, complete = false, index_cache = nothing, ignored_connections = nothing, - parent = nothing, isscheduled = false; checks::Union{Bool, Int} = true) + parent = nothing, is_initializesystem = false, isscheduled = false; + checks::Union{Bool, Int} = true) + + if is_initializesystem && iv !== nothing + throw(ArgumentError(""" + Expected initialization system to be time-independent. Found independent + variable $iv. + """)) + end if (checks == true || (checks & CheckComponents) > 0) && iv !== nothing check_independent_variables([iv]) check_variables(unknowns, iv) @@ -89,7 +98,8 @@ function System(eqs, iv, dvs, ps, brownians = []; connector_type = nothing, assertions = Dict{BasicSymbolic, String}(), metadata = nothing, gui_metadata = nothing, is_dde = nothing, tstops = [], tearing_state = nothing, ignored_connections = nothing, parent = nothing, - description = "", name = nothing, discover_from_metadata = true, checks = true) + description = "", name = nothing, discover_from_metadata = true, is_initializesystem = false, + checks = true) name === nothing && throw(NoNameError()) iv = unwrap(iv) @@ -151,7 +161,7 @@ function System(eqs, iv, dvs, ps, brownians = []; costs, consolidate, dvs, ps, brownians, iv, observed, parameter_dependencies, var_to_name, name, description, defaults, guesses, systems, initialization_eqs, continuous_events, discrete_events, connector_type, assertions, metadata, gui_metadata, is_dde, - tstops, tearing_state, true, false, nothing, ignored_connections, parent; checks) + tstops, tearing_state, true, false, nothing, ignored_connections, parent, is_initializesystem; checks) end function System(eqs, iv; kwargs...) From 413d41dbec3e49020655d0ee478a4b7e6fea5c56 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Mon, 21 Apr 2025 10:49:51 +0530 Subject: [PATCH 058/185] refactor: move `constraints` to `abstractsystem.jl` --- src/systems/abstractsystem.jl | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/systems/abstractsystem.jl b/src/systems/abstractsystem.jl index b4a687e157..6e613f272c 100644 --- a/src/systems/abstractsystem.jl +++ b/src/systems/abstractsystem.jl @@ -1776,6 +1776,30 @@ function cost(sys::AbstractSystem) return consolidate(cs, subcosts) end +namespace_constraint(eq::Equation, sys) = namespace_equation(eq, sys) + +namespace_constraint(ineq::Inequality, sys) = namespace_inequality(ineq, sys) + +function namespace_inequality(ineq::Inequality, sys, n = nameof(sys)) + _lhs = namespace_expr(ineq.lhs, sys, n) + _rhs = namespace_expr(ineq.rhs, sys, n) + Inequality(_lhs, + _rhs, + ineq.relational_op) +end + +function namespace_constraints(sys) + cstrs = constraints(sys) + isempty(cstrs) && return Vector{Union{Equation, Inequality}}(undef, 0) + map(cstr -> namespace_constraint(cstr, sys), cstrs) +end + +function constraints(sys) + cs = get_constraints(sys) + systems = get_systems(sys) + isempty(systems) ? cs : [cs; reduce(vcat, namespace_constraints.(systems))] +end + """ $(TYPEDSIGNATURES) From 9e34b0a35241bb49fc372f3db978bb5a0ada6236 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Mon, 21 Apr 2025 10:50:14 +0530 Subject: [PATCH 059/185] refactor: remove `optimizationsystem.jl` --- src/ModelingToolkit.jl | 3 +- .../optimization/optimizationsystem.jl | 760 ------------------ 2 files changed, 1 insertion(+), 762 deletions(-) delete mode 100644 src/systems/optimization/optimizationsystem.jl diff --git a/src/ModelingToolkit.jl b/src/ModelingToolkit.jl index b8dfe2e882..ca88a1bfae 100644 --- a/src/ModelingToolkit.jl +++ b/src/ModelingToolkit.jl @@ -163,7 +163,6 @@ include("systems/problem_utils.jl") include("linearization.jl") include("systems/optimization/constraints_system.jl") -include("systems/optimization/optimizationsystem.jl") include("systems/optimization/modelingtoolkitize.jl") include("systems/nonlinear/homotopy_continuation.jl") @@ -269,7 +268,7 @@ export IntervalNonlinearProblem, IntervalNonlinearProblemExpr export OptimizationProblem, OptimizationProblemExpr, constraints export SteadyStateProblem, SteadyStateProblemExpr export JumpProblem -export OptimizationSystem, ConstraintsSystem +export ConstraintsSystem export alias_elimination, flatten export connect, domain_connect, @connector, Connection, AnalysisPoint, Flow, Stream, instream diff --git a/src/systems/optimization/optimizationsystem.jl b/src/systems/optimization/optimizationsystem.jl deleted file mode 100644 index bfe15b62d7..0000000000 --- a/src/systems/optimization/optimizationsystem.jl +++ /dev/null @@ -1,760 +0,0 @@ -""" -$(TYPEDEF) - -A scalar equation for optimization. - -# Fields -$(FIELDS) - -# Examples - -```julia -@variables x y z -@parameters a b c - -obj = a * (y - x) + x * (b - z) - y + x * y - c * z -cons = [x^2 + y^2 ≲ 1] -@named os = OptimizationSystem(obj, [x, y, z], [a, b, c]; constraints = cons) -``` -""" -struct OptimizationSystem <: AbstractOptimizationSystem - """ - A tag for the system. If two systems have the same tag, then they are - structurally identical. - """ - tag::UInt - """Objective function of the system.""" - op::Any - """Unknown variables.""" - unknowns::Array - """Parameters.""" - ps::Vector - """Array variables.""" - var_to_name::Any - """Observed equations.""" - observed::Vector{Equation} - """List of constraint equations of the system.""" - constraints::Vector{Union{Equation, Inequality}} - """The name of the system.""" - name::Symbol - """A description of the system.""" - description::String - """The internal systems. These are required to have unique names.""" - systems::Vector{OptimizationSystem} - """ - The default values to use when initial guess and/or - parameters are not supplied in `OptimizationProblem`. - """ - defaults::Dict - """ - Metadata for the system, to be used by downstream packages. - """ - metadata::Any - """ - Metadata for MTK GUI. - """ - gui_metadata::Union{Nothing, GUIMetadata} - """ - If false, then `sys.x` no longer performs namespacing. - """ - namespacing::Bool - """ - If true, denotes the model will not be modified any further. - """ - complete::Bool - """ - Cached data for fast symbolic indexing. - """ - index_cache::Union{Nothing, IndexCache} - """ - The hierarchical parent system before simplification. - """ - parent::Any - isscheduled::Bool - - function OptimizationSystem(tag, op, unknowns, ps, var_to_name, observed, - constraints, name, description, systems, defaults, metadata = nothing, - gui_metadata = nothing, namespacing = true, complete = false, - index_cache = nothing, parent = nothing, isscheduled = false; - checks::Union{Bool, Int} = true) - if checks == true || (checks & CheckUnits) > 0 - u = __get_unit_type(unknowns, ps) - unwrap(op) isa Symbolic && check_units(u, op) - check_units(u, observed) - check_units(u, constraints) - check_subsystems(systems) - end - new(tag, op, unknowns, ps, var_to_name, observed, - constraints, name, description, systems, defaults, metadata, gui_metadata, - namespacing, complete, index_cache, parent, isscheduled) - end -end - -equations(sys::AbstractOptimizationSystem) = objective(sys) # needed for Base.show - -function OptimizationSystem(op, unknowns, ps; - observed = [], - constraints = [], - default_u0 = Dict(), - default_p = Dict(), - defaults = _merge(Dict(default_u0), Dict(default_p)), - name = nothing, - description = "", - systems = OptimizationSystem[], - checks = true, - metadata = nothing, - gui_metadata = nothing) - name === nothing && - throw(ArgumentError("The `name` keyword must be provided. Please consider using the `@named` macro")) - constraints = value.(reduce(vcat, scalarize(constraints); init = [])) - unknowns′ = value.(reduce(vcat, scalarize(unknowns); init = [])) - ps′ = value.(ps) - op′ = value(scalarize(op)) - - irreducible_subs = Dict() - for i in eachindex(unknowns′) - var = unknowns′[i] - if hasbounds(var) - irreducible_subs[var] = irrvar = setirreducible(var, true) - unknowns′[i] = irrvar - end - end - op′ = fast_substitute(op′, irreducible_subs) - constraints = fast_substitute.(constraints, (irreducible_subs,)) - - if !(isempty(default_u0) && isempty(default_p)) - Base.depwarn( - "`default_u0` and `default_p` are deprecated. Use `defaults` instead.", - :OptimizationSystem, force = true) - end - sysnames = nameof.(systems) - if length(unique(sysnames)) != length(sysnames) - throw(ArgumentError("System names must be unique.")) - end - defaults = todict(defaults) - defaults = Dict(fast_substitute(value(k), irreducible_subs) => fast_substitute( - value(v), irreducible_subs) - for (k, v) in pairs(defaults) if value(v) !== nothing) - - var_to_name = Dict() - process_variables!(var_to_name, defaults, Dict(), unknowns′) - process_variables!(var_to_name, defaults, Dict(), ps′) - isempty(observed) || collect_var_to_name!(var_to_name, (eq.lhs for eq in observed)) - - OptimizationSystem(Threads.atomic_add!(SYSTEM_COUNT, UInt(1)), - op′, unknowns′, ps′, var_to_name, - observed, - constraints, - name, description, systems, defaults, metadata, gui_metadata; - checks = checks) -end - -function OptimizationSystem(objective; constraints = [], kwargs...) - allunknowns = OrderedSet() - ps = OrderedSet() - collect_vars!(allunknowns, ps, objective, nothing) - for cons in constraints - collect_vars!(allunknowns, ps, cons, nothing) - end - for ssys in get(kwargs, :systems, OptimizationSystem[]) - collect_scoped_vars!(allunknowns, ps, ssys, nothing) - end - new_ps = OrderedSet() - for p in ps - if iscall(p) && operation(p) === getindex - par = arguments(p)[begin] - if Symbolics.shape(Symbolics.unwrap(par)) !== Symbolics.Unknown() && - all(par[i] in ps for i in eachindex(par)) - push!(new_ps, par) - else - push!(new_ps, p) - end - else - push!(new_ps, p) - end - end - return OptimizationSystem( - objective, collect(allunknowns), collect(new_ps); constraints, kwargs...) -end - -function flatten(sys::OptimizationSystem) - systems = get_systems(sys) - isempty(systems) && return sys - - return OptimizationSystem( - objective(sys), - unknowns(sys), - parameters(sys); - observed = observed(sys), - constraints = constraints(sys), - defaults = defaults(sys), - name = nameof(sys), - metadata = get_metadata(sys), - checks = false - ) -end - -function calculate_gradient(sys::OptimizationSystem) - expand_derivatives.(gradient(objective(sys), unknowns(sys))) -end - -function generate_gradient(sys::OptimizationSystem, vs = unknowns(sys), - ps = parameters(sys; initial_parameters = true); kwargs...) - grad = calculate_gradient(sys) - p = reorder_parameters(sys, ps) - return build_function_wrapper(sys, grad, vs, p...; kwargs...) -end - -function calculate_hessian(sys::OptimizationSystem) - expand_derivatives.(hessian(objective(sys), unknowns(sys))) -end - -function generate_hessian( - sys::OptimizationSystem, vs = unknowns(sys), ps = parameters( - sys; initial_parameters = true); - sparse = false, kwargs...) - if sparse - hess = sparsehessian(objective(sys), unknowns(sys)) - else - hess = calculate_hessian(sys) - end - p = reorder_parameters(sys, ps) - return build_function_wrapper(sys, hess, vs, p...; kwargs...) -end - -function generate_function(sys::OptimizationSystem, vs = unknowns(sys), - ps = parameters(sys; initial_parameters = true); - kwargs...) - eqs = objective(sys) - p = reorder_parameters(sys, ps) - return build_function_wrapper(sys, eqs, vs, p...; kwargs...) -end - -function namespace_objective(sys::AbstractSystem) - op = objective(sys) - namespace_expr(op, sys) -end - -function objective(sys) - op = get_op(sys) - systems = get_systems(sys) - if isempty(systems) - op - else - op + reduce(+, map(sys_ -> namespace_objective(sys_), systems)) - end -end - -namespace_constraint(eq::Equation, sys) = namespace_equation(eq, sys) - -namespace_constraint(ineq::Inequality, sys) = namespace_inequality(ineq, sys) - -function namespace_inequality(ineq::Inequality, sys, n = nameof(sys)) - _lhs = namespace_expr(ineq.lhs, sys, n) - _rhs = namespace_expr(ineq.rhs, sys, n) - Inequality(_lhs, - _rhs, - ineq.relational_op) -end - -function namespace_constraints(sys) - cstrs = constraints(sys) - isempty(cstrs) && return Vector{Union{Equation, Inequality}}(undef, 0) - map(cstr -> namespace_constraint(cstr, sys), cstrs) -end - -function constraints(sys) - cs = get_constraints(sys) - systems = get_systems(sys) - isempty(systems) ? cs : [cs; reduce(vcat, namespace_constraints.(systems))] -end - -hessian_sparsity(sys::OptimizationSystem) = hessian_sparsity(get_op(sys), unknowns(sys)) - -""" -```julia -DiffEqBase.OptimizationProblem{iip}(sys::OptimizationSystem, u0map, - parammap = DiffEqBase.NullParameters(); - grad = false, - hess = false, sparse = false, - cons_j = false, cons_h = false, - checkbounds = false, - linenumbers = true, parallel = SerialForm(), - kwargs...) where {iip} -``` - -Generates an OptimizationProblem from an OptimizationSystem and allows for automatically -symbolically calculating numerical enhancements. - -Certain solvers require setting `cons_j`, `cons_h` to `true` for constrained-optimization problems. -""" -function DiffEqBase.OptimizationProblem(sys::OptimizationSystem, args...; kwargs...) - DiffEqBase.OptimizationProblem{true}(sys::OptimizationSystem, args...; kwargs...) -end -function DiffEqBase.OptimizationProblem{iip}(sys::OptimizationSystem, u0map, - parammap = DiffEqBase.NullParameters(); - lb = nothing, ub = nothing, - grad = false, - hess = false, sparse = false, - cons_j = false, cons_h = false, - cons_sparse = false, checkbounds = false, - linenumbers = true, parallel = SerialForm(), - eval_expression = false, eval_module = @__MODULE__, - checks = true, cse = true, - kwargs...) where {iip} - if !iscomplete(sys) - error("A completed `OptimizationSystem` is required. Call `complete` or `structural_simplify` on the system before creating a `OptimizationProblem`") - end - if haskey(kwargs, :lcons) || haskey(kwargs, :ucons) - Base.depwarn( - "`lcons` and `ucons` are deprecated. Specify constraints directly instead.", - :OptimizationProblem, force = true) - end - - dvs = unknowns(sys) - ps = parameters(sys) - cstr = constraints(sys) - - if isnothing(lb) && isnothing(ub) # use the symbolically specified bounds - lb = first.(getbounds.(dvs)) - ub = last.(getbounds.(dvs)) - isboolean = symtype.(unwrap.(dvs)) .<: Bool - lb[isboolean] .= 0 - ub[isboolean] .= 1 - else # use the user supplied variable bounds - xor(isnothing(lb), isnothing(ub)) && - throw(ArgumentError("Expected both `lb` and `ub` to be supplied")) - !isnothing(lb) && length(lb) != length(dvs) && - throw(ArgumentError("Expected both `lb` to be of the same length as the vector of optimization variables")) - !isnothing(ub) && length(ub) != length(dvs) && - throw(ArgumentError("Expected both `ub` to be of the same length as the vector of optimization variables")) - end - - int = symtype.(unwrap.(dvs)) .<: Integer - - defs = defaults(sys) - defs = mergedefaults(defs, parammap, ps) - defs = mergedefaults(defs, u0map, dvs) - - u0 = varmap_to_vars(u0map, dvs; defaults = defs, tofloat = false) - if parammap isa MTKParameters - p = parammap - elseif has_index_cache(sys) && get_index_cache(sys) !== nothing - p = MTKParameters(sys, parammap, u0map) - else - p = varmap_to_vars(parammap, ps; defaults = defs, tofloat = false) - end - lb = varmap_to_vars(dvs .=> lb, dvs; defaults = defs, tofloat = false) - ub = varmap_to_vars(dvs .=> ub, dvs; defaults = defs, tofloat = false) - - if !isnothing(lb) && all(lb .== -Inf) && !isnothing(ub) && all(ub .== Inf) - lb = nothing - ub = nothing - end - - f = let _f = eval_or_rgf( - generate_function( - sys; checkbounds = checkbounds, linenumbers = linenumbers, - expression = Val{true}, wrap_mtkparameters = false, cse); - eval_expression, - eval_module) - __f(u, p) = _f(u, p) - __f(u, p::MTKParameters) = _f(u, p...) - __f - end - obj_expr = subs_constants(objective(sys)) - - if grad - _grad = let (grad_oop, grad_iip) = eval_or_rgf.( - generate_gradient( - sys; checkbounds = checkbounds, - linenumbers = linenumbers, - parallel = parallel, expression = Val{true}, - wrap_mtkparameters = false, cse); - eval_expression, - eval_module) - _grad(u, p) = grad_oop(u, p) - _grad(J, u, p) = (grad_iip(J, u, p); J) - _grad(u, p::MTKParameters) = grad_oop(u, p...) - _grad(J, u, p::MTKParameters) = (grad_iip(J, u, p...); J) - _grad - end - else - _grad = nothing - end - - if hess - _hess = let (hess_oop, hess_iip) = eval_or_rgf.( - generate_hessian( - sys; checkbounds = checkbounds, - linenumbers = linenumbers, - sparse = sparse, parallel = parallel, - expression = Val{true}, wrap_mtkparameters = false, cse); - eval_expression, - eval_module) - _hess(u, p) = hess_oop(u, p) - _hess(J, u, p) = (hess_iip(J, u, p); J) - _hess(u, p::MTKParameters) = hess_oop(u, p...) - _hess(J, u, p::MTKParameters) = (hess_iip(J, u, p...); J) - _hess - end - else - _hess = nothing - end - - if sparse - hess_prototype = hessian_sparsity(sys) - else - hess_prototype = nothing - end - - observedfun = ObservedFunctionCache(sys; eval_expression, eval_module, checkbounds, cse) - - if length(cstr) > 0 - @named cons_sys = ConstraintsSystem(cstr, dvs, ps; checks) - cons_sys = complete(cons_sys) - cons, lcons_, ucons_ = generate_function(cons_sys; checkbounds = checkbounds, - linenumbers = linenumbers, - expression = Val{true}, wrap_mtkparameters = false, cse) - cons = let (cons_oop, cons_iip) = eval_or_rgf.(cons; eval_expression, eval_module) - _cons(u, p) = cons_oop(u, p) - _cons(resid, u, p) = cons_iip(resid, u, p) - _cons(u, p::MTKParameters) = cons_oop(u, p...) - _cons(resid, u, p::MTKParameters) = cons_iip(resid, u, p...) - end - if cons_j - _cons_j = let (cons_jac_oop, cons_jac_iip) = eval_or_rgf.( - generate_jacobian(cons_sys; - checkbounds = checkbounds, - linenumbers = linenumbers, - parallel = parallel, expression = Val{true}, - sparse = cons_sparse, wrap_mtkparameters = false, cse); - eval_expression, - eval_module) - _cons_j(u, p) = cons_jac_oop(u, p) - _cons_j(J, u, p) = (cons_jac_iip(J, u, p); J) - _cons_j(u, p::MTKParameters) = cons_jac_oop(u, p...) - _cons_j(J, u, p::MTKParameters) = (cons_jac_iip(J, u, p...); J) - _cons_j - end - else - _cons_j = nothing - end - if cons_h - _cons_h = let (cons_hess_oop, cons_hess_iip) = eval_or_rgf.( - generate_hessian( - cons_sys; checkbounds = checkbounds, - linenumbers = linenumbers, - sparse = cons_sparse, parallel = parallel, - expression = Val{true}, wrap_mtkparameters = false, cse); - eval_expression, - eval_module) - _cons_h(u, p) = cons_hess_oop(u, p) - _cons_h(J, u, p) = (cons_hess_iip(J, u, p); J) - _cons_h(u, p::MTKParameters) = cons_hess_oop(u, p...) - _cons_h(J, u, p::MTKParameters) = (cons_hess_iip(J, u, p...); J) - _cons_h - end - else - _cons_h = nothing - end - cons_expr = subs_constants(constraints(cons_sys)) - - if !haskey(kwargs, :lcons) && !haskey(kwargs, :ucons) # use the symbolically specified bounds - lcons = lcons_ - ucons = ucons_ - else # use the user supplied constraints bounds - (haskey(kwargs, :lcons) ⊻ haskey(kwargs, :ucons)) && - throw(ArgumentError("Expected both `ucons` and `lcons` to be supplied")) - haskey(kwargs, :lcons) && length(kwargs[:lcons]) != length(cstr) && - throw(ArgumentError("Expected `lcons` to be of the same length as the vector of constraints")) - haskey(kwargs, :ucons) && length(kwargs[:ucons]) != length(cstr) && - throw(ArgumentError("Expected `ucons` to be of the same length as the vector of constraints")) - lcons = haskey(kwargs, :lcons) - ucons = haskey(kwargs, :ucons) - end - - if cons_sparse - cons_jac_prototype = jacobian_sparsity(cons_sys) - cons_hess_prototype = hessian_sparsity(cons_sys) - else - cons_jac_prototype = nothing - cons_hess_prototype = nothing - end - _f = DiffEqBase.OptimizationFunction{iip}(f, - sys = sys, - SciMLBase.NoAD(); - grad = _grad, - hess = _hess, - hess_prototype = hess_prototype, - cons = cons, - cons_j = _cons_j, - cons_h = _cons_h, - cons_jac_prototype = cons_jac_prototype, - cons_hess_prototype = cons_hess_prototype, - expr = obj_expr, - cons_expr = cons_expr, - observed = observedfun) - OptimizationProblem{iip}(_f, u0, p; lb = lb, ub = ub, int = int, - lcons = lcons, ucons = ucons, kwargs...) - else - _f = DiffEqBase.OptimizationFunction{iip}(f, - sys = sys, - SciMLBase.NoAD(); - grad = _grad, - hess = _hess, - hess_prototype = hess_prototype, - expr = obj_expr, - observed = observedfun) - OptimizationProblem{iip}(_f, u0, p; lb = lb, ub = ub, int = int, - kwargs...) - end -end - -""" -```julia -DiffEqBase.OptimizationProblemExpr{iip}(sys::OptimizationSystem, - parammap = DiffEqBase.NullParameters(); - u0 = nothing, - grad = false, - hes = false, sparse = false, - checkbounds = false, - linenumbers = true, parallel = SerialForm(), - kwargs...) where {iip} -``` - -Generates a Julia expression for an OptimizationProblem from an -OptimizationSystem and allows for automatically symbolically -calculating numerical enhancements. -""" -struct OptimizationProblemExpr{iip} end - -function OptimizationProblemExpr(sys::OptimizationSystem, args...; kwargs...) - OptimizationProblemExpr{true}(sys::OptimizationSystem, args...; kwargs...) -end - -function OptimizationProblemExpr{iip}(sys::OptimizationSystem, u0map, - parammap = DiffEqBase.NullParameters(); - lb = nothing, ub = nothing, - grad = false, - hess = false, sparse = false, - cons_j = false, cons_h = false, - checkbounds = false, - linenumbers = false, parallel = SerialForm(), - eval_expression = false, eval_module = @__MODULE__, - kwargs...) where {iip} - if !iscomplete(sys) - error("A completed `OptimizationSystem` is required. Call `complete` or `structural_simplify` on the system before creating a `OptimizationProblemExpr`") - end - if haskey(kwargs, :lcons) || haskey(kwargs, :ucons) - Base.depwarn( - "`lcons` and `ucons` are deprecated. Specify constraints directly instead.", - :OptimizationProblem, force = true) - end - - dvs = unknowns(sys) - ps = parameters(sys) - cstr = constraints(sys) - - if isnothing(lb) && isnothing(ub) # use the symbolically specified bounds - lb = first.(getbounds.(dvs)) - ub = last.(getbounds.(dvs)) - isboolean = symtype.(unwrap.(dvs)) .<: Bool - lb[isboolean] .= 0 - ub[isboolean] .= 1 - else # use the user supplied variable bounds - xor(isnothing(lb), isnothing(ub)) && - throw(ArgumentError("Expected both `lb` and `ub` to be supplied")) - !isnothing(lb) && length(lb) != length(dvs) && - throw(ArgumentError("Expected `lb` to be of the same length as the vector of optimization variables")) - !isnothing(ub) && length(ub) != length(dvs) && - throw(ArgumentError("Expected `ub` to be of the same length as the vector of optimization variables")) - end - - int = symtype.(unwrap.(dvs)) .<: Integer - - defs = defaults(sys) - defs = mergedefaults(defs, parammap, ps) - defs = mergedefaults(defs, u0map, dvs) - - u0 = varmap_to_vars(u0map, dvs; defaults = defs, tofloat = false) - if has_index_cache(sys) && get_index_cache(sys) !== nothing - p = MTKParameters(sys, parammap, u0map) - else - p = varmap_to_vars(parammap, ps; defaults = defs, tofloat = false) - end - lb = varmap_to_vars(dvs .=> lb, dvs; defaults = defs, tofloat = false) - ub = varmap_to_vars(dvs .=> ub, dvs; defaults = defs, tofloat = false) - - if !isnothing(lb) && all(lb .== -Inf) && !isnothing(ub) && all(ub .== Inf) - lb = nothing - ub = nothing - end - - idx = iip ? 2 : 1 - f = generate_function(sys, checkbounds = checkbounds, linenumbers = linenumbers, - expression = Val{true}) - if grad - _grad = eval_or_rgf( - generate_gradient( - sys, checkbounds = checkbounds, linenumbers = linenumbers, - parallel = parallel, expression = Val{true})[idx]; - eval_expression, - eval_module) - else - _grad = :nothing - end - - if hess - _hess = eval_or_rgf( - generate_hessian(sys, checkbounds = checkbounds, linenumbers = linenumbers, - sparse = sparse, parallel = parallel, - expression = Val{false})[idx]; - eval_expression, - eval_module) - else - _hess = :nothing - end - - if sparse - hess_prototype = hessian_sparsity(sys) - else - hess_prototype = nothing - end - - obj_expr = toexpr(subs_constants(objective(sys))) - pairs_arr = if p isa SciMLBase.NullParameters - [Symbol(_s) => Expr(:ref, :x, i) for (i, _s) in enumerate(dvs)] - else - vcat([Symbol(_s) => Expr(:ref, :x, i) for (i, _s) in enumerate(dvs)], - [Symbol(_p) => p[i] for (i, _p) in enumerate(ps)]) - end - rep_pars_vals!(obj_expr, pairs_arr) - - if length(cstr) > 0 - @named cons_sys = ConstraintsSystem(cstr, dvs, ps) - cons, lcons_, ucons_ = generate_function(cons_sys, checkbounds = checkbounds, - linenumbers = linenumbers, - expression = Val{true}) - cons = eval_or_rgf(cons; eval_expression, eval_module) - if cons_j - _cons_j = eval_or_rgf( - generate_jacobian(cons_sys; expression = Val{true}, sparse = sparse)[2]; - eval_expression, eval_module) - else - _cons_j = nothing - end - if cons_h - _cons_h = eval_or_rgf( - generate_hessian(cons_sys; expression = Val{true}, sparse = sparse)[2]; - eval_expression, eval_module) - else - _cons_h = nothing - end - - cons_expr = toexpr.(subs_constants(constraints(cons_sys))) - rep_pars_vals!.(cons_expr, Ref(pairs_arr)) - - if !haskey(kwargs, :lcons) && !haskey(kwargs, :ucons) # use the symbolically specified bounds - lcons = lcons_ - ucons = ucons_ - else # use the user supplied constraints bounds - (haskey(kwargs, :lcons) ⊻ haskey(kwargs, :ucons)) && - throw(ArgumentError("Expected both `ucons` and `lcons` to be supplied")) - haskey(kwargs, :lcons) && length(kwargs[:lcons]) != length(cstr) && - throw(ArgumentError("Expected `lcons` to be of the same length as the vector of constraints")) - haskey(kwargs, :ucons) && length(kwargs[:ucons]) != length(cstr) && - throw(ArgumentError("Expected `ucons` to be of the same length as the vector of constraints")) - lcons = haskey(kwargs, :lcons) - ucons = haskey(kwargs, :ucons) - end - - if sparse - cons_jac_prototype = jacobian_sparsity(cons_sys) - cons_hess_prototype = hessian_sparsity(cons_sys) - else - cons_jac_prototype = nothing - cons_hess_prototype = nothing - end - - quote - f = $f - p = $p - u0 = $u0 - grad = $_grad - hess = $_hess - lb = $lb - ub = $ub - int = $int - cons = $cons[1] - lcons = $lcons - ucons = $ucons - cons_j = $_cons_j - cons_h = $_cons_h - _f = OptimizationFunction{iip}(f, SciMLBase.NoAD(); - grad = grad, - hess = hess, - hess_prototype = hess_prototype, - cons = cons, - cons_j = cons_j, - cons_h = cons_h, - cons_jac_prototype = cons_jac_prototype, - cons_hess_prototype = cons_hess_prototype, - expr = obj_expr, - cons_expr = cons_expr) - OptimizationProblem{$iip}( - _f, u0, p; lb = lb, ub = ub, int = int, lcons = lcons, - ucons = ucons, kwargs...) - end - else - quote - f = $f - p = $p - u0 = $u0 - grad = $_grad - hess = $_hess - lb = $lb - ub = $ub - int = $int - _f = OptimizationFunction{iip}(f, SciMLBase.NoAD(); - grad = grad, - hess = hess, - hess_prototype = hess_prototype, - expr = obj_expr) - OptimizationProblem{$iip}(_f, u0, p; lb = lb, ub = ub, int = int, kwargs...) - end - end -end - -function structural_simplify(sys::OptimizationSystem; split = true, kwargs...) - sys = flatten(sys) - cons = constraints(sys) - econs = Equation[] - icons = similar(cons, 0) - for e in cons - if e isa Equation - push!(econs, e) - else - push!(icons, e) - end - end - nlsys = NonlinearSystem(econs, unknowns(sys), parameters(sys); name = :___tmp_nlsystem) - snlsys = structural_simplify(nlsys; fully_determined = false, kwargs...) - obs = observed(snlsys) - subs = Dict(eq.lhs => eq.rhs for eq in observed(snlsys)) - seqs = equations(snlsys) - cons_simplified = similar(cons, length(icons) + length(seqs)) - for (i, eq) in enumerate(Iterators.flatten((seqs, icons))) - cons_simplified[i] = fixpoint_sub(eq, subs) - end - newsts = setdiff(unknowns(sys), keys(subs)) - @set! sys.constraints = cons_simplified - @set! sys.observed = [observed(sys); obs] - neweqs = fixpoint_sub.(equations(sys), (subs,)) - @set! sys.op = length(neweqs) == 1 ? first(neweqs) : neweqs - @set! sys.unknowns = newsts - sys = complete(sys; split) - return sys -end - -supports_initialization(::OptimizationSystem) = false From 62fc4e955625a3d2745c8719d5ee89d48843269c Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Mon, 21 Apr 2025 10:51:39 +0530 Subject: [PATCH 060/185] refactor: remove `constraints_system.jl` --- src/ModelingToolkit.jl | 2 - .../optimization/constraints_system.jl | 255 ------------------ 2 files changed, 257 deletions(-) delete mode 100644 src/systems/optimization/constraints_system.jl diff --git a/src/ModelingToolkit.jl b/src/ModelingToolkit.jl index ca88a1bfae..4d134fd8b4 100644 --- a/src/ModelingToolkit.jl +++ b/src/ModelingToolkit.jl @@ -162,7 +162,6 @@ include("systems/codegen_utils.jl") include("systems/problem_utils.jl") include("linearization.jl") -include("systems/optimization/constraints_system.jl") include("systems/optimization/modelingtoolkitize.jl") include("systems/nonlinear/homotopy_continuation.jl") @@ -268,7 +267,6 @@ export IntervalNonlinearProblem, IntervalNonlinearProblemExpr export OptimizationProblem, OptimizationProblemExpr, constraints export SteadyStateProblem, SteadyStateProblemExpr export JumpProblem -export ConstraintsSystem export alias_elimination, flatten export connect, domain_connect, @connector, Connection, AnalysisPoint, Flow, Stream, instream diff --git a/src/systems/optimization/constraints_system.jl b/src/systems/optimization/constraints_system.jl deleted file mode 100644 index ae8577662d..0000000000 --- a/src/systems/optimization/constraints_system.jl +++ /dev/null @@ -1,255 +0,0 @@ -""" -$(TYPEDEF) - -A constraint system of equations. - -# Fields -$(FIELDS) - -# Examples - -```julia -@variables x y z -@parameters a b c - -cstr = [0 ~ a*(y-x), - 0 ~ x*(b-z)-y, - 0 ~ x*y - c*z - x^2 + y^2 ≲ 1] -@named ns = ConstraintsSystem(cstr, [x,y,z],[a,b,c]) -``` -""" -struct ConstraintsSystem <: AbstractTimeIndependentSystem - """ - A tag for the system. If two systems have the same tag, then they are - structurally identical. - """ - tag::UInt - """Vector of equations defining the system.""" - constraints::Vector{Union{Equation, Inequality}} - """Unknown variables.""" - unknowns::Vector - """Parameters.""" - ps::Vector - """Array variables.""" - var_to_name::Any - """Observed equations.""" - observed::Vector{Equation} - """ - Jacobian matrix. Note: this field will not be defined until - [`calculate_jacobian`](@ref) is called on the system. - """ - jac::RefValue{Any} - """ - The name of the system. - """ - name::Symbol - """ - A description of the system. - """ - description::String - """ - The internal systems. These are required to have unique names. - """ - systems::Vector{ConstraintsSystem} - """ - The default values to use when initial conditions and/or - parameters are not supplied in `ODEProblem`. - """ - defaults::Dict - """ - Type of the system. - """ - connector_type::Any - """ - Metadata for the system, to be used by downstream packages. - """ - metadata::Any - """ - Cache for intermediate tearing state. - """ - tearing_state::Any - """ - Substitutions generated by tearing. - """ - substitutions::Any - """ - If false, then `sys.x` no longer performs namespacing. - """ - namespacing::Bool - """ - If true, denotes the model will not be modified any further. - """ - complete::Bool - """ - Cached data for fast symbolic indexing. - """ - index_cache::Union{Nothing, IndexCache} - - function ConstraintsSystem(tag, constraints, unknowns, ps, var_to_name, observed, jac, - name, description, - systems, - defaults, connector_type, metadata = nothing, - tearing_state = nothing, substitutions = nothing, namespacing = true, - complete = false, index_cache = nothing; - checks::Union{Bool, Int} = true) - if checks == true || (checks & CheckUnits) > 0 - u = __get_unit_type(unknowns, ps) - check_units(u, constraints) - check_subsystems(systems) - end - new(tag, constraints, unknowns, ps, var_to_name, - observed, jac, name, description, systems, - defaults, connector_type, metadata, tearing_state, substitutions, - namespacing, complete, index_cache) - end -end - -equations(sys::ConstraintsSystem) = constraints(sys) # needed for Base.show - -function ConstraintsSystem(constraints, unknowns, ps; - observed = [], - name = nothing, - description = "", - default_u0 = Dict(), - default_p = Dict(), - defaults = _merge(Dict(default_u0), Dict(default_p)), - systems = ConstraintsSystem[], - connector_type = nothing, - continuous_events = nothing, # this argument is only required for ODESystems, but is added here for the constructor to accept it without error - discrete_events = nothing, # this argument is only required for ODESystems, but is added here for the constructor to accept it without error - checks = true, - metadata = nothing) - continuous_events === nothing || isempty(continuous_events) || - throw(ArgumentError("ConstraintsSystem does not accept `continuous_events`, you provided $continuous_events")) - discrete_events === nothing || isempty(discrete_events) || - throw(ArgumentError("ConstraintsSystem does not accept `discrete_events`, you provided $discrete_events")) - - name === nothing && - throw(ArgumentError("The `name` keyword must be provided. Please consider using the `@named` macro")) - - cstr = value.(Symbolics.canonical_form.(vcat(scalarize(constraints)...))) - unknowns′ = value.(scalarize(unknowns)) - ps′ = value.(ps) - - if !(isempty(default_u0) && isempty(default_p)) - Base.depwarn( - "`default_u0` and `default_p` are deprecated. Use `defaults` instead.", - :ConstraintsSystem, force = true) - end - sysnames = nameof.(systems) - if length(unique(sysnames)) != length(sysnames) - throw(ArgumentError("System names must be unique.")) - end - - jac = RefValue{Any}(EMPTY_JAC) - defaults = todict(defaults) - defaults = Dict(value(k) => value(v) - for (k, v) in pairs(defaults) if value(v) !== nothing) - - var_to_name = Dict() - process_variables!(var_to_name, defaults, Dict(), unknowns′) - process_variables!(var_to_name, defaults, Dict(), ps′) - isempty(observed) || collect_var_to_name!(var_to_name, (eq.lhs for eq in observed)) - - ConstraintsSystem(Threads.atomic_add!(SYSTEM_COUNT, UInt(1)), - cstr, unknowns, ps, var_to_name, observed, jac, name, description, systems, - defaults, - connector_type, metadata, checks = checks) -end - -function calculate_jacobian(sys::ConstraintsSystem; sparse = false, simplify = false) - cache = get_jac(sys)[] - if cache isa Tuple && cache[2] == (sparse, simplify) - return cache[1] - end - - lhss = generate_canonical_form_lhss(sys) - vals = [dv for dv in unknowns(sys)] - if sparse - jac = sparsejacobian(lhss, vals, simplify = simplify) - else - jac = jacobian(lhss, vals, simplify = simplify) - end - get_jac(sys)[] = jac, (sparse, simplify) - return jac -end - -function generate_jacobian( - sys::ConstraintsSystem, vs = unknowns(sys), ps = parameters( - sys; initial_parameters = true); - sparse = false, simplify = false, kwargs...) - jac = calculate_jacobian(sys, sparse = sparse, simplify = simplify) - p = reorder_parameters(sys, ps) - return build_function_wrapper(sys, jac, vs, p...; kwargs...) -end - -function calculate_hessian(sys::ConstraintsSystem; sparse = false, simplify = false) - lhss = generate_canonical_form_lhss(sys) - vals = [dv for dv in unknowns(sys)] - if sparse - hess = [sparsehessian(lhs, vals, simplify = simplify) for lhs in lhss] - else - hess = [hessian(lhs, vals, simplify = simplify) for lhs in lhss] - end - return hess -end - -function generate_hessian( - sys::ConstraintsSystem, vs = unknowns(sys), ps = parameters( - sys; initial_parameters = true); - sparse = false, simplify = false, kwargs...) - hess = calculate_hessian(sys, sparse = sparse, simplify = simplify) - p = reorder_parameters(sys, ps) - return build_function_wrapper(sys, hess, vs, p...; kwargs...) -end - -function generate_function(sys::ConstraintsSystem, dvs = unknowns(sys), - ps = parameters(sys; initial_parameters = true); - kwargs...) - lhss = generate_canonical_form_lhss(sys) - p = reorder_parameters(sys, value.(ps)) - func = build_function_wrapper(sys, lhss, value.(dvs), p...; kwargs...) - - cstr = constraints(sys) - lcons = fill(-Inf, length(cstr)) - ucons = zeros(length(cstr)) - lcons[findall(Base.Fix2(isa, Equation), cstr)] .= 0.0 - - return func, lcons, ucons -end - -function jacobian_sparsity(sys::ConstraintsSystem) - lhss = generate_canonical_form_lhss(sys) - jacobian_sparsity(lhss, unknowns(sys)) -end - -function hessian_sparsity(sys::ConstraintsSystem) - lhss = generate_canonical_form_lhss(sys) - [hessian_sparsity(eq, unknowns(sys)) for eq in lhss] -end - -""" -Convert the system of equalities and inequalities into a canonical form: -h(x) = 0 -g(x) <= 0 -""" -function generate_canonical_form_lhss(sys) - lhss = subs_constants([Symbolics.canonical_form(eq).lhs for eq in constraints(sys)]) -end - -function get_cmap(sys::ConstraintsSystem, exprs = nothing) - #Inject substitutions for constants => values - cs = collect_constants([get_constraints(sys); get_observed(sys)]) #ctrls? what else? - if !empty_substitutions(sys) - cs = [cs; collect_constants(get_substitutions(sys).subs)] - end - if exprs !== nothing - cs = [cs; collect_constants(exprs)] - end - # Swap constants for their values - cmap = map(x -> x ~ getdefault(x), cs) - return cmap, cs -end - -supports_initialization(::ConstraintsSystem) = false From 0c4dc1705e70e57f4199d7004cede7e0827d95c9 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Mon, 21 Apr 2025 12:40:30 +0530 Subject: [PATCH 061/185] docs: add docstring for new `BVProblem` constructor --- src/problems/bvproblem.jl | 45 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/src/problems/bvproblem.jl b/src/problems/bvproblem.jl index 919496e108..86bf6a8126 100644 --- a/src/problems/bvproblem.jl +++ b/src/problems/bvproblem.jl @@ -1,3 +1,48 @@ +""" +```julia +SciMLBase.BVProblem{iip}(sys::AbstractSystem, u0map, tspan, + parammap = DiffEqBase.NullParameters(); + constraints = nothing, guesses = nothing, + version = nothing, tgrad = false, + jac = true, sparse = true, + simplify = false, + kwargs...) where {iip} +``` + +Create a boundary value problem from the [`System`](@ref). + +`u0map` is used to specify fixed initial values for the states. Every variable +must have either an initial guess supplied using `guesses` or a fixed initial +value specified using `u0map`. + +Boundary value conditions are supplied to Systems in the form of a list of constraints. +These equations should specify values that state variables should take at specific points, +as in `x(0.5) ~ 1`). More general constraints that should hold over the entire solution, +such as `x(t)^2 + y(t)^2`, should be specified as one of the equations used to build the +`System`. + +If a `System` without `constraints` is specified, it will be treated as an initial value problem. + +```julia + @parameters g t_c = 0.5 + @variables x(..) y(t) λ(t) + eqs = [D(D(x(t))) ~ λ * x(t) + D(D(y)) ~ λ * y - g + x(t)^2 + y^2 ~ 1] + cstr = [x(0.5) ~ 1] + @mtkbuild pend = System(eqs, t; constraints = cstrs) + + tspan = (0.0, 1.5) + u0map = [x(t) => 0.6, y => 0.8] + parammap = [g => 1] + guesses = [λ => 1] + + bvp = SciMLBase.BVProblem{true, SciMLBase.AutoSpecialize}(pend, u0map, tspan, parammap; guesses, check_length = false) +``` + +If the `System` has algebraic equations, like `x(t)^2 + y(t)^2`, the resulting +`BVProblem` must be solved using BVDAE solvers, such as Ascher. +""" @fallback_iip_specialize function SciMLBase.BVProblem{iip, spec}( sys::System, u0map, tspan, parammap = SciMLBase.NullParameters(); check_compatibility = true, cse = true, checkbounds = false, eval_expression = false, From 7eb1ff17073bd2e00300f8b2ae63729a181c12b2 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Mon, 21 Apr 2025 12:41:09 +0530 Subject: [PATCH 062/185] refactor: improve `BVProblem` validation --- src/problems/bvproblem.jl | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/src/problems/bvproblem.jl b/src/problems/bvproblem.jl index 86bf6a8126..5a78101b23 100644 --- a/src/problems/bvproblem.jl +++ b/src/problems/bvproblem.jl @@ -46,11 +46,13 @@ If the `System` has algebraic equations, like `x(t)^2 + y(t)^2`, the resulting @fallback_iip_specialize function SciMLBase.BVProblem{iip, spec}( sys::System, u0map, tspan, parammap = SciMLBase.NullParameters(); check_compatibility = true, cse = true, checkbounds = false, eval_expression = false, - eval_module = @__MODULE__, guesses = Dict(), kwargs...) where {iip, spec} + eval_module = @__MODULE__, guesses = Dict(), callback = nothing, kwargs...) where { + iip, spec} check_complete(sys, BVProblem) check_compatibility && check_compatible_system(BVProblem, sys) + isnothing(callback) || error("BVP solvers do not support callbacks.") - # ODESystems without algebraic equations should use both fixed values + guesses + # Systems without algebraic equations should use both fixed values + guesses # for initialization. _u0map = has_alg_eqs(sys) ? u0map : merge(Dict(u0map), Dict(guesses)) fode, u0, p = process_SciMLProblem( @@ -63,12 +65,17 @@ If the `System` has algebraic equations, like `x(t)^2 + y(t)^2`, the resulting u0_idxs = has_alg_eqs(sys) ? collect(1:length(dvs)) : [stidxmap[k] for (k, v) in u0map] fbc = generate_boundary_conditions( sys, u0, u0_idxs, tspan; expression = Val{false}, cse, checkbounds) + + if (length(constraints(sys)) + length(u0map) > length(dvs)) + @warn "The BVProblem is overdetermined. The total number of conditions (# constraints + # fixed initial values given by u0map) exceeds the total number of states. The BVP solvers will default to doing a nonlinear least-squares optimization." + end + kwargs = process_kwargs(sys; kwargs...) # Call `remake` so it runs initialization if it is trivial return remake(BVProblem{iip}(fode, fbc, u0, tspan[1], p; kwargs...)) end -function check_compatible_system(T::Union{Type{BVPFunction}, Type{BVProblem}}, sys::System) +function check_compatible_system(T::Type{BVProblem}, sys::System) check_time_dependent(sys, T) check_not_dde(sys) check_no_cost(sys, T) @@ -76,4 +83,8 @@ function check_compatible_system(T::Union{Type{BVPFunction}, Type{BVProblem}}, s check_no_jumps(sys, T) check_no_noise(sys, T) check_is_continuous(sys, T) + + if !isempty(discrete_events(sys)) || !isempty(continuous_events(sys)) + throw(SystemCompatibilityError("BVP solvers do not support events.")) + end end From 8c3a391eaa29045cdca87848ef1d2bfe6b7bf4ac Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Mon, 21 Apr 2025 12:41:46 +0530 Subject: [PATCH 063/185] feat: implement `SteadyStateProblem` for `System` --- src/problems/odeproblem.jl | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/src/problems/odeproblem.jl b/src/problems/odeproblem.jl index fe5e9ac4ac..a62863216c 100644 --- a/src/problems/odeproblem.jl +++ b/src/problems/odeproblem.jl @@ -72,8 +72,39 @@ end f, u0, tspan, p, StandardODEProblem(); kwargs...)) end +""" +```julia +SciMLBase.SteadyStateProblem(sys::System, u0map, + parammap = DiffEqBase.NullParameters(); + version = nothing, tgrad = false, + jac = false, + checkbounds = false, sparse = false, + linenumbers = true, parallel = SerialForm(), + kwargs...) where {iip} +``` + +Generates an SteadyStateProblem from a `System` of ODEs and allows for automatically +symbolically calculating numerical enhancements. +""" +@fallback_iip_specialize function DiffEqBase.SteadyStateProblem{iip, spec}( + sys::System, u0map, + parammap = SciMLBase.NullParameters(); check_length = true, + check_compatibility = true, kwargs...) where {iip, spec} + check_complete(sys, SteadyStateProblem) + check_compatibility && check_compatible_system(SteadyStateProblem, sys) + + f, u0, p = process_SciMLProblem(ODEFunction{iip}, sys, u0map, parammap; + steady_state = true, check_length, check_compatibility, + force_initialization_time_independent = true, kwargs...) + + kwargs = process_kwargs(sys; kwargs...) + # Call `remake` so it runs initialization if it is trivial + remake(SteadyStateProblem{iip}(f, u0, p; kwargs...)) +end + function check_compatible_system( - T::Union{Type{ODEFunction}, Type{ODEProblem}, Type{DAEFunction}, Type{DAEProblem}}, + T::Union{Type{ODEFunction}, Type{ODEProblem}, Type{DAEFunction}, + Type{DAEProblem}, Type{SteadyStateProblem}}, sys::System) check_time_dependent(sys, T) check_not_dde(sys) From 9aaf7dc632cce34c117cfcbab3bda5b15b6f7cca Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Mon, 21 Apr 2025 12:42:52 +0530 Subject: [PATCH 064/185] fix: fix `toexpr(::AbstractSystem)` --- src/systems/abstractsystem.jl | 21 +++++++-------------- 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/src/systems/abstractsystem.jl b/src/systems/abstractsystem.jl index 6e613f272c..3d95572127 100644 --- a/src/systems/abstractsystem.jl +++ b/src/systems/abstractsystem.jl @@ -2037,20 +2037,13 @@ function toexpr(sys::AbstractSystem) defs_name = push_defaults!(stmt, filtered_defs, var2name) obs_name = push_eqs!(stmt, obs, var2name) - if sys isa ODESystem - iv = get_iv(sys) - ivname = gensym(:iv) - push!(stmt, :($ivname = (@variables $(getname(iv)))[1])) - push!(stmt, - :($ODESystem($eqs_name, $ivname, $stsname, $psname; defaults = $defs_name, - observed = $obs_name, - name = $name, checks = false))) - elseif sys isa NonlinearSystem - push!(stmt, - :($NonlinearSystem($eqs_name, $stsname, $psname; defaults = $defs_name, - observed = $obs_name, - name = $name, checks = false))) - end + iv = get_iv(sys) + ivname = gensym(:iv) + push!(stmt, :($ivname = (@variables $(getname(iv)))[1])) + push!(stmt, + :($System($eqs_name, $ivname, $stsname, $psname; defaults = $defs_name, + observed = $obs_name, + name = $name, checks = false))) expr = :(let $expr From 9078b61a40a695365bdb8651cfbb89402d3a6f33 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Mon, 21 Apr 2025 12:43:12 +0530 Subject: [PATCH 065/185] fix: fix `extend(::AbstractSystem)` --- src/systems/abstractsystem.jl | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/systems/abstractsystem.jl b/src/systems/abstractsystem.jl index 3d95572127..3212dabe9f 100644 --- a/src/systems/abstractsystem.jl +++ b/src/systems/abstractsystem.jl @@ -2711,11 +2711,9 @@ function extend(sys::AbstractSystem, basesys::AbstractSystem; name = name, description = description, gui_metadata = gui_metadata) # collect fields specific to some system types - if basesys isa ODESystem - ieqs = union(get_initialization_eqs(basesys), get_initialization_eqs(sys)) - guesses = merge(get_guesses(basesys), get_guesses(sys)) # prefer `sys` - kwargs = merge(kwargs, (initialization_eqs = ieqs, guesses = guesses)) - end + ieqs = union(get_initialization_eqs(basesys), get_initialization_eqs(sys)) + guesses = merge(get_guesses(basesys), get_guesses(sys)) # prefer `sys` + kwargs = merge(kwargs, (initialization_eqs = ieqs, guesses = guesses)) if has_assertions(basesys) kwargs = merge( From 3236f71812f88cf7b446ce5cb139ba838b3d4ea9 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Mon, 21 Apr 2025 12:43:45 +0530 Subject: [PATCH 066/185] fix: fix `substitute(::AbstractSystem, _...)` --- src/systems/abstractsystem.jl | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/systems/abstractsystem.jl b/src/systems/abstractsystem.jl index 3212dabe9f..e43387ff82 100644 --- a/src/systems/abstractsystem.jl +++ b/src/systems/abstractsystem.jl @@ -2814,7 +2814,7 @@ function Symbolics.substitute(sys::AbstractSystem, rules::Union{Vector{<:Pair}, # post-walk to avoid infinite recursion @set! sys.systems = map(Base.Fix2(substitute, dict), systems) something(get(rules, nameof(sys), nothing), sys) - elseif sys isa ODESystem + elseif sys isa System rules = todict(map(r -> Symbolics.unwrap(r[1]) => Symbolics.unwrap(r[2]), collect(rules))) eqs = fast_substitute(get_eqs(sys), rules) @@ -2823,9 +2823,15 @@ function Symbolics.substitute(sys::AbstractSystem, rules::Union{Vector{<:Pair}, for (k, v) in get_defaults(sys)) guess = Dict(fast_substitute(k, rules) => fast_substitute(v, rules) for (k, v) in get_guesses(sys)) + noise_eqs = fast_substitute(get_noise_eqs(sys), rules) + costs = fast_substitute(get_costs(sys), rules) + observed = fast_substitute(get_observed(sys), rules) + initialization_eqs = fast_substitute(get_initialization_eqs(sys), rules) + cstrs = fast_substitute(get_constraints(sys), rules) subsys = map(s -> substitute(s, rules), get_systems(sys)) - ODESystem(eqs, get_iv(sys); name = nameof(sys), defaults = defs, - guesses = guess, parameter_dependencies = pdeps, systems = subsys) + System(eqs, get_iv(sys); name = nameof(sys), defaults = defs, + guesses = guess, parameter_dependencies = pdeps, systems = subsys, noise_eqs, + observed, initialization_eqs, constraints = cstrs) else error("substituting symbols is not supported for $(typeof(sys))") end From d0690369d086eb15102c35ae2452274259937266 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Mon, 21 Apr 2025 12:44:12 +0530 Subject: [PATCH 067/185] fix: construct `System` in `@mtkmodel` --- src/systems/model_parsing.jl | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/systems/model_parsing.jl b/src/systems/model_parsing.jl index 195b02118e..1a726a5c60 100644 --- a/src/systems/model_parsing.jl +++ b/src/systems/model_parsing.jl @@ -70,8 +70,7 @@ function _model_macro(mod, fullname::Union{Expr, Symbol}, expr, isconnector) push!(exprs.args, :(variables = [])) push!(exprs.args, :(parameters = [])) - # We build `System` by default; vectors can't be created for `System` as it is - # a function. + # We build `System` by default push!(exprs.args, :(systems = ModelingToolkit.AbstractSystem[])) push!(exprs.args, :(equations = Union{Equation, Vector{Equation}}[])) push!(exprs.args, :(defaults = Dict{Num, Union{Number, Symbol, Function}}())) From e90da4133ec8b5e64c8c0dc8d4f94efa761e91a2 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Mon, 21 Apr 2025 12:44:31 +0530 Subject: [PATCH 068/185] docs: fix docstring of `process_SciMLProblem` --- src/systems/problem_utils.jl | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/systems/problem_utils.jl b/src/systems/problem_utils.jl index 95add1d990..805a237c6c 100644 --- a/src/systems/problem_utils.jl +++ b/src/systems/problem_utils.jl @@ -996,12 +996,11 @@ Initial values provided in terms of other variables will be symbolically evaluat type of the containers (if parameters are not in an `MTKParameters` object). `Dict`s will be turned into `Array`s. -If `sys isa ODESystem`, this will also build the initialization problem and related objects -and pass them to the SciMLFunction as keyword arguments. +This will also build the initialization problem and related objects and pass them to the +SciMLFunction as keyword arguments. Keyword arguments: -- `build_initializeprob`: If `false`, avoids building the initialization problem for an - `ODESystem`. +- `build_initializeprob`: If `false`, avoids building the initialization problem. - `t`: The initial time of the `ODEProblem`. If this is not provided, the initialization problem cannot be built. - `implicit_dae`: Also build a mapping of derivatives of states to values for implicit DAEs, From 8593c7d2fc2faeb5c3029ca8bbc2ca74c735885f Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Mon, 21 Apr 2025 12:44:46 +0530 Subject: [PATCH 069/185] refactor: move `filter_kwargs` and `SymbolicTstops` to `problem_utils.jl` --- src/systems/problem_utils.jl | 41 ++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/src/systems/problem_utils.jl b/src/systems/problem_utils.jl index 805a237c6c..a87c0a358e 100644 --- a/src/systems/problem_utils.jl +++ b/src/systems/problem_utils.jl @@ -1254,6 +1254,47 @@ function process_kwargs(sys::System; callback = nothing, eval_expression = false return merge(kwargs1, kwargs) end +function filter_kwargs(kwargs) + kwargs = Dict(kwargs) + for key in keys(kwargs) + key in DiffEqBase.allowedkeywords || delete!(kwargs, key) + end + pairs(NamedTuple(kwargs)) +end + +struct SymbolicTstops{F} + fn::F +end + +function (st::SymbolicTstops)(p, tspan) + unique!(sort!(reduce(vcat, st.fn(p, tspan...)))) +end + +function SymbolicTstops( + sys::AbstractSystem; eval_expression = false, eval_module = @__MODULE__) + tstops = symbolic_tstops(sys) + isempty(tstops) && return nothing + t0 = gensym(:t0) + t1 = gensym(:t1) + tstops = map(tstops) do val + if is_array_of_symbolics(val) || val isa AbstractArray + collect(val) + else + term(:, t0, unwrap(val), t1; type = AbstractArray{Real}) + end + end + rps = reorder_parameters(sys) + tstops, _ = build_function_wrapper(sys, tstops, + rps..., + t0, + t1; + expression = Val{true}, + p_start = 1, p_end = length(rps), add_observed = false, force_SA = true) + tstops = eval_or_rgf(tstops; eval_expression, eval_module) + tstops = GeneratedFunctionWrapper{(1, 3, is_split(sys))}(tstops, nothing) + return SymbolicTstops(tstops) +end + """ $(TYPEDSIGNATURES) From 1900978ec3455d7a511861ef03351285099842f7 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Mon, 21 Apr 2025 12:44:59 +0530 Subject: [PATCH 070/185] feat: add `schedule` field in `System` --- src/systems/system.jl | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/src/systems/system.jl b/src/systems/system.jl index c89a46ccaf..6d463733cd 100644 --- a/src/systems/system.jl +++ b/src/systems/system.jl @@ -1,3 +1,14 @@ +struct Schedule{V <: BipartiteGraphs.Matching} + """ + Maximal matching of variables to equations calculated during structural simplification. + """ + var_eq_matching::V + """ + Mapping of `Differential`s of variables to corresponding derivative expressions. + """ + dummy_sub::Dict{Any, Any} +end + struct System <: AbstractSystem tag::UInt eqs::Vector{Equation} @@ -40,6 +51,7 @@ struct System <: AbstractSystem parent::Union{Nothing, System} is_initializesystem::Bool isscheduled::Bool + schedule::Union{Schedule, Nothing} function System( tag, eqs, noise_eqs, jumps, constraints, costs, consolidate, unknowns, ps, @@ -49,8 +61,8 @@ struct System <: AbstractSystem metadata = nothing, gui_metadata = nothing, is_dde = false, tstops = [], tearing_state = nothing, namespacing = true, complete = false, index_cache = nothing, ignored_connections = nothing, - parent = nothing, is_initializesystem = false, isscheduled = false; - checks::Union{Bool, Int} = true) + parent = nothing, is_initializesystem = false, isscheduled = false, + schedule = nothing; checks::Union{Bool, Int} = true) if is_initializesystem && iv !== nothing throw(ArgumentError(""" @@ -81,7 +93,7 @@ struct System <: AbstractSystem guesses, systems, initialization_eqs, continuous_events, discrete_events, connector_type, assertions, metadata, gui_metadata, is_dde, tstops, tearing_state, namespacing, complete, index_cache, ignored_connections, - parent, isscheduled) + parent, is_initializesystem, isscheduled, schedule) end end @@ -318,7 +330,7 @@ function validate_vars_and_find_ps!(auxvars, auxps, sysvars, iv) elseif length(arguments(var)) == 1 arg = only(arguments(var)) operation(var)(iv) ∈ sts || - throw(ArgumentError("Variable $var is not a variable of the ODESystem. Called variables must be variables of the ODESystem.")) + throw(ArgumentError("Variable $var is not a variable of the System. Called variables must be variables of the System.")) isequal(arg, iv) || isparameter(arg) || arg isa Integer || arg isa AbstractFloat || @@ -346,12 +358,12 @@ end SymbolicIndexingInterface.is_time_dependent(sys::System) = get_iv(sys) !== nothing """ - is_dde(sys::System) + is_dde(sys::AbstractSystem) Return a boolean indicating whether a system represents a set of delay differential equations. """ -is_dde(sys::System) = has_is_dde(sys) && get_is_dde(sys) +is_dde(sys::AbstractSystem) = has_is_dde(sys) && get_is_dde(sys) function _check_if_dde(eqs, iv, subsystems) is_dde = any(ModelingToolkit.is_dde, subsystems) From 3c749f3de92b988c94658ed45e32ace4777c80a2 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Mon, 21 Apr 2025 12:53:34 +0530 Subject: [PATCH 071/185] refactor: move `flatten_equations` to `utils.jl` --- src/utils.jl | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/src/utils.jl b/src/utils.jl index ec58de13b5..a45c8a23f0 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -1326,3 +1326,31 @@ function _eq_unordered(a::AbstractArray, b::AbstractArray) end return true end + +""" + $(TYPEDSIGNATURES) + +Given a list of equations where some may be array equations, flatten the array equations +without scalarizing occurrences of array variables and return the new list of equations. +""" +function flatten_equations(eqs::Vector{Equation}) + mapreduce(vcat, eqs; init = Equation[]) do eq + islhsarr = eq.lhs isa AbstractArray || Symbolics.isarraysymbolic(eq.lhs) + isrhsarr = eq.rhs isa AbstractArray || Symbolics.isarraysymbolic(eq.rhs) + if islhsarr || isrhsarr + islhsarr && isrhsarr || + error(""" + LHS ($(eq.lhs)) and RHS ($(eq.rhs)) must either both be array expressions \ + or both scalar + """) + size(eq.lhs) == size(eq.rhs) || + error(""" + Size of LHS ($(eq.lhs)) and RHS ($(eq.rhs)) must match: got \ + $(size(eq.lhs)) and $(size(eq.rhs)) + """) + return vec(collect(eq.lhs) .~ collect(eq.rhs)) + else + eq + end + end +end From 27e617a9412e73c70f087c33925a5bc758053985 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Mon, 21 Apr 2025 12:54:14 +0530 Subject: [PATCH 072/185] refactor: remove old clock handling code, retain error messages --- src/systems/systemstructure.jl | 66 +++++++++++----------------------- 1 file changed, 21 insertions(+), 45 deletions(-) diff --git a/src/systems/systemstructure.jl b/src/systems/systemstructure.jl index e0feb0d34d..ecbbbed0de 100644 --- a/src/systems/systemstructure.jl +++ b/src/systems/systemstructure.jl @@ -671,53 +671,29 @@ end function structural_simplify!(state::TearingState, io = nothing; simplify = false, check_consistency = true, fully_determined = true, warn_initialize_determined = true, kwargs...) - if state.sys isa ODESystem - ci = ModelingToolkit.ClockInference(state) - ci = ModelingToolkit.infer_clocks!(ci) - time_domains = merge(Dict(state.fullvars .=> ci.var_domain), - Dict(default_toterm.(state.fullvars) .=> ci.var_domain)) - tss, inputs, continuous_id, id_to_clock = ModelingToolkit.split_system(ci) - cont_io = merge_io(io, inputs[continuous_id]) - sys, input_idxs = _structural_simplify!(tss[continuous_id], cont_io; simplify, - check_consistency, fully_determined, - kwargs...) - if length(tss) > 1 - if continuous_id > 0 - throw(HybridSystemNotSupportedException("Hybrid continuous-discrete systems are currently not supported with the standard MTK compiler. This system requires JuliaSimCompiler.jl, see https://help.juliahub.com/juliasimcompiler/stable/")) - end - # TODO: rename it to something else - discrete_subsystems = Vector{ODESystem}(undef, length(tss)) - # Note that the appended_parameters must agree with - # `generate_discrete_affect`! - appended_parameters = parameters(sys) - for (i, state) in enumerate(tss) - if i == continuous_id - discrete_subsystems[i] = sys - continue - end - dist_io = merge_io(io, inputs[i]) - ss, = _structural_simplify!(state, dist_io; simplify, check_consistency, - fully_determined, kwargs...) - append!(appended_parameters, inputs[i], unknowns(ss)) - discrete_subsystems[i] = ss - end - @set! sys.discrete_subsystems = discrete_subsystems, inputs, continuous_id, - id_to_clock - @set! sys.ps = appended_parameters - @set! sys.defaults = merge(ModelingToolkit.defaults(sys), - Dict(v => 0.0 for v in Iterators.flatten(inputs))) + ci = ModelingToolkit.ClockInference(state) + ci = ModelingToolkit.infer_clocks!(ci) + time_domains = merge(Dict(state.fullvars .=> ci.var_domain), + Dict(default_toterm.(state.fullvars) .=> ci.var_domain)) + tss, inputs, continuous_id, id_to_clock = ModelingToolkit.split_system(ci) + if length(tss) > 1 + if continuous_id == 0 + throw(HybridSystemNotSupportedException(""" + Discrete systems with multiple clocks are not supported with the standard \ + MTK compiler. + """)) + else + throw(HybridSystemNotSupportedException(""" + Hybrid continuous-discrete systems are currently not supported with \ + the standard MTK compiler. This system requires JuliaSimCompiler.jl, \ + see https://help.juliahub.com/juliasimcompiler/stable/ + """)) end - ps = [sym isa CallWithMetadata ? sym : - setmetadata( - sym, VariableTimeDomain, get(time_domains, sym, ContinuousClock())) - for sym in get_ps(sys)] - @set! sys.ps = ps - else - sys, input_idxs = _structural_simplify!(state, io; simplify, check_consistency, - fully_determined, kwargs...) end - has_io = io !== nothing - return has_io ? (sys, input_idxs) : sys + + sys, input_idxs = _structural_simplify!(state, io; simplify, check_consistency, + fully_determined, kwargs...) + return io !== nothing ? (sys, input_idxs) : sys end function _structural_simplify!(state::TearingState, io; simplify = false, From 75f9d9a23b880c6853f9d057238bcf3d6777e856 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Mon, 21 Apr 2025 12:54:47 +0530 Subject: [PATCH 073/185] fix: add temporary error message when simplifying systems with `noise_eqs` --- src/systems/systems.jl | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/systems/systems.jl b/src/systems/systems.jl index 82a47d4f52..38aaf45363 100644 --- a/src/systems/systems.jl +++ b/src/systems/systems.jl @@ -68,6 +68,11 @@ end function __structural_simplify( sys::AbstractSystem, io = nothing; simplify = false, sort_eqs = true, kwargs...) + # TODO: convert noise_eqs to brownians for simplification + if has_noise_eqs(sys) && get_noise_eqs(sys) !== nothing + throw(ArgumentError("Cannot simplify systems with `noise_eqs`")) + end + sys = expand_connections(sys) state = TearingState(sys; sort_eqs) From 92f3d561698a320e06b5bf8564976368f1b8c0c4 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Mon, 21 Apr 2025 12:55:17 +0530 Subject: [PATCH 074/185] refactor: remove `abstractodesystem.jl` --- src/ModelingToolkit.jl | 1 - src/systems/diffeqs/abstractodesystem.jl | 1311 ---------------------- 2 files changed, 1312 deletions(-) delete mode 100644 src/systems/diffeqs/abstractodesystem.jl diff --git a/src/ModelingToolkit.jl b/src/ModelingToolkit.jl index 4d134fd8b4..d30ae64efb 100644 --- a/src/ModelingToolkit.jl +++ b/src/ModelingToolkit.jl @@ -165,7 +165,6 @@ include("linearization.jl") include("systems/optimization/modelingtoolkitize.jl") include("systems/nonlinear/homotopy_continuation.jl") -include("systems/diffeqs/abstractodesystem.jl") include("systems/nonlinear/modelingtoolkitize.jl") include("systems/nonlinear/initializesystem.jl") include("systems/diffeqs/first_order_transform.jl") diff --git a/src/systems/diffeqs/abstractodesystem.jl b/src/systems/diffeqs/abstractodesystem.jl deleted file mode 100644 index 7ae0eb3e52..0000000000 --- a/src/systems/diffeqs/abstractodesystem.jl +++ /dev/null @@ -1,1311 +0,0 @@ -struct Schedule - var_eq_matching::Any - dummy_sub::Any -end - -""" - is_dde(sys::AbstractSystem) - -Return a boolean indicating whether a system represents a set of delay -differential equations. -""" -is_dde(sys::AbstractSystem) = has_is_dde(sys) && get_is_dde(sys) - -function filter_kwargs(kwargs) - kwargs = Dict(kwargs) - for key in keys(kwargs) - key in DiffEqBase.allowedkeywords || delete!(kwargs, key) - end - pairs(NamedTuple(kwargs)) -end -function gen_quoted_kwargs(kwargs) - kwargparam = Expr(:parameters) - for kw in kwargs - push!(kwargparam.args, Expr(:kw, kw[1], kw[2])) - end - kwargparam -end - -function calculate_tgrad(sys::AbstractODESystem; - simplify = false) - isempty(get_tgrad(sys)[]) || return get_tgrad(sys)[] # use cached tgrad, if possible - - # We need to remove explicit time dependence on the unknown because when we - # have `u(t) * t` we want to have the tgrad to be `u(t)` instead of `u'(t) * - # t + u(t)`. - rhs = [detime_dvs(eq.rhs) for eq in full_equations(sys)] - iv = get_iv(sys) - xs = unknowns(sys) - rule = Dict(map((x, xt) -> xt => x, detime_dvs.(xs), xs)) - rhs = substitute.(rhs, Ref(rule)) - tgrad = [expand_derivatives(Differential(iv)(r), simplify) for r in rhs] - reverse_rule = Dict(map((x, xt) -> x => xt, detime_dvs.(xs), xs)) - tgrad = Num.(substitute.(tgrad, Ref(reverse_rule))) - get_tgrad(sys)[] = tgrad - return tgrad -end - -function calculate_jacobian(sys::AbstractODESystem; - sparse = false, simplify = false, dvs = unknowns(sys)) - if isequal(dvs, unknowns(sys)) - cache = get_jac(sys)[] - if cache isa Tuple && cache[2] == (sparse, simplify) - return cache[1] - end - end - - rhs = [eq.rhs - eq.lhs for eq in full_equations(sys)] #need du terms on rhs for differentiating wrt du - - if sparse - jac = sparsejacobian(rhs, dvs, simplify = simplify) - W_s = W_sparsity(sys) - (Is, Js, Vs) = findnz(W_s) - # Add nonzeros of W as non-structural zeros of the Jacobian (to ensure equal results for oop and iip Jacobian.) - for (i, j) in zip(Is, Js) - iszero(jac[i, j]) && begin - jac[i, j] = 1 - jac[i, j] = 0 - end - end - else - jac = jacobian(rhs, dvs, simplify = simplify) - end - - if isequal(dvs, unknowns(sys)) - get_jac(sys)[] = jac, (sparse, simplify) # cache Jacobian - end - - return jac -end - -function calculate_control_jacobian(sys::AbstractODESystem; - sparse = false, simplify = false) - cache = get_ctrl_jac(sys)[] - if cache isa Tuple && cache[2] == (sparse, simplify) - return cache[1] - end - - rhs = [eq.rhs for eq in full_equations(sys)] - ctrls = controls(sys) - - if sparse - jac = sparsejacobian(rhs, ctrls, simplify = simplify) - else - jac = jacobian(rhs, ctrls, simplify = simplify) - end - - get_ctrl_jac(sys)[] = jac, (sparse, simplify) # cache Jacobian - return jac -end - -function generate_tgrad( - sys::AbstractODESystem, dvs = unknowns(sys), ps = parameters( - sys; initial_parameters = true); - simplify = false, kwargs...) - tgrad = calculate_tgrad(sys, simplify = simplify) - p = reorder_parameters(sys, ps) - return build_function_wrapper(sys, tgrad, - dvs, - p..., - get_iv(sys); - kwargs...) -end - -function generate_jacobian(sys::AbstractODESystem, dvs = unknowns(sys), - ps = parameters(sys; initial_parameters = true); - simplify = false, sparse = false, kwargs...) - jac = calculate_jacobian(sys; simplify = simplify, sparse = sparse) - p = reorder_parameters(sys, ps) - return build_function_wrapper(sys, jac, - dvs, - p..., - get_iv(sys); - wrap_code = sparse ? assert_jac_length_header(sys) : (identity, identity), - kwargs...) -end - -function generate_W(sys::AbstractODESystem, γ = 1.0, dvs = unknowns(sys), - ps = parameters(sys; initial_parameters = true); - simplify = false, sparse = false, kwargs...) - @variables ˍ₋gamma - M = calculate_massmatrix(sys; simplify) - sparse && (M = SparseArrays.sparse(M)) - J = calculate_jacobian(sys; simplify, sparse, dvs) - W = ˍ₋gamma * M + J - - p = reorder_parameters(sys, ps) - return build_function_wrapper(sys, W, - dvs, - p..., - ˍ₋gamma, - get_iv(sys); - wrap_code = sparse ? assert_jac_length_header(sys) : (identity, identity), - p_end = 1 + length(p), - kwargs...) -end - -function generate_control_jacobian(sys::AbstractODESystem, dvs = unknowns(sys), - ps = parameters(sys; initial_parameters = true); - simplify = false, sparse = false, kwargs...) - jac = calculate_control_jacobian(sys; simplify = simplify, sparse = sparse) - p = reorder_parameters(sys, ps) - return build_function_wrapper(sys, jac, dvs, p..., get_iv(sys); kwargs...) -end - -function generate_dae_jacobian(sys::AbstractODESystem, dvs = unknowns(sys), - ps = parameters(sys; initial_parameters = true); simplify = false, sparse = false, - kwargs...) - jac_u = calculate_jacobian(sys; simplify = simplify, sparse = sparse) - derivatives = Differential(get_iv(sys)).(unknowns(sys)) - jac_du = calculate_jacobian(sys; simplify = simplify, sparse = sparse, - dvs = derivatives) - dvs = unknowns(sys) - @variables ˍ₋gamma - jac = ˍ₋gamma * jac_du + jac_u - pre = get_preprocess_constants(jac) - p = reorder_parameters(sys, ps) - return build_function_wrapper(sys, jac, derivatives, dvs, p..., ˍ₋gamma, get_iv(sys); - p_start = 3, p_end = 2 + length(p), kwargs...) -end - -function generate_function(sys::AbstractODESystem, dvs = unknowns(sys), - ps = parameters(sys; initial_parameters = true); - implicit_dae = false, - ddvs = implicit_dae ? map(Differential(get_iv(sys)), dvs) : - nothing, - isdde = false, - kwargs...) - eqs = [eq for eq in equations(sys)] - if !implicit_dae - check_operator_variables(eqs, Differential) - check_lhs(eqs, Differential, Set(dvs)) - end - - rhss = implicit_dae ? [_iszero(eq.lhs) ? eq.rhs : eq.rhs - eq.lhs for eq in eqs] : - [eq.rhs for eq in eqs] - - if !isempty(assertions(sys)) - rhss[end] += unwrap(get_assertions_expr(sys)) - end - - # TODO: add an optional check on the ordering of observed equations - u = dvs - p = reorder_parameters(sys, ps) - t = get_iv(sys) - - if implicit_dae - build_function_wrapper(sys, rhss, ddvs, u, p..., t; p_start = 3, kwargs...) - else - build_function_wrapper(sys, rhss, u, p..., t; kwargs...) - end -end - -function calculate_massmatrix(sys::AbstractODESystem; simplify = false) - eqs = [eq for eq in equations(sys)] - M = zeros(length(eqs), length(eqs)) - for (i, eq) in enumerate(eqs) - if iscall(eq.lhs) && operation(eq.lhs) isa Differential - st = var_from_nested_derivative(eq.lhs)[1] - j = variable_index(sys, st) - M[i, j] = 1 - else - _iszero(eq.lhs) || - error("Only semi-explicit constant mass matrices are currently supported. Faulty equation: $eq.") - end - end - M = simplify ? ModelingToolkit.simplify.(M) : M - # M should only contain concrete numbers - M == I ? I : M -end - -function jacobian_sparsity(sys::AbstractODESystem) - sparsity = torn_system_jacobian_sparsity(sys) - sparsity === nothing || return sparsity - - jacobian_sparsity([eq.rhs for eq in full_equations(sys)], - [dv for dv in unknowns(sys)]) -end - -function jacobian_dae_sparsity(sys::AbstractODESystem) - J1 = jacobian_sparsity([eq.rhs for eq in full_equations(sys)], - [dv for dv in unknowns(sys)]) - derivatives = Differential(get_iv(sys)).(unknowns(sys)) - J2 = jacobian_sparsity([eq.rhs for eq in full_equations(sys)], - [dv for dv in derivatives]) - J1 + J2 -end - -function W_sparsity(sys::AbstractODESystem) - jac_sparsity = jacobian_sparsity(sys) - (n, n) = size(jac_sparsity) - M = calculate_massmatrix(sys) - M_sparsity = M isa UniformScaling ? sparse(I(n)) : - SparseMatrixCSC{Bool, Int64}((!iszero).(M)) - jac_sparsity .| M_sparsity -end - -function isautonomous(sys::AbstractODESystem) - tgrad = calculate_tgrad(sys; simplify = true) - all(iszero, tgrad) -end - -""" -```julia -DiffEqBase.ODEFunction{iip}(sys::AbstractODESystem, dvs = unknowns(sys), - ps = parameters(sys); - version = nothing, tgrad = false, - jac = false, - sparse = false, - kwargs...) where {iip} -``` - -Create an `ODEFunction` from the [`ODESystem`](@ref). The arguments `dvs` and `ps` -are used to set the order of the dependent variable and parameter vectors, -respectively. -""" -function DiffEqBase.ODEFunction(sys::AbstractODESystem, args...; kwargs...) - ODEFunction{true}(sys, args...; kwargs...) -end - -function DiffEqBase.ODEFunction{true}(sys::AbstractODESystem, args...; - kwargs...) - ODEFunction{true, SciMLBase.AutoSpecialize}(sys, args...; kwargs...) -end - -function DiffEqBase.ODEFunction{false}(sys::AbstractODESystem, args...; - kwargs...) - ODEFunction{false, SciMLBase.FullSpecialize}(sys, args...; kwargs...) -end - -function DiffEqBase.ODEFunction{iip, specialize}(sys::AbstractODESystem, - dvs = unknowns(sys), - ps = parameters(sys), u0 = nothing; - version = nothing, tgrad = false, - jac = false, p = nothing, - t = nothing, - eval_expression = false, - sparse = false, simplify = false, - eval_module = @__MODULE__, - steady_state = false, - checkbounds = false, - sparsity = false, - analytic = nothing, - split_idxs = nothing, - initialization_data = nothing, - cse = true, - kwargs...) where {iip, specialize} - if !iscomplete(sys) - error("A completed system is required. Call `complete` or `structural_simplify` on the system before creating an `ODEFunction`") - end - f_gen = generate_function(sys, dvs, ps; expression = Val{true}, - expression_module = eval_module, checkbounds = checkbounds, cse, - kwargs...) - f_oop, f_iip = eval_or_rgf.(f_gen; eval_expression, eval_module) - f = GeneratedFunctionWrapper{(2, 3, is_split(sys))}(f_oop, f_iip) - - if specialize === SciMLBase.FunctionWrapperSpecialize && iip - if u0 === nothing || p === nothing || t === nothing - error("u0, p, and t must be specified for FunctionWrapperSpecialize on ODEFunction.") - end - f = SciMLBase.wrapfun_iip(f, (u0, u0, p, t)) - end - - if tgrad - tgrad_gen = generate_tgrad(sys, dvs, ps; - simplify = simplify, - expression = Val{true}, - expression_module = eval_module, cse, - checkbounds = checkbounds, kwargs...) - tgrad_oop, tgrad_iip = eval_or_rgf.(tgrad_gen; eval_expression, eval_module) - _tgrad = GeneratedFunctionWrapper{(2, 3, is_split(sys))}(tgrad_oop, tgrad_iip) - else - _tgrad = nothing - end - - if jac - jac_gen = generate_jacobian(sys, dvs, ps; - simplify = simplify, sparse = sparse, - expression = Val{true}, - expression_module = eval_module, cse, - checkbounds = checkbounds, kwargs...) - jac_oop, jac_iip = eval_or_rgf.(jac_gen; eval_expression, eval_module) - - _jac = GeneratedFunctionWrapper{(2, 3, is_split(sys))}(jac_oop, jac_iip) - else - _jac = nothing - end - - M = calculate_massmatrix(sys) - - _M = if sparse && !(u0 === nothing || M === I) - SparseArrays.sparse(M) - elseif u0 === nothing || M === I - M - else - ArrayInterface.restructure(u0 .* u0', M) - end - - observedfun = ObservedFunctionCache( - sys; steady_state, eval_expression, eval_module, checkbounds, cse) - - if sparse - uElType = u0 === nothing ? Float64 : eltype(u0) - W_prototype = similar(W_sparsity(sys), uElType) - else - W_prototype = nothing - end - - @set! sys.split_idxs = split_idxs - - ODEFunction{iip, specialize}(f; - sys = sys, - jac = _jac === nothing ? nothing : _jac, - tgrad = _tgrad === nothing ? nothing : _tgrad, - mass_matrix = _M, - jac_prototype = W_prototype, - observed = observedfun, - sparsity = sparsity ? W_sparsity(sys) : nothing, - analytic = analytic, - initialization_data) -end - -""" -```julia -DiffEqBase.DAEFunction{iip}(sys::AbstractODESystem, dvs = unknowns(sys), - ps = parameters(sys); - version = nothing, tgrad = false, - jac = false, - sparse = false, - kwargs...) where {iip} -``` - -Create an `DAEFunction` from the [`ODESystem`](@ref). The arguments `dvs` and -`ps` are used to set the order of the dependent variable and parameter vectors, -respectively. -""" -function DiffEqBase.DAEFunction(sys::AbstractODESystem, args...; kwargs...) - DAEFunction{true}(sys, args...; kwargs...) -end - -function DiffEqBase.DAEFunction{iip}(sys::AbstractODESystem, dvs = unknowns(sys), - ps = parameters(sys), u0 = nothing; - ddvs = map(Base.Fix2(diff2term, get_iv(sys)) ∘ Differential(get_iv(sys)), dvs), - version = nothing, p = nothing, - jac = false, - eval_expression = false, - sparse = false, simplify = false, - eval_module = @__MODULE__, - checkbounds = false, - initialization_data = nothing, - cse = true, - kwargs...) where {iip} - if !iscomplete(sys) - error("A completed system is required. Call `complete` or `structural_simplify` on the system before creating a `DAEFunction`") - end - f_gen = generate_function(sys, dvs, ps; implicit_dae = true, - expression = Val{true}, cse, - expression_module = eval_module, checkbounds = checkbounds, - kwargs...) - f_oop, f_iip = eval_or_rgf.(f_gen; eval_expression, eval_module) - f = GeneratedFunctionWrapper{(3, 4, is_split(sys))}(f_oop, f_iip) - - if jac - jac_gen = generate_dae_jacobian(sys, dvs, ps; - simplify = simplify, sparse = sparse, - expression = Val{true}, - expression_module = eval_module, cse, - checkbounds = checkbounds, kwargs...) - jac_oop, jac_iip = eval_or_rgf.(jac_gen; eval_expression, eval_module) - - _jac = GeneratedFunctionWrapper{(3, 5, is_split(sys))}(jac_oop, jac_iip) - else - _jac = nothing - end - - observedfun = ObservedFunctionCache( - sys; eval_expression, eval_module, checkbounds = get(kwargs, :checkbounds, false), cse) - - jac_prototype = if sparse - uElType = u0 === nothing ? Float64 : eltype(u0) - if jac - J1 = calculate_jacobian(sys, sparse = sparse) - derivatives = Differential(get_iv(sys)).(unknowns(sys)) - J2 = calculate_jacobian(sys; sparse = sparse, dvs = derivatives) - similar(J1 + J2, uElType) - else - similar(jacobian_dae_sparsity(sys), uElType) - end - else - nothing - end - - DAEFunction{iip}(f; - sys = sys, - jac = _jac === nothing ? nothing : _jac, - jac_prototype = jac_prototype, - observed = observedfun, - initialization_data) -end - -function DiffEqBase.DDEFunction(sys::AbstractODESystem, args...; kwargs...) - DDEFunction{true}(sys, args...; kwargs...) -end - -function DiffEqBase.DDEFunction{iip}(sys::AbstractODESystem, dvs = unknowns(sys), - ps = parameters(sys), u0 = nothing; - eval_expression = false, - eval_module = @__MODULE__, - checkbounds = false, - initialization_data = nothing, - cse = true, - kwargs...) where {iip} - if !iscomplete(sys) - error("A completed system is required. Call `complete` or `structural_simplify` on the system before creating an `DDEFunction`") - end - f_gen = generate_function(sys, dvs, ps; isdde = true, - expression = Val{true}, - expression_module = eval_module, checkbounds = checkbounds, - cse, kwargs...) - f_oop, f_iip = eval_or_rgf.(f_gen; eval_expression, eval_module) - f = GeneratedFunctionWrapper{(3, 4, is_split(sys))}(f_oop, f_iip) - - DDEFunction{iip}(f; sys = sys, initialization_data) -end - -function DiffEqBase.SDDEFunction(sys::AbstractODESystem, args...; kwargs...) - SDDEFunction{true}(sys, args...; kwargs...) -end - -function DiffEqBase.SDDEFunction{iip}(sys::AbstractODESystem, dvs = unknowns(sys), - ps = parameters(sys), u0 = nothing; - eval_expression = false, - eval_module = @__MODULE__, - checkbounds = false, - initialization_data = nothing, - cse = true, - kwargs...) where {iip} - if !iscomplete(sys) - error("A completed system is required. Call `complete` or `structural_simplify` on the system before creating an `SDDEFunction`") - end - f_gen = generate_function(sys, dvs, ps; isdde = true, - expression = Val{true}, - expression_module = eval_module, checkbounds = checkbounds, - cse, kwargs...) - f_oop, f_iip = eval_or_rgf.(f_gen; eval_expression, eval_module) - f = GeneratedFunctionWrapper{(3, 4, is_split(sys))}(f_oop, f_iip) - - g_gen = generate_diffusion_function(sys, dvs, ps; expression = Val{true}, - isdde = true, cse, kwargs...) - g_oop, g_iip = eval_or_rgf.(g_gen; eval_expression, eval_module) - g = GeneratedFunctionWrapper{(3, 4, is_split(sys))}(g_oop, g_iip) - - SDDEFunction{iip}(f, g; sys = sys, initialization_data) -end - -""" -```julia -ODEFunctionExpr{iip}(sys::AbstractODESystem, dvs = unknowns(sys), - ps = parameters(sys); - version = nothing, tgrad = false, - jac = false, - sparse = false, - kwargs...) where {iip} -``` - -Create a Julia expression for an `ODEFunction` from the [`ODESystem`](@ref). -The arguments `dvs` and `ps` are used to set the order of the dependent -variable and parameter vectors, respectively. -""" -struct ODEFunctionExpr{iip, specialize} end - -function ODEFunctionExpr{iip, specialize}(sys::AbstractODESystem, dvs = unknowns(sys), - ps = parameters(sys), u0 = nothing; - version = nothing, tgrad = false, - jac = false, p = nothing, - linenumbers = false, - sparse = false, simplify = false, - steady_state = false, - sparsity = false, - observedfun_exp = nothing, - kwargs...) where {iip, specialize} - if !iscomplete(sys) - error("A completed system is required. Call `complete` or `structural_simplify` on the system before creating an `ODEFunctionExpr`") - end - f_oop, f_iip = generate_function(sys, dvs, ps; expression = Val{true}, kwargs...) - - fsym = gensym(:f) - _f = :($fsym = $(GeneratedFunctionWrapper{(2, 3, is_split(sys))})($f_oop, $f_iip)) - tgradsym = gensym(:tgrad) - if tgrad - tgrad_oop, tgrad_iip = generate_tgrad(sys, dvs, ps; - simplify = simplify, - expression = Val{true}, kwargs...) - _tgrad = :($tgradsym = $(GeneratedFunctionWrapper{(2, 3, is_split(sys))})( - $tgrad_oop, $tgrad_iip)) - else - _tgrad = :($tgradsym = nothing) - end - - jacsym = gensym(:jac) - if jac - jac_oop, jac_iip = generate_jacobian(sys, dvs, ps; - sparse = sparse, simplify = simplify, - expression = Val{true}, kwargs...) - _jac = :($jacsym = $(GeneratedFunctionWrapper{(2, 3, is_split(sys))})( - $jac_oop, $jac_iip)) - else - _jac = :($jacsym = nothing) - end - - Msym = gensym(:M) - M = calculate_massmatrix(sys) - if sparse && !(u0 === nothing || M === I) - _M = :($Msym = $(SparseArrays.sparse(M))) - elseif u0 === nothing || M === I - _M = :($Msym = $M) - else - _M = :($Msym = $(ArrayInterface.restructure(u0 .* u0', M))) - end - - jp_expr = sparse ? :($similar($(get_jac(sys)[]), Float64)) : :nothing - ex = quote - let $_f, $_tgrad, $_jac, $_M - ODEFunction{$iip, $specialize}($fsym, - sys = $sys, - jac = $jacsym, - tgrad = $tgradsym, - mass_matrix = $Msym, - jac_prototype = $jp_expr, - sparsity = $(sparsity ? jacobian_sparsity(sys) : nothing), - observed = $observedfun_exp) - end - end - !linenumbers ? Base.remove_linenums!(ex) : ex -end - -function ODEFunctionExpr(sys::AbstractODESystem, args...; kwargs...) - ODEFunctionExpr{true}(sys, args...; kwargs...) -end - -function ODEFunctionExpr{true}(sys::AbstractODESystem, args...; kwargs...) - return ODEFunctionExpr{true, SciMLBase.AutoSpecialize}(sys, args...; kwargs...) -end - -function ODEFunctionExpr{false}(sys::AbstractODESystem, args...; kwargs...) - return ODEFunctionExpr{false, SciMLBase.FullSpecialize}(sys, args...; kwargs...) -end - -""" -```julia -DAEFunctionExpr{iip}(sys::AbstractODESystem, dvs = unknowns(sys), - ps = parameters(sys); - version = nothing, tgrad = false, - jac = false, - sparse = false, - kwargs...) where {iip} -``` - -Create a Julia expression for an `ODEFunction` from the [`ODESystem`](@ref). -The arguments `dvs` and `ps` are used to set the order of the dependent -variable and parameter vectors, respectively. -""" -struct DAEFunctionExpr{iip} end - -function DAEFunctionExpr{iip}(sys::AbstractODESystem, dvs = unknowns(sys), - ps = parameters(sys), u0 = nothing; - version = nothing, tgrad = false, - jac = false, p = nothing, - linenumbers = false, - sparse = false, simplify = false, - kwargs...) where {iip} - if !iscomplete(sys) - error("A completed system is required. Call `complete` or `structural_simplify` on the system before creating an `DAEFunctionExpr`") - end - f_oop, f_iip = generate_function(sys, dvs, ps; expression = Val{true}, - implicit_dae = true, kwargs...) - fsym = gensym(:f) - _f = :($fsym = $(GeneratedFunctionWrapper{(3, 4, is_split(sys))})($f_oop, $f_iip)) - ex = quote - $_f - ODEFunction{$iip}($fsym) - end - !linenumbers ? Base.remove_linenums!(ex) : ex -end - -function DAEFunctionExpr(sys::AbstractODESystem, args...; kwargs...) - DAEFunctionExpr{true}(sys, args...; kwargs...) -end - -struct SymbolicTstops{F} - fn::F -end - -function (st::SymbolicTstops)(p, tspan) - unique!(sort!(reduce(vcat, st.fn(p, tspan...)))) -end - -function SymbolicTstops( - sys::AbstractSystem; eval_expression = false, eval_module = @__MODULE__) - tstops = symbolic_tstops(sys) - isempty(tstops) && return nothing - t0 = gensym(:t0) - t1 = gensym(:t1) - tstops = map(tstops) do val - if is_array_of_symbolics(val) || val isa AbstractArray - collect(val) - else - term(:, t0, unwrap(val), t1; type = AbstractArray{Real}) - end - end - rps = reorder_parameters(sys) - tstops, _ = build_function_wrapper(sys, tstops, - rps..., - t0, - t1; - expression = Val{true}, - p_start = 1, p_end = length(rps), add_observed = false, force_SA = true) - tstops = eval_or_rgf(tstops; eval_expression, eval_module) - tstops = GeneratedFunctionWrapper{(1, 3, is_split(sys))}(tstops, nothing) - return SymbolicTstops(tstops) -end - -""" -```julia -DiffEqBase.ODEProblem{iip}(sys::AbstractODESystem, u0map, tspan, - parammap = DiffEqBase.NullParameters(); - allow_cost = false, - version = nothing, tgrad = false, - jac = false, - checkbounds = false, sparse = false, - simplify = false, - linenumbers = true, parallel = SerialForm(), - kwargs...) where {iip} -``` - -Generates an ODEProblem from an ODESystem and allows for automatically -symbolically calculating numerical enhancements. -""" -function DiffEqBase.ODEProblem(sys::AbstractODESystem, args...; kwargs...) - ODEProblem{true}(sys, args...; kwargs...) -end - -function DiffEqBase.ODEProblem(sys::AbstractODESystem, - u0map::StaticArray, - args...; - kwargs...) - ODEProblem{false, SciMLBase.FullSpecialize}(sys, u0map, args...; kwargs...) -end - -function DiffEqBase.ODEProblem{true}(sys::AbstractODESystem, args...; kwargs...) - ODEProblem{true, SciMLBase.AutoSpecialize}(sys, args...; kwargs...) -end - -function DiffEqBase.ODEProblem{false}(sys::AbstractODESystem, args...; kwargs...) - ODEProblem{false, SciMLBase.FullSpecialize}(sys, args...; kwargs...) -end - -function DiffEqBase.ODEProblem{iip, specialize}(sys::AbstractODESystem, u0map = [], - tspan = get_tspan(sys), - parammap = DiffEqBase.NullParameters(); - allow_cost = false, - callback = nothing, - check_length = true, - warn_initialize_determined = true, - eval_expression = false, - eval_module = @__MODULE__, - kwargs...) where {iip, specialize} - if !iscomplete(sys) - error("A completed system is required. Call `complete` or `structural_simplify` on the system before creating an `ODEProblem`") - end - - if !isnothing(get_constraintsystem(sys)) - error("An ODESystem with constraints cannot be used to construct a regular ODEProblem. - Consider a BVProblem instead.") - end - - if !isempty(get_costs(sys)) && !allow_cost - error("ODEProblem will not optimize solutions of ODESystems that have associated cost functions. - Solvers for optimal control problems are forthcoming. In order to bypass this error (e.g. - to check the cost of a regular solution), pass `allow_cost` = true into the constructor.") - end - - f, u0, p = process_SciMLProblem(ODEFunction{iip, specialize}, sys, u0map, parammap; - t = tspan !== nothing ? tspan[1] : tspan, - check_length, warn_initialize_determined, eval_expression, eval_module, kwargs...) - cbs = process_events(sys; callback, eval_expression, eval_module, kwargs...) - - kwargs = filter_kwargs(kwargs) - pt = something(get_metadata(sys), StandardODEProblem()) - - kwargs1 = (;) - if cbs !== nothing - kwargs1 = merge(kwargs1, (callback = cbs,)) - end - - tstops = SymbolicTstops(sys; eval_expression, eval_module) - if tstops !== nothing - kwargs1 = merge(kwargs1, (; tstops)) - end - - # Call `remake` so it runs initialization if it is trivial - return remake(ODEProblem{iip}(f, u0, tspan, p, pt; kwargs1..., kwargs...)) -end -get_callback(prob::ODEProblem) = prob.kwargs[:callback] - -""" -```julia -SciMLBase.BVProblem{iip}(sys::AbstractODESystem, u0map, tspan, - parammap = DiffEqBase.NullParameters(); - constraints = nothing, guesses = nothing, - version = nothing, tgrad = false, - jac = true, sparse = true, - simplify = false, - kwargs...) where {iip} -``` - -Create a boundary value problem from the [`ODESystem`](@ref). - -`u0map` is used to specify fixed initial values for the states. Every variable -must have either an initial guess supplied using `guesses` or a fixed initial -value specified using `u0map`. - -Boundary value conditions are supplied to ODESystems -in the form of a ConstraintsSystem. These equations -should specify values that state variables should -take at specific points, as in `x(0.5) ~ 1`). More general constraints that -should hold over the entire solution, such as `x(t)^2 + y(t)^2`, should be -specified as one of the equations used to build the `ODESystem`. - -If an ODESystem without `constraints` is specified, it will be treated as an initial value problem. - -```julia - @parameters g t_c = 0.5 - @variables x(..) y(t) λ(t) - eqs = [D(D(x(t))) ~ λ * x(t) - D(D(y)) ~ λ * y - g - x(t)^2 + y^2 ~ 1] - cstr = [x(0.5) ~ 1] - @mtkbuild pend = ODESystem(eqs, t; constraints = cstrs) - - tspan = (0.0, 1.5) - u0map = [x(t) => 0.6, y => 0.8] - parammap = [g => 1] - guesses = [λ => 1] - - bvp = SciMLBase.BVProblem{true, SciMLBase.AutoSpecialize}(pend, u0map, tspan, parammap; guesses, check_length = false) -``` - -If the `ODESystem` has algebraic equations, like `x(t)^2 + y(t)^2`, the resulting -`BVProblem` must be solved using BVDAE solvers, such as Ascher. -""" -function SciMLBase.BVProblem(sys::AbstractODESystem, args...; kwargs...) - BVProblem{true}(sys, args...; kwargs...) -end - -function SciMLBase.BVProblem(sys::AbstractODESystem, - u0map::StaticArray, - args...; - kwargs...) - BVProblem{false, SciMLBase.FullSpecialize}(sys, u0map, args...; kwargs...) -end - -function SciMLBase.BVProblem{true}(sys::AbstractODESystem, args...; kwargs...) - BVProblem{true, SciMLBase.AutoSpecialize}(sys, args...; kwargs...) -end - -function SciMLBase.BVProblem{false}(sys::AbstractODESystem, args...; kwargs...) - BVProblem{false, SciMLBase.FullSpecialize}(sys, args...; kwargs...) -end - -function SciMLBase.BVProblem{iip, specialize}(sys::AbstractODESystem, u0map = [], - tspan = get_tspan(sys), - parammap = DiffEqBase.NullParameters(); - guesses = Dict(), - allow_cost = false, - version = nothing, tgrad = false, - callback = nothing, - check_length = true, - warn_initialize_determined = true, - eval_expression = false, - eval_module = @__MODULE__, - cse = true, - kwargs...) where {iip, specialize} - if !iscomplete(sys) - error("A completed system is required. Call `complete` or `structural_simplify` on the system before creating an `BVProblem`") - end - !isnothing(callback) && error("BVP solvers do not support callbacks.") - - if !isempty(get_costs(sys)) && !allow_cost - error("BVProblem will not optimize solutions of ODESystems that have associated cost functions. - Solvers for optimal control problems are forthcoming. In order to bypass this error (e.g. - to check the cost of a regular solution), pass `allow_cost` = true into the constructor.") - end - - has_alg_eqs(sys) && - error("The BVProblem constructor currently does not support ODESystems with algebraic equations.") # Remove this when the BVDAE solvers get updated, the codegen should work when it does. - - sts = unknowns(sys) - ps = parameters(sys) - constraintsys = get_constraintsystem(sys) - - if !isnothing(constraintsys) - (length(constraints(constraintsys)) + length(u0map) > length(sts)) && - @warn "The BVProblem is overdetermined. The total number of conditions (# constraints + # fixed initial values given by u0map) exceeds the total number of states. The BVP solvers will default to doing a nonlinear least-squares optimization." - end - - # ODESystems without algebraic equations should use both fixed values + guesses - # for initialization. - _u0map = has_alg_eqs(sys) ? u0map : merge(Dict(u0map), Dict(guesses)) - f, u0, p = process_SciMLProblem(ODEFunction{iip, specialize}, sys, _u0map, parammap; - t = tspan !== nothing ? tspan[1] : tspan, guesses, - check_length, warn_initialize_determined, eval_expression, eval_module, cse, kwargs...) - - stidxmap = Dict([v => i for (i, v) in enumerate(sts)]) - u0_idxs = has_alg_eqs(sys) ? collect(1:length(sts)) : [stidxmap[k] for (k, v) in u0map] - - fns = generate_function_bc(sys, u0, u0_idxs, tspan; cse) - bc_oop, bc_iip = eval_or_rgf.(fns; eval_expression, eval_module) - bc(sol, p, t) = bc_oop(sol, p, t) - bc(resid, u, p, t) = bc_iip(resid, u, p, t) - - return BVProblem{iip}(f, bc, u0, tspan, p; kwargs...) -end - -get_callback(prob::BVProblem) = error("BVP solvers do not support callbacks.") - -""" - generate_function_bc(sys::ODESystem, u0, u0_idxs, tspan) - - Given an ODESystem with constraints, generate the boundary condition function to pass to boundary value problem solvers. - Expression uses the constraints and the provided initial conditions. -""" -function generate_function_bc(sys::ODESystem, u0, u0_idxs, tspan; kwargs...) - iv = get_iv(sys) - sts = unknowns(sys) - ps = parameters(sys) - np = length(ps) - ns = length(sts) - stidxmap = Dict([v => i for (i, v) in enumerate(sts)]) - pidxmap = Dict([v => i for (i, v) in enumerate(ps)]) - - @variables sol(..)[1:ns] - - conssys = get_constraintsystem(sys) - cons = Any[] - if !isnothing(conssys) - cons = [con.lhs - con.rhs for con in constraints(conssys)] - - for st in get_unknowns(conssys) - x = operation(st) - t = only(arguments(st)) - idx = stidxmap[x(iv)] - - cons = map(c -> Symbolics.substitute(c, Dict(x(t) => sol(t)[idx])), cons) - end - end - - init_conds = Any[] - for i in u0_idxs - expr = sol(tspan[1])[i] - u0[i] - push!(init_conds, expr) - end - - exprs = vcat(init_conds, cons) - _p = reorder_parameters(sys, ps) - - build_function_wrapper(sys, exprs, sol, _p..., iv; output_type = Array, kwargs...) -end - -""" -```julia -DiffEqBase.DAEProblem{iip}(sys::AbstractODESystem, du0map, u0map, tspan, - parammap = DiffEqBase.NullParameters(); - version = nothing, tgrad = false, - jac = false, - checkbounds = false, sparse = false, - simplify = false, - linenumbers = true, parallel = SerialForm(), - kwargs...) where {iip} -``` - -Generates a DAEProblem from an ODESystem and allows for automatically -symbolically calculating numerical enhancements. - -Note: Solvers for DAEProblems like DFBDF, DImplicitEuler, DABDF2 are -generally slower than the ones for ODEProblems. We recommend trying -ODEProblem and its solvers for your problem first. -""" -function DiffEqBase.DAEProblem(sys::AbstractODESystem, args...; kwargs...) - DAEProblem{true}(sys, args...; kwargs...) -end - -function DiffEqBase.DAEProblem{iip}(sys::AbstractODESystem, du0map, u0map, tspan, - parammap = DiffEqBase.NullParameters(); - allow_cost = false, - warn_initialize_determined = true, - check_length = true, eval_expression = false, eval_module = @__MODULE__, kwargs...) where {iip} - if !iscomplete(sys) - error("A completed system is required. Call `complete` or `structural_simplify` on the system before creating a `DAEProblem`.") - end - - if !isempty(get_costs(sys)) && !allow_cost - error("DAEProblem will not optimize solutions of ODESystems that have associated cost functions. - Solvers for optimal control problems are forthcoming. In order to bypass this error (e.g. - to check the cost of a regular solution), pass `allow_cost` = true into the constructor.") - end - - f, du0, u0, p = process_SciMLProblem(DAEFunction{iip}, sys, u0map, parammap; - implicit_dae = true, du0map = du0map, check_length, - t = tspan !== nothing ? tspan[1] : tspan, - warn_initialize_determined, kwargs...) - diffvars = collect_differential_variables(sys) - sts = unknowns(sys) - differential_vars = map(Base.Fix2(in, diffvars), sts) - kwargs = filter_kwargs(kwargs) - - kwargs1 = (;) - - tstops = SymbolicTstops(sys; eval_expression, eval_module) - if tstops !== nothing - kwargs1 = merge(kwargs1, (; tstops)) - end - - # Call `remake` so it runs initialization if it is trivial - return remake(DAEProblem{iip}( - f, du0, u0, tspan, p; differential_vars = differential_vars, - kwargs..., kwargs1...)) -end - -function generate_history(sys::AbstractODESystem, u0; expression = Val{false}, kwargs...) - p = reorder_parameters(sys) - build_function_wrapper( - sys, u0, p..., get_iv(sys); expression, p_start = 1, p_end = length(p), - similarto = typeof(u0), wrap_delays = false, kwargs...) -end - -function DiffEqBase.DDEProblem(sys::AbstractODESystem, args...; kwargs...) - DDEProblem{true}(sys, args...; kwargs...) -end -function DiffEqBase.DDEProblem{iip}(sys::AbstractODESystem, u0map = [], - tspan = get_tspan(sys), - parammap = DiffEqBase.NullParameters(); - callback = nothing, - check_length = true, - eval_expression = false, - eval_module = @__MODULE__, - u0_constructor = identity, - cse = true, - kwargs...) where {iip} - if !iscomplete(sys) - error("A completed system is required. Call `complete` or `structural_simplify` on the system before creating a `DDEProblem`") - end - f, u0, p = process_SciMLProblem(DDEFunction{iip}, sys, u0map, parammap; - t = tspan !== nothing ? tspan[1] : tspan, - symbolic_u0 = true, u0_constructor, cse, - check_length, eval_expression, eval_module, kwargs...) - h_gen = generate_history(sys, u0; expression = Val{true}, cse) - h_oop, h_iip = eval_or_rgf.(h_gen; eval_expression, eval_module) - h = h_oop - u0 = float.(h(p, tspan[1])) - if u0 !== nothing - u0 = u0_constructor(u0) - end - - cbs = process_events(sys; callback, eval_expression, eval_module, kwargs...) - kwargs = filter_kwargs(kwargs) - - kwargs1 = (;) - if cbs !== nothing - kwargs1 = merge(kwargs1, (callback = cbs,)) - end - # Call `remake` so it runs initialization if it is trivial - return remake(DDEProblem{iip}(f, u0, h, tspan, p; kwargs1..., kwargs...)) -end - -function DiffEqBase.SDDEProblem(sys::AbstractODESystem, args...; kwargs...) - SDDEProblem{true}(sys, args...; kwargs...) -end -function DiffEqBase.SDDEProblem{iip}(sys::AbstractODESystem, u0map = [], - tspan = get_tspan(sys), - parammap = DiffEqBase.NullParameters(); - callback = nothing, - check_length = true, - sparsenoise = nothing, - eval_expression = false, - eval_module = @__MODULE__, - u0_constructor = identity, - cse = true, - kwargs...) where {iip} - if !iscomplete(sys) - error("A completed system is required. Call `complete` or `structural_simplify` on the system before creating a `SDDEProblem`") - end - f, u0, p = process_SciMLProblem(SDDEFunction{iip}, sys, u0map, parammap; - t = tspan !== nothing ? tspan[1] : tspan, - symbolic_u0 = true, eval_expression, eval_module, u0_constructor, - check_length, cse, kwargs...) - h_gen = generate_history(sys, u0; expression = Val{true}, cse) - h_oop, h_iip = eval_or_rgf.(h_gen; eval_expression, eval_module) - h = h_oop - u0 = h(p, tspan[1]) - if u0 !== nothing - u0 = u0_constructor(u0) - end - - cbs = process_events(sys; callback, eval_expression, eval_module, kwargs...) - kwargs = filter_kwargs(kwargs) - - kwargs1 = (;) - if cbs !== nothing - kwargs1 = merge(kwargs1, (callback = cbs,)) - end - - noiseeqs = get_noiseeqs(sys) - sparsenoise === nothing && (sparsenoise = get(kwargs, :sparse, false)) - if noiseeqs isa AbstractVector - noise_rate_prototype = nothing - elseif sparsenoise - I, J, V = findnz(SparseArrays.sparse(noiseeqs)) - noise_rate_prototype = SparseArrays.sparse(I, J, zero(eltype(u0))) - else - noise_rate_prototype = zeros(eltype(u0), size(noiseeqs)) - end - # Call `remake` so it runs initialization if it is trivial - return remake(SDDEProblem{iip}(f, f.g, u0, h, tspan, p; - noise_rate_prototype = - noise_rate_prototype, kwargs1..., kwargs...)) -end - -""" -```julia -ODEProblemExpr{iip}(sys::AbstractODESystem, u0map, tspan, - parammap = DiffEqBase.NullParameters(); - version = nothing, tgrad = false, - jac = false, - checkbounds = false, sparse = false, - linenumbers = true, parallel = SerialForm(), - skipzeros = true, fillzeros = true, - simplify = false, - kwargs...) where {iip} -``` - -Generates a Julia expression for constructing an ODEProblem from an -ODESystem and allows for automatically symbolically calculating -numerical enhancements. -""" -struct ODEProblemExpr{iip} end - -function ODEProblemExpr{iip}(sys::AbstractODESystem, u0map, tspan, - parammap = DiffEqBase.NullParameters(); check_length = true, - kwargs...) where {iip} - if !iscomplete(sys) - error("A completed system is required. Call `complete` or `structural_simplify` on the system before creating a `ODEProblemExpr`") - end - f, u0, p = process_SciMLProblem( - ODEFunctionExpr{iip}, sys, u0map, parammap; check_length, - t = tspan !== nothing ? tspan[1] : tspan, - kwargs...) - linenumbers = get(kwargs, :linenumbers, true) - kwargs = filter_kwargs(kwargs) - kwarg_params = gen_quoted_kwargs(kwargs) - odep = Expr(:call, :ODEProblem, kwarg_params, :f, :u0, :tspan, :p) - ex = quote - f = $f - u0 = $u0 - tspan = $tspan - p = $p - $odep - end - !linenumbers ? Base.remove_linenums!(ex) : ex -end - -function ODEProblemExpr(sys::AbstractODESystem, args...; kwargs...) - ODEProblemExpr{true}(sys, args...; kwargs...) -end - -""" -```julia -DAEProblemExpr{iip}(sys::AbstractODESystem, u0map, tspan, - parammap = DiffEqBase.NullParameters(); - version = nothing, tgrad = false, - jac = false, - checkbounds = false, sparse = false, - linenumbers = true, parallel = SerialForm(), - skipzeros = true, fillzeros = true, - simplify = false, - kwargs...) where {iip} -``` - -Generates a Julia expression for constructing a DAEProblem from an -ODESystem and allows for automatically symbolically calculating -numerical enhancements. -""" -struct DAEProblemExpr{iip} end - -function DAEProblemExpr{iip}(sys::AbstractODESystem, du0map, u0map, tspan, - parammap = DiffEqBase.NullParameters(); check_length = true, - kwargs...) where {iip} - if !iscomplete(sys) - error("A completed system is required. Call `complete` or `structural_simplify` on the system before creating a `DAEProblemExpr`") - end - f, du0, u0, p = process_SciMLProblem(DAEFunctionExpr{iip}, sys, u0map, parammap; - t = tspan !== nothing ? tspan[1] : tspan, - implicit_dae = true, du0map = du0map, check_length, - kwargs...) - linenumbers = get(kwargs, :linenumbers, true) - diffvars = collect_differential_variables(sys) - sts = unknowns(sys) - differential_vars = map(Base.Fix2(in, diffvars), sts) - kwargs = filter_kwargs(kwargs) - kwarg_params = gen_quoted_kwargs(kwargs) - push!(kwarg_params.args, Expr(:kw, :differential_vars, :differential_vars)) - prob = Expr(:call, :(DAEProblem{$iip}), kwarg_params, :f, :du0, :u0, :tspan, :p) - ex = quote - f = $f - u0 = $u0 - du0 = $du0 - tspan = $tspan - p = $p - differential_vars = $differential_vars - $prob - end - !linenumbers ? Base.remove_linenums!(ex) : ex -end - -function DAEProblemExpr(sys::AbstractODESystem, args...; kwargs...) - DAEProblemExpr{true}(sys, args...; kwargs...) -end - -""" -```julia -SciMLBase.SteadyStateProblem(sys::AbstractODESystem, u0map, - parammap = DiffEqBase.NullParameters(); - version = nothing, tgrad = false, - jac = false, - checkbounds = false, sparse = false, - linenumbers = true, parallel = SerialForm(), - kwargs...) where {iip} -``` - -Generates an SteadyStateProblem from an ODESystem and allows for automatically -symbolically calculating numerical enhancements. -""" -function SciMLBase.SteadyStateProblem(sys::AbstractODESystem, args...; kwargs...) - SteadyStateProblem{true}(sys, args...; kwargs...) -end - -function DiffEqBase.SteadyStateProblem{iip}(sys::AbstractODESystem, u0map, - parammap = SciMLBase.NullParameters(); - check_length = true, kwargs...) where {iip} - if !iscomplete(sys) - error("A completed system is required. Call `complete` or `structural_simplify` on the system before creating a `SteadyStateProblem`") - end - f, u0, p = process_SciMLProblem(ODEFunction{iip}, sys, u0map, parammap; - steady_state = true, - check_length, force_initialization_time_independent = true, kwargs...) - kwargs = filter_kwargs(kwargs) - SteadyStateProblem{iip}(f, u0, p; kwargs...) -end - -""" -```julia -SciMLBase.SteadyStateProblemExpr(sys::AbstractODESystem, u0map, - parammap = DiffEqBase.NullParameters(); - version = nothing, tgrad = false, - jac = false, - checkbounds = false, sparse = false, - skipzeros = true, fillzeros = true, - linenumbers = true, parallel = SerialForm(), - kwargs...) where {iip} -``` - -Generates a Julia expression for building a SteadyStateProblem from -an ODESystem and allows for automatically symbolically calculating -numerical enhancements. -""" -struct SteadyStateProblemExpr{iip} end - -function SteadyStateProblemExpr{iip}(sys::AbstractODESystem, u0map, - parammap = SciMLBase.NullParameters(); - check_length = true, - kwargs...) where {iip} - if !iscomplete(sys) - error("A completed system is required. Call `complete` or `structural_simplify` on the system before creating a `SteadyStateProblemExpr`") - end - f, u0, p = process_SciMLProblem(ODEFunctionExpr{iip}, sys, u0map, parammap; - steady_state = true, - check_length, kwargs...) - linenumbers = get(kwargs, :linenumbers, true) - kwargs = filter_kwargs(kwargs) - kwarg_params = gen_quoted_kwargs(kwargs) - prob = Expr(:call, :SteadyStateProblem, kwarg_params, :f, :u0, :p) - ex = quote - f = $f - u0 = $u0 - p = $p - $prob - end - !linenumbers ? Base.remove_linenums!(ex) : ex -end - -function SteadyStateProblemExpr(sys::AbstractODESystem, args...; kwargs...) - SteadyStateProblemExpr{true}(sys, args...; kwargs...) -end - -function _match_eqs(eqs1, eqs2) - eqpairs = Pair[] - for (i, eq) in enumerate(eqs1) - for (j, eq2) in enumerate(eqs2) - if isequal(eq, eq2) - push!(eqpairs, i => j) - break - end - end - end - eqpairs -end - -function isisomorphic(sys1::AbstractODESystem, sys2::AbstractODESystem) - sys1 = flatten(sys1) - sys2 = flatten(sys2) - - iv2 = only(independent_variables(sys2)) - sys1 = convert_system(ODESystem, sys1, iv2) - s1, s2 = unknowns(sys1), unknowns(sys2) - p1, p2 = parameters(sys1), parameters(sys2) - - (length(s1) != length(s2)) || (length(p1) != length(p2)) && return false - - eqs1 = equations(sys1) - eqs2 = equations(sys2) - - pps = permutations(p2) - psts = permutations(s2) - orig = [p1; s1] - perms = ([x; y] for x in pps for y in psts) - - for perm in perms - rules = Dict(orig .=> perm) - neweqs1 = substitute(eqs1, rules) - eqpairs = _match_eqs(neweqs1, eqs2) - if length(eqpairs) == length(eqs1) - return true - end - end - return false -end - -function flatten_equations(eqs) - mapreduce(vcat, eqs; init = Equation[]) do eq - islhsarr = eq.lhs isa AbstractArray || Symbolics.isarraysymbolic(eq.lhs) - isrhsarr = eq.rhs isa AbstractArray || Symbolics.isarraysymbolic(eq.rhs) - if islhsarr || isrhsarr - islhsarr && isrhsarr || - error("LHS ($(eq.lhs)) and RHS ($(eq.rhs)) must either both be array expressions or both scalar") - size(eq.lhs) == size(eq.rhs) || - error("Size of LHS ($(eq.lhs)) and RHS ($(eq.rhs)) must match: got $(size(eq.lhs)) and $(size(eq.rhs))") - return vec(collect(eq.lhs) .~ collect(eq.rhs)) - else - eq - end - end -end From 919a64137c450c3261a25b49c650bd569b4e3203 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Mon, 21 Apr 2025 12:57:44 +0530 Subject: [PATCH 075/185] refactor: remove `schedule(sys)` --- src/systems/abstractsystem.jl | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/src/systems/abstractsystem.jl b/src/systems/abstractsystem.jl index e43387ff82..d47bef8f77 100644 --- a/src/systems/abstractsystem.jl +++ b/src/systems/abstractsystem.jl @@ -604,17 +604,6 @@ end """ $(TYPEDSIGNATURES) -Mark a system as scheduled. It is only intended in compiler internals. A system -is scheduled after tearing based simplifications where equations are converted -into assignments. -""" -function schedule(sys::AbstractSystem) - has_schedule(sys) ? sys : (@set! sys.isscheduled = true) -end - -""" -$(TYPEDSIGNATURES) - If a system is scheduled, then changing its equations, variables, and parameters is no longer legal. """ From 0de53f183f1b963eb0bc90abf894c0f97994fab6 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Mon, 21 Apr 2025 12:58:17 +0530 Subject: [PATCH 076/185] feat: set system scheduling information in `structural_simplify` --- src/structural_transformation/symbolics_tearing.jl | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/structural_transformation/symbolics_tearing.jl b/src/structural_transformation/symbolics_tearing.jl index 548c7da519..99eab00685 100644 --- a/src/structural_transformation/symbolics_tearing.jl +++ b/src/structural_transformation/symbolics_tearing.jl @@ -756,11 +756,13 @@ function update_simplified_system!( @set! sys.substitutions = Substitutions(subeqs, deps) # Only makes sense for time-dependent - # TODO: generalize to SDE - if sys isa ODESystem + if ModelingToolkit.has_schedule(sys) @set! sys.schedule = Schedule(var_eq_matching, dummy_sub) end - sys = schedule(sys) + if ModelingToolkit.has_isscheduled(sys) + @set! sys.isscheduled = true + end + return sys end """ From 262bde191dd994f2be7799a398bc252832e40394 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Mon, 21 Apr 2025 12:59:09 +0530 Subject: [PATCH 077/185] refactor: remove `AbstractODESystem` --- src/ModelingToolkit.jl | 1 - 1 file changed, 1 deletion(-) diff --git a/src/ModelingToolkit.jl b/src/ModelingToolkit.jl index d30ae64efb..deb41f0ec7 100644 --- a/src/ModelingToolkit.jl +++ b/src/ModelingToolkit.jl @@ -127,7 +127,6 @@ TODO abstract type AbstractSystem end abstract type AbstractTimeDependentSystem <: AbstractSystem end abstract type AbstractTimeIndependentSystem <: AbstractSystem end -abstract type AbstractODESystem <: AbstractTimeDependentSystem end abstract type AbstractMultivariateSystem <: AbstractSystem end abstract type AbstractOptimizationSystem <: AbstractTimeIndependentSystem end abstract type AbstractDiscreteSystem <: AbstractTimeDependentSystem end From cd1ffc8d0db8fdac64f1e30e3feee6d91353244d Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Mon, 21 Apr 2025 13:01:03 +0530 Subject: [PATCH 078/185] refactor: remove references to `ODESystem` in source code --- src/ModelingToolkit.jl | 2 +- src/inputoutput.jl | 12 +++---- src/linearization.jl | 10 +++--- .../StructuralTransformations.jl | 2 +- src/structural_transformation/pantelides.jl | 6 ++-- .../symbolics_tearing.jl | 4 +-- src/systems/abstractsystem.jl | 36 +++++++++---------- src/systems/analysis_points.jl | 6 ++-- src/systems/diffeqs/basic_transformations.jl | 16 ++++----- src/systems/diffeqs/first_order_transform.jl | 6 ++-- src/systems/diffeqs/modelingtoolkitize.jl | 4 +-- src/systems/if_lifting.jl | 5 ++- src/systems/systems.jl | 6 +--- src/systems/systemstructure.jl | 2 +- src/utils.jl | 5 +-- 15 files changed, 61 insertions(+), 61 deletions(-) diff --git a/src/ModelingToolkit.jl b/src/ModelingToolkit.jl index deb41f0ec7..33075ff63b 100644 --- a/src/ModelingToolkit.jl +++ b/src/ModelingToolkit.jl @@ -213,7 +213,7 @@ const D = Differential(t) PrecompileTools.@compile_workload begin using ModelingToolkit @variables x(ModelingToolkit.t_nounits) - @named sys = ODESystem([ModelingToolkit.D_nounits(x) ~ -x], ModelingToolkit.t_nounits) + @named sys = System([ModelingToolkit.D_nounits(x) ~ -x], ModelingToolkit.t_nounits) prob = ODEProblem(structural_simplify(sys), [x => 30.0], (0, 100), [], jac = true) @mtkmodel __testmod__ begin @constants begin diff --git a/src/inputoutput.jl b/src/inputoutput.jl index 28b60b13dc..2a2a47fa63 100644 --- a/src/inputoutput.jl +++ b/src/inputoutput.jl @@ -161,7 +161,7 @@ has_var(ex, x) = x ∈ Set(get_variables(ex)) """ (f_oop, f_ip), x_sym, p_sym, io_sys = generate_control_function( - sys::AbstractODESystem, + sys::System, inputs = unbound_inputs(sys), disturbance_inputs = nothing; implicit_dae = false, @@ -193,7 +193,7 @@ t = 0 f[1](x, inputs, p, t) ``` """ -function generate_control_function(sys::AbstractODESystem, inputs = unbound_inputs(sys), +function generate_control_function(sys::AbstractSystem, inputs = unbound_inputs(sys), disturbance_inputs = disturbances(sys); disturbance_argument = false, implicit_dae = false, @@ -344,7 +344,7 @@ The structure represents a model of a disturbance, along with the input variable # Fields: - `input`: The variable affected by the disturbance. - - `model::M`: A model of the disturbance. This is typically an `ODESystem`, but type that implements [`ModelingToolkit.get_disturbance_system`](@ref)`(dist::DisturbanceModel) -> ::ODESystem` is supported. + - `model::M`: A model of the disturbance. This is typically a `System`, but type that implements [`ModelingToolkit.get_disturbance_system`](@ref)`(dist::DisturbanceModel) -> ::System` is supported. """ struct DisturbanceModel{M} input::Any @@ -354,7 +354,7 @@ end DisturbanceModel(input, model; name) = DisturbanceModel(input, model, name) # Point of overloading for libraries, e.g., to be able to support disturbance models from ControlSystemsBase -function get_disturbance_system(dist::DisturbanceModel{<:ODESystem}) +function get_disturbance_system(dist::DisturbanceModel{System}) dist.model end @@ -395,7 +395,7 @@ c = 10 # Damping coefficient eqs = [connect(torque.flange, inertia1.flange_a) connect(inertia1.flange_b, spring.flange_a, damper.flange_a) connect(inertia2.flange_a, spring.flange_b, damper.flange_b)] -model = ODESystem(eqs, t; systems = [torque, inertia1, inertia2, spring, damper], +model = System(eqs, t; systems = [torque, inertia1, inertia2, spring, damper], name = :model) model = complete(model) model_outputs = [model.inertia1.w, model.inertia2.w, model.inertia1.phi, model.inertia2.phi] @@ -427,7 +427,7 @@ function add_input_disturbance(sys, dist::DisturbanceModel, inputs = nothing; kw eqs = [dsys.input.u[1] ~ d dist.input ~ u + dsys.output.u[1]] - augmented_sys = ODESystem(eqs, t, systems = [dsys], name = gensym(:outer)) + augmented_sys = System(eqs, t, systems = [dsys], name = gensym(:outer)) augmented_sys = extend(augmented_sys, sys) (f_oop, f_ip), dvs, p, io_sys = generate_control_function(augmented_sys, all_inputs, diff --git a/src/linearization.jl b/src/linearization.jl index 77f4422b63..6669ba558b 100644 --- a/src/linearization.jl +++ b/src/linearization.jl @@ -19,7 +19,7 @@ The `simplified_sys` has undergone [`structural_simplify`](@ref) and had any occ # Arguments: - - `sys`: An [`ODESystem`](@ref). This function will automatically apply simplification passes on `sys` and return the resulting `simplified_sys`. + - `sys`: A [`System`](@ref) of ODEs. This function will automatically apply simplification passes on `sys` and return the resulting `simplified_sys`. - `inputs`: A vector of variables that indicate the inputs of the linearized input-output model. - `outputs`: A vector of variables that indicate the outputs of the linearized input-output model. - `simplify`: Apply simplification in tearing. @@ -640,7 +640,7 @@ function plant(; name) @variables u(t)=0 y(t)=0 eqs = [D(x) ~ -x + u y ~ x] - ODESystem(eqs, t; name = name) + System(eqs, t; name = name) end function ref_filt(; name) @@ -648,7 +648,7 @@ function ref_filt(; name) @variables u(t)=0 [input = true] eqs = [D(x) ~ -2 * x + u y ~ x] - ODESystem(eqs, t, name = name) + System(eqs, t, name = name) end function controller(kp; name) @@ -657,7 +657,7 @@ function controller(kp; name) eqs = [ u ~ kp * (r - y), ] - ODESystem(eqs, t; name = name) + System(eqs, t; name = name) end @named f = ref_filt() @@ -668,7 +668,7 @@ connections = [f.y ~ c.r # filtered reference to controller reference c.u ~ p.u # controller output to plant input p.y ~ c.y] -@named cl = ODESystem(connections, t, systems = [f, c, p]) +@named cl = System(connections, t, systems = [f, c, p]) lsys0, ssys = linearize(cl, [f.u], [p.x]) desired_order = [f.x, p.x] diff --git a/src/structural_transformation/StructuralTransformations.jl b/src/structural_transformation/StructuralTransformations.jl index 4adc817ef8..16d3a75464 100644 --- a/src/structural_transformation/StructuralTransformations.jl +++ b/src/structural_transformation/StructuralTransformations.jl @@ -11,7 +11,7 @@ using SymbolicUtils.Rewriters using SymbolicUtils: maketerm, iscall using ModelingToolkit -using ModelingToolkit: ODESystem, AbstractSystem, var_from_nested_derivative, Differential, +using ModelingToolkit: System, AbstractSystem, var_from_nested_derivative, Differential, unknowns, equations, vars, Symbolic, diff2term_with_unit, shift2term_with_unit, value, operation, arguments, Sym, Term, simplify, symbolic_linear_solve, diff --git a/src/structural_transformation/pantelides.jl b/src/structural_transformation/pantelides.jl index 585c4a29d1..53c790863f 100644 --- a/src/structural_transformation/pantelides.jl +++ b/src/structural_transformation/pantelides.jl @@ -196,7 +196,7 @@ function pantelides!( eq′ = eq_to_diff[eq′] end # for _ in 1:maxiters pathfound || - error("maxiters=$maxiters reached! File a bug report if your system has a reasonable index (<100), and you are using the default `maxiters`. Try to increase the maxiters by `pantelides(sys::ODESystem; maxiters=1_000_000)` if your system has an incredibly high index and it is truly extremely large.") + error("maxiters=$maxiters reached! File a bug report if your system has a reasonable index (<100), and you are using the default `maxiters`. Try to increase the maxiters by `pantelides(sys::System; maxiters=1_000_000)` if your system has an incredibly high index and it is truly extremely large.") end # for k in 1:neqs′ finalize && for var in 1:ndsts(graph) @@ -207,13 +207,13 @@ function pantelides!( end """ - dae_index_lowering(sys::ODESystem; kwargs...) -> ODESystem + dae_index_lowering(sys::System; kwargs...) -> System Perform the Pantelides algorithm to transform a higher index DAE to an index 1 DAE. `kwargs` are forwarded to [`pantelides!`](@ref). End users are encouraged to call [`structural_simplify`](@ref) instead, which calls this function internally. """ -function dae_index_lowering(sys::ODESystem; kwargs...) +function dae_index_lowering(sys::System; kwargs...) state = TearingState(sys) var_eq_matching = pantelides!(state; finalize = false, kwargs...) return invalidate_cache!(pantelides_reassemble(state, var_eq_matching)) diff --git a/src/structural_transformation/symbolics_tearing.jl b/src/structural_transformation/symbolics_tearing.jl index 99eab00685..5558d27770 100644 --- a/src/structural_transformation/symbolics_tearing.jl +++ b/src/structural_transformation/symbolics_tearing.jl @@ -40,7 +40,7 @@ function var_derivative_graph!(s::SystemStructure, v::Int) return var_diff end -function var_derivative!(ts::TearingState{ODESystem}, v::Int) +function var_derivative!(ts::TearingState, v::Int) s = ts.structure var_diff = var_derivative_graph!(s, v) sys = ts.sys @@ -58,7 +58,7 @@ function eq_derivative_graph!(s::SystemStructure, eq::Int) return eq_diff end -function eq_derivative!(ts::TearingState{ODESystem}, ieq::Int; kwargs...) +function eq_derivative!(ts::TearingState, ieq::Int; kwargs...) s = ts.structure eq_diff = eq_derivative_graph!(s, ieq) diff --git a/src/systems/abstractsystem.jl b/src/systems/abstractsystem.jl index d47bef8f77..09b09d82f8 100644 --- a/src/systems/abstractsystem.jl +++ b/src/systems/abstractsystem.jl @@ -1673,7 +1673,7 @@ function defaults(sys::AbstractSystem) # `mapfoldr` is really important!!! We should prefer the base model for # defaults, because people write: # - # `compose(ODESystem(...; defaults=defs), ...)` + # `compose(System(...; defaults=defs), ...)` # # Thus, right associativity is required and crucial for correctness. isempty(systems) ? defs : mapfoldr(namespace_defaults, merge, systems; init = defs) @@ -2887,7 +2887,7 @@ using ModelingToolkit: t, D @parameters p = 1.0, [description = "My parameter", tunable = false] q = 2.0, [description = "Other parameter"] @variables x(t) = 3.0 [unit = u"m"] -@named sys = ODESystem(Equation[], t, [x], [p, q]) +@named sys = System(Equation[], t, [x], [p, q]) ModelingToolkit.dump_parameters(sys) ``` @@ -2928,7 +2928,7 @@ using ModelingToolkit: t, D @parameters p = 1.0, [description = "My parameter", tunable = false] q = 2.0, [description = "Other parameter"] @variables x(t) = 3.0 [unit = u"m"] -@named sys = ODESystem(Equation[], t, [x], [p, q]) +@named sys = System(Equation[], t, [x], [p, q]) ModelingToolkit.dump_unknowns(sys) ``` @@ -3106,7 +3106,7 @@ using ModelingToolkit: t_nounits as t, D_nounits as D @variables X(t) eq1 = D(X) ~ p - d*X eq2 = 0 ~ p - d*X -@named osys = ODESystem([eq1, eq2], t) +@named osys = System([eq1, eq2], t) alg_equations(osys) # returns `[0 ~ p - d*X(t)]`. """ @@ -3125,7 +3125,7 @@ using ModelingToolkit: t_nounits as t, D_nounits as D @variables X(t) eq1 = D(X) ~ p - d*X eq2 = 0 ~ p - d*X -@named osys = ODESystem([eq1, eq2], t) +@named osys = System([eq1, eq2], t) diff_equations(osys) # returns `[Differential(t)(X(t)) ~ p - d*X(t)]`. """ @@ -3145,8 +3145,8 @@ using ModelingToolkit: t_nounits as t, D_nounits as D @variables X(t) eq1 = D(X) ~ p - d*X eq2 = 0 ~ p - d*X -@named osys1 = ODESystem([eq1], t) -@named osys2 = ODESystem([eq2], t) +@named osys1 = System([eq1], t) +@named osys2 = System([eq2], t) has_alg_equations(osys1) # returns `false`. has_alg_equations(osys2) # returns `true`. @@ -3167,8 +3167,8 @@ using ModelingToolkit: t_nounits as t, D_nounits as D @variables X(t) eq1 = D(X) ~ p - d*X eq2 = 0 ~ p - d*X -@named osys1 = ODESystem([eq1], t) -@named osys2 = ODESystem([eq2], t) +@named osys1 = System([eq1], t) +@named osys2 = System([eq2], t) has_diff_equations(osys1) # returns `true`. has_diff_equations(osys2) # returns `false`. @@ -3190,9 +3190,9 @@ using ModelingToolkit: t_nounits as t, D_nounits as D @variables X(t) eq1 = D(X) ~ p - d*X eq2 = 0 ~ p - d*X -@named osys1 = ODESystem([eq1], t) -@named osys2 = ODESystem([eq2], t) -osys12 = compose(osys1, [osys2]) +@named osys1 = ([eq1], t) +@named osys2 = ([eq2], t) +osys12 = compose(sys1, [osys2]) osys21 = compose(osys2, [osys1]) get_alg_eqs(osys12) # returns `Equation[]`. @@ -3215,8 +3215,8 @@ using ModelingToolkit: t_nounits as t, D_nounits as D @variables X(t) eq1 = D(X) ~ p - d*X eq2 = 0 ~ p - d*X -@named osys1 = ODESystem([eq1], t) -@named osys2 = ODESystem([eq2], t) +@named osys1 = tem([eq1], t) +@named osys2 = tem([eq2], t) osys12 = compose(osys1, [osys2]) osys21 = compose(osys2, [osys1]) @@ -3240,8 +3240,8 @@ using ModelingToolkit: t_nounits as t, D_nounits as D @variables X(t) eq1 = D(X) ~ p - d*X eq2 = 0 ~ p - d*X -@named osys1 = ODESystem([eq1], t) -@named osys2 = ODESystem([eq2], t) +@named osys1 = System([eq1], t) +@named osys2 = System([eq2], t) osys12 = compose(osys1, [osys2]) osys21 = compose(osys2, [osys1]) @@ -3266,8 +3266,8 @@ using ModelingToolkit: t_nounits as t, D_nounits as D @variables X(t) eq1 = D(X) ~ p - d*X eq2 = 0 ~ p - d*X -@named osys1 = ODESystem([eq1], t) -@named osys2 = ODESystem([eq2], t) +@named osys1 = tem([eq1], t) +@named osys2 = tem([eq2], t) osys12 = compose(osys1, [osys2]) osys21 = compose(osys2, [osys1]) diff --git a/src/systems/analysis_points.jl b/src/systems/analysis_points.jl index 0d1a2830cf..27a0204cb8 100644 --- a/src/systems/analysis_points.jl +++ b/src/systems/analysis_points.jl @@ -31,7 +31,7 @@ t = ModelingToolkit.get_iv(P) eqs = [connect(P.output, C.input) connect(C.output, :plant_input, P.input)] -sys = ODESystem(eqs, t, systems = [P, C], name = :feedback_system) +sys = System(eqs, t, systems = [P, C], name = :feedback_system) matrices_S, _ = get_sensitivity(sys, :plant_input) # Compute the matrices of a state-space representation of the (input) sensitivity function. matrices_T, _ = get_comp_sensitivity(sys, :plant_input) @@ -1007,12 +1007,12 @@ See also [`get_sensitivity`](@ref), [`get_comp_sensitivity`](@ref), [`open_loop` # """ - generate_control_function(sys::ModelingToolkit.AbstractODESystem, input_ap_name::Union{Symbol, Vector{Symbol}, AnalysisPoint, Vector{AnalysisPoint}}, dist_ap_name::Union{Symbol, Vector{Symbol}, AnalysisPoint, Vector{AnalysisPoint}}; system_modifier = identity, kwargs) + generate_control_function(sys::ModelingToolkit.AbstractSystem, input_ap_name::Union{Symbol, Vector{Symbol}, AnalysisPoint, Vector{AnalysisPoint}}, dist_ap_name::Union{Symbol, Vector{Symbol}, AnalysisPoint, Vector{AnalysisPoint}}; system_modifier = identity, kwargs) When called with analysis points as input arguments, we assume that all analysis points corresponds to connections that should be opened (broken). The use case for this is to get rid of input signal blocks, such as `Step` or `Sine`, since these are useful for simulation but are not needed when using the plant model in a controller or state estimator. """ function generate_control_function( - sys::ModelingToolkit.AbstractODESystem, input_ap_name::Union{ + sys::ModelingToolkit.AbstractSystem, input_ap_name::Union{ Symbol, Vector{Symbol}, AnalysisPoint, Vector{AnalysisPoint}}, dist_ap_name::Union{ Nothing, Symbol, Vector{Symbol}, AnalysisPoint, Vector{AnalysisPoint}} = nothing; diff --git a/src/systems/diffeqs/basic_transformations.jl b/src/systems/diffeqs/basic_transformations.jl index 5600239c57..b80ba3a820 100644 --- a/src/systems/diffeqs/basic_transformations.jl +++ b/src/systems/diffeqs/basic_transformations.jl @@ -21,7 +21,7 @@ using ModelingToolkit, OrdinaryDiffEq @variables x(t) y(t) D = Differential(t) eqs = [D(x) ~ α*x - β*x*y, D(y) ~ -δ*y + γ*x*y] -@named sys = ODESystem(eqs, t) +@named sys = System(eqs, t) sys2 = liouville_transform(sys) sys2 = complete(sys2) @@ -40,14 +40,14 @@ Optimal Transport Approach Abhishek Halder, Kooktae Lee, and Raktim Bhattacharya https://abhishekhalder.bitbucket.io/F16ACC2013Final.pdf """ -function liouville_transform(sys::AbstractODESystem; kwargs...) +function liouville_transform(sys::System; kwargs...) t = get_iv(sys) @variables trJ - D = ModelingToolkit.Differential(t) + D = Differential(t) neweq = D(trJ) ~ trJ * -tr(calculate_jacobian(sys)) neweqs = [equations(sys); neweq] vars = [unknowns(sys); trJ] - ODESystem( + System( neweqs, t, vars, parameters(sys); checks = false, name = nameof(sys), kwargs... ) @@ -55,7 +55,7 @@ end """ change_independent_variable( - sys::AbstractODESystem, iv, eqs = []; + sys::System, iv, eqs = []; add_old_diff = false, simplify = true, fold = false ) @@ -95,7 +95,7 @@ By changing the independent variable, it can be reformulated for vertical positi ```julia julia> @variables x(t) y(t); -julia> @named M = ODESystem([D(D(y)) ~ -9.81, D(D(x)) ~ 0.0], t); +julia> @named M = System([D(D(y)) ~ -9.81, D(D(x)) ~ 0.0], t); julia> M = change_independent_variable(M, x); @@ -109,7 +109,7 @@ julia> unknowns(M) ``` """ function change_independent_variable( - sys::AbstractODESystem, iv, eqs = []; + sys::System, iv, eqs = []; add_old_diff = false, simplify = true, fold = false ) iv2_of_iv1 = unwrap(iv) # e.g. u(t) @@ -166,7 +166,7 @@ function change_independent_variable( end # Use the utility function to transform everything in the system! - function transform(sys::AbstractODESystem) + function transform(sys::System) eqs = map(transform, get_eqs(sys)) unknowns = map(transform, get_unknowns(sys)) unknowns = filter(var -> !isequal(var, iv2), unknowns) # remove e.g. u diff --git a/src/systems/diffeqs/first_order_transform.jl b/src/systems/diffeqs/first_order_transform.jl index 97fd6460d9..d017ea362b 100644 --- a/src/systems/diffeqs/first_order_transform.jl +++ b/src/systems/diffeqs/first_order_transform.jl @@ -1,10 +1,10 @@ """ $(TYPEDSIGNATURES) -Takes a Nth order ODESystem and returns a new ODESystem written in first order +Takes a Nth order System and returns a new System written in first order form by defining new variables which represent the N-1 derivatives. """ -function ode_order_lowering(sys::ODESystem) +function ode_order_lowering(sys::System) iv = get_iv(sys) eqs_lowered, new_vars = ode_order_lowering(equations(sys), iv, unknowns(sys)) @set! sys.eqs = eqs_lowered @@ -12,7 +12,7 @@ function ode_order_lowering(sys::ODESystem) return sys end -function dae_order_lowering(sys::ODESystem) +function dae_order_lowering(sys::System) iv = get_iv(sys) eqs_lowered, new_vars = dae_order_lowering(equations(sys), iv, unknowns(sys)) @set! sys.eqs = eqs_lowered diff --git a/src/systems/diffeqs/modelingtoolkitize.jl b/src/systems/diffeqs/modelingtoolkitize.jl index b2954f81e4..ab13ecca29 100644 --- a/src/systems/diffeqs/modelingtoolkitize.jl +++ b/src/systems/diffeqs/modelingtoolkitize.jl @@ -1,7 +1,7 @@ """ $(TYPEDSIGNATURES) -Generate `ODESystem`, dependent variables, and parameters from an `ODEProblem`. +Generate `System`, dependent variables, and parameters from an `ODEProblem`. """ function modelingtoolkitize( prob::DiffEqBase.ODEProblem; u_names = nothing, p_names = nothing, kwargs...) @@ -95,7 +95,7 @@ function modelingtoolkitize( end filter!(x -> !iscall(x) || !(operation(x) isa Initial), params) filter!(x -> !iscall(x[1]) || !(operation(x[1]) isa Initial), default_p) - de = ODESystem(eqs, t, sts, params, + de = System(eqs, t, sts, params, defaults = merge(default_u0, default_p); name = gensym(:MTKizedODE), tspan = prob.tspan, diff --git a/src/systems/if_lifting.jl b/src/systems/if_lifting.jl index da069cc76e..9fd1958e5b 100644 --- a/src/systems/if_lifting.jl +++ b/src/systems/if_lifting.jl @@ -411,7 +411,10 @@ Lifting proceeds through the following process: * rewrite comparisons to be of the form eqn [op] 0; subtract the RHS from the LHS * replace comparisons with generated parameters; for each comparison eqn [op] 0, generate an event (dependent on op) that sets the parameter """ -function IfLifting(sys::ODESystem) +function IfLifting(sys::System) + if !is_time_dependent(sys) + throw(ArgumentError("IfLifting is only supported for time-dependent systems.")) + end cw = CondRewriter(get_iv(sys)) eqs = copy(equations(sys)) diff --git a/src/systems/systems.jl b/src/systems/systems.jl index 38aaf45363..47e94c6347 100644 --- a/src/systems/systems.jl +++ b/src/systems/systems.jl @@ -45,7 +45,7 @@ function structural_simplify( for pass in additional_passes newsys = pass(newsys) end - if newsys isa ODESystem || has_parent(newsys) + if has_parent(newsys) @set! newsys.parent = complete(sys; split = false, flatten = false) end newsys = complete(newsys; split) @@ -61,10 +61,6 @@ function __structural_simplify(sys::JumpSystem, args...; kwargs...) return sys end -function __structural_simplify(sys::SDESystem, args...; kwargs...) - return __structural_simplify(ODESystem(sys), args...; kwargs...) -end - function __structural_simplify( sys::AbstractSystem, io = nothing; simplify = false, sort_eqs = true, kwargs...) diff --git a/src/systems/systemstructure.jl b/src/systems/systemstructure.jl index ecbbbed0de..3c500014f9 100644 --- a/src/systems/systemstructure.jl +++ b/src/systems/systemstructure.jl @@ -9,7 +9,7 @@ import ..ModelingToolkit: isdiffeq, var_from_nested_derivative, vars!, flatten, independent_variables, SparseMatrixCLIL, AbstractSystem, equations, isirreducible, input_timedomain, TimeDomain, InferredTimeDomain, - VariableType, getvariabletype, has_equations, ODESystem + VariableType, getvariabletype, has_equations, System using ..BipartiteGraphs import ..BipartiteGraphs: invview, complete using Graphs diff --git a/src/utils.jl b/src/utils.jl index a45c8a23f0..8dd281b60a 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -1202,7 +1202,8 @@ end """ $(TYPEDSIGNATURES) -Find all the unknowns and parameters from the equations of a SDESystem or ODESystem. Return re-ordered equations, differential variables, all variables, and parameters. +Find all the unknowns and parameters from the equations of a System. Return re-ordered +equations, differential variables, all variables, and parameters. """ function process_equations(eqs, iv) if eltype(eqs) <: AbstractVector @@ -1238,7 +1239,7 @@ function process_equations(eqs, iv) diffvar, _ = var_from_nested_derivative(eq.lhs) if check_scope_depth(getmetadata(diffvar, SymScope, LocalScope()), 0) isequal(iv, iv_from_nested_derivative(eq.lhs)) || - throw(ArgumentError("An ODESystem can only have one independent variable.")) + throw(ArgumentError("A system of differential equations can only have one independent variable.")) diffvar in diffvars && throw(ArgumentError("The differential variable $diffvar is not unique in the system of equations.")) !has_diffvar_type(diffvar) && From b1712798ecaec992cf3db9f7b9e3052678def131 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Mon, 21 Apr 2025 13:01:25 +0530 Subject: [PATCH 079/185] refactor: remove `__structural_simplify(::JumpSystem)` --- src/systems/systems.jl | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/systems/systems.jl b/src/systems/systems.jl index 47e94c6347..031068583f 100644 --- a/src/systems/systems.jl +++ b/src/systems/systems.jl @@ -57,10 +57,6 @@ function structural_simplify( end end -function __structural_simplify(sys::JumpSystem, args...; kwargs...) - return sys -end - function __structural_simplify( sys::AbstractSystem, io = nothing; simplify = false, sort_eqs = true, kwargs...) From 4caf8c5be8e9010896088b08dfd7846c4c245d2e Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Mon, 21 Apr 2025 15:30:21 +0530 Subject: [PATCH 080/185] refactor: remove references to `SDESystem` in source code --- src/systems/diffeqs/basic_transformations.jl | 4 ++-- src/systems/diffeqs/modelingtoolkitize.jl | 4 ++-- src/systems/nonlinear/initializesystem.jl | 1 + src/systems/systems.jl | 4 ++-- src/utils.jl | 2 +- 5 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/systems/diffeqs/basic_transformations.jl b/src/systems/diffeqs/basic_transformations.jl index b80ba3a820..55eb6cc31a 100644 --- a/src/systems/diffeqs/basic_transformations.jl +++ b/src/systems/diffeqs/basic_transformations.jl @@ -255,7 +255,7 @@ Measure transformation method that allows for a reduction in the variance of an Input: Original SDE system and symbolic function `u(t,x)` with scalar output that defines the adjustable parameters `d` in the Girsanov transformation. Optional: initial condition for `θ0`. -Output: Modified SDESystem with additional component `θ_t` and initial value `θ0`, as well as +Output: Modified SDE System with additional component `θ_t` and initial value `θ0`, as well as the weight `θ_t/θ0` as observed equation, such that the estimator `Exp(g(X_t)θ_t/θ0)` has a smaller variance. @@ -275,7 +275,7 @@ using ModelingToolkit: t_nounits as t, D_nounits as D eqs = [D(x) ~ α*x] noiseeqs = [β*x] -@named de = SDESystem(eqs,noiseeqs,t,[x],[α,β]) +@named de = System(eqs,t,[x],[α,β]; noise_eqs = noiseeqs) # define u (user choice) u = x diff --git a/src/systems/diffeqs/modelingtoolkitize.jl b/src/systems/diffeqs/modelingtoolkitize.jl index ab13ecca29..cfa3ac43dd 100644 --- a/src/systems/diffeqs/modelingtoolkitize.jl +++ b/src/systems/diffeqs/modelingtoolkitize.jl @@ -226,7 +226,7 @@ end """ $(TYPEDSIGNATURES) -Generate `SDESystem`, dependent variables, and parameters from an `SDEProblem`. +Generate `System`, dependent variables, and parameters from an `SDEProblem`. """ function modelingtoolkitize(prob::DiffEqBase.SDEProblem; kwargs...) prob.f isa DiffEqBase.AbstractParameterizedFunction && @@ -293,7 +293,7 @@ function modelingtoolkitize(prob::DiffEqBase.SDEProblem; kwargs...) Dict() end - de = SDESystem(deqs, neqs, t, sts, params; + de = System(deqs, t, sts, params; noise_eqs = neqs, name = gensym(:MTKizedSDE), tspan = prob.tspan, defaults = merge(default_u0, default_p), diff --git a/src/systems/nonlinear/initializesystem.jl b/src/systems/nonlinear/initializesystem.jl index b5b93c8158..050333ca26 100644 --- a/src/systems/nonlinear/initializesystem.jl +++ b/src/systems/nonlinear/initializesystem.jl @@ -90,6 +90,7 @@ function generate_initializesystem_timevarying(sys::AbstractSystem; end end else + # TODO: Check if this is still necessary # 2) System doesn't have a schedule, so dummy derivatives don't exist/aren't handled (SDESystem) for (k, v) in u0map defs[k] = v diff --git a/src/systems/systems.jl b/src/systems/systems.jl index 031068583f..f75428a6dd 100644 --- a/src/systems/systems.jl +++ b/src/systems/systems.jl @@ -145,8 +145,8 @@ function __structural_simplify( end noise_eqs = StructuralTransformations.tearing_substitute_expr(ode_sys, noise_eqs) - ssys = SDESystem(Vector{Equation}(full_equations(ode_sys)), noise_eqs, - get_iv(ode_sys), unknowns(ode_sys), parameters(ode_sys); + ssys = System(Vector{Equation}(full_equations(ode_sys)), + get_iv(ode_sys), unknowns(ode_sys), parameters(ode_sys); noise_eqs, name = nameof(ode_sys), is_scalar_noise, observed = observed(ode_sys), defaults = defaults(sys), parameter_dependencies = parameter_dependencies(sys), assertions = assertions(sys), guesses = guesses(sys), initialization_eqs = initialization_equations(sys)) diff --git a/src/utils.jl b/src/utils.jl index 8dd281b60a..2d967ad45e 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -1308,7 +1308,7 @@ have different multiplicities in `a` and `b`. """ function _eq_unordered(a::AbstractArray, b::AbstractArray) # a and b may be multidimensional - # e.g. comparing noiseeqs of SDESystem + # e.g. comparing noiseeqs of SDEs a = vec(a) b = vec(b) length(a) === length(b) || return false From b8c5352722b29e580a3295f76f990691d5aa83da Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Mon, 21 Apr 2025 15:35:37 +0530 Subject: [PATCH 081/185] refactor: remove references to `NonlinearSystem` --- src/problems/sccnonlinearproblem.jl | 8 ++--- src/systems/codegen.jl | 2 +- .../nonlinear/homotopy_continuation.jl | 30 +++++++++---------- src/systems/nonlinear/initializesystem.jl | 10 +++---- src/systems/nonlinear/modelingtoolkitize.jl | 4 +-- 5 files changed, 26 insertions(+), 28 deletions(-) diff --git a/src/problems/sccnonlinearproblem.jl b/src/problems/sccnonlinearproblem.jl index 864dcb71ee..fae83ac018 100644 --- a/src/problems/sccnonlinearproblem.jl +++ b/src/problems/sccnonlinearproblem.jl @@ -54,7 +54,7 @@ function SCCNonlinearFunction{iip}( f_oop, f_iip = eval_or_rgf.(f_gen; eval_expression, eval_module) f = GeneratedFunctionWrapper{(2, 2, is_split(sys))}(f_oop, f_iip) - subsys = NonlinearSystem(_eqs, _dvs, ps; observed = _obs, + subsys = System(_eqs, _dvs, ps; observed = _obs, parameter_dependencies = parameter_dependencies(sys), name = nameof(sys)) if get_index_cache(sys) !== nothing @set! subsys.index_cache = subset_unknowns_observed( @@ -65,15 +65,15 @@ function SCCNonlinearFunction{iip}( return NonlinearFunction{iip}(f; sys = subsys) end -function SciMLBase.SCCNonlinearProblem(sys::NonlinearSystem, args...; kwargs...) +function SciMLBase.SCCNonlinearProblem(sys::System, args...; kwargs...) SCCNonlinearProblem{true}(sys, args...; kwargs...) end -function SciMLBase.SCCNonlinearProblem{iip}(sys::NonlinearSystem, u0map, +function SciMLBase.SCCNonlinearProblem{iip}(sys::System, u0map, parammap = SciMLBase.NullParameters(); eval_expression = false, eval_module = @__MODULE__, cse = true, kwargs...) where {iip} if !iscomplete(sys) || get_tearing_state(sys) === nothing - error("A simplified `NonlinearSystem` is required. Call `structural_simplify` on the system before creating an `SCCNonlinearProblem`.") + error("A simplified `System` is required. Call `structural_simplify` on the system before creating an `SCCNonlinearProblem`.") end if !is_split(sys) diff --git a/src/systems/codegen.jl b/src/systems/codegen.jl index eeb5f85de5..22e0fd6501 100644 --- a/src/systems/codegen.jl +++ b/src/systems/codegen.jl @@ -609,7 +609,7 @@ The signatures will be of the form `g(...)` with arguments: - `unknowns` if `param_only` is `false` - `inputs` if `inputs` is an array of symbolic inputs that should be available in `ts` - `p...` unconditionally; note that in the case of `MTKParameters` more than one parameters argument may be present, so it must be splatted -- `t` if the system is time-dependent; for example `NonlinearSystem` will not have `t` +- `t` if the system is time-dependent; for example systems of nonlinear equations will not have `t` For example, a function `g(op, unknowns, p..., inputs, t)` will be the in-place function generated if `return_inplace` is true, `ts` is a vector, an array of inputs `inputs` is given, and `param_only` is false for a time-dependent system. diff --git a/src/systems/nonlinear/homotopy_continuation.jl b/src/systems/nonlinear/homotopy_continuation.jl index 9a77779103..e68737c19b 100644 --- a/src/systems/nonlinear/homotopy_continuation.jl +++ b/src/systems/nonlinear/homotopy_continuation.jl @@ -211,7 +211,7 @@ end """ $(TYPEDEF) -Information representing how to transform a `NonlinearSystem` into a polynomial +Information representing how to transform a `System` into a polynomial system. """ struct PolynomialTransformation @@ -236,7 +236,7 @@ struct PolynomialTransformation polydata::Vector{PolynomialData} end -function PolynomialTransformation(sys::NonlinearSystem) +function PolynomialTransformation(sys::System) # we need to consider `full_equations` because observed also should be # polynomials (if used in equations) and we don't know if observed is used # in denominator. @@ -342,7 +342,7 @@ using the appropriate `PolynomialTransformation`. Also contains the denominators in the equations, to rule out invalid roots. """ struct PolynomialTransformationResult - sys::NonlinearSystem + sys::System denominators::Vector{BasicSymbolic} end @@ -353,7 +353,7 @@ Transform the system `sys` with `transformation` and return a `PolynomialTransformationResult`, or a `NotPolynomialError` if the system cannot be transformed. """ -function transform_system(sys::NonlinearSystem, transformation::PolynomialTransformation; +function transform_system(sys::System, transformation::PolynomialTransformation; fraction_cancel_fn = simplify_fractions) subrules = transformation.substitution_rules dvs = unknowns(sys) @@ -473,26 +473,26 @@ function handle_rational_polynomials(x, wrt; fraction_cancel_fn = simplify_fract return num, den end -function SciMLBase.HomotopyNonlinearFunction(sys::NonlinearSystem, args...; kwargs...) +function SciMLBase.HomotopyNonlinearFunction(sys::System, args...; kwargs...) ODEFunction{true}(sys, args...; kwargs...) end -function SciMLBase.HomotopyNonlinearFunction{true}(sys::NonlinearSystem, args...; +function SciMLBase.HomotopyNonlinearFunction{true}(sys::System, args...; kwargs...) ODEFunction{true, SciMLBase.AutoSpecialize}(sys, args...; kwargs...) end -function SciMLBase.HomotopyNonlinearFunction{false}(sys::NonlinearSystem, args...; +function SciMLBase.HomotopyNonlinearFunction{false}(sys::System, args...; kwargs...) ODEFunction{false, SciMLBase.FullSpecialize}(sys, args...; kwargs...) end function SciMLBase.HomotopyNonlinearFunction{iip, specialize}( - sys::NonlinearSystem, args...; eval_expression = false, eval_module = @__MODULE__, + sys::System, args...; eval_expression = false, eval_module = @__MODULE__, p = nothing, fraction_cancel_fn = SymbolicUtils.simplify_fractions, cse = true, kwargs...) where {iip, specialize} if !iscomplete(sys) - error("A completed `NonlinearSystem` is required. Call `complete` or `structural_simplify` on the system before creating a `HomotopyContinuationFunction`") + error("A completed `System` is required. Call `complete` or `structural_simplify` on the system before creating a `HomotopyContinuationFunction`") end transformation = PolynomialTransformation(sys) if transformation isa NotPolynomialError @@ -529,11 +529,11 @@ end struct HomotopyContinuationProblem{iip, specialization} end -function HomotopyContinuationProblem(sys::NonlinearSystem, args...; kwargs...) +function HomotopyContinuationProblem(sys::System, args...; kwargs...) HomotopyContinuationProblem{true}(sys, args...; kwargs...) end -function HomotopyContinuationProblem(sys::NonlinearSystem, t, +function HomotopyContinuationProblem(sys::System, t, u0map::StaticArray, args...; kwargs...) @@ -541,19 +541,19 @@ function HomotopyContinuationProblem(sys::NonlinearSystem, t, sys, t, u0map, args...; kwargs...) end -function HomotopyContinuationProblem{true}(sys::NonlinearSystem, args...; kwargs...) +function HomotopyContinuationProblem{true}(sys::System, args...; kwargs...) HomotopyContinuationProblem{true, SciMLBase.AutoSpecialize}(sys, args...; kwargs...) end -function HomotopyContinuationProblem{false}(sys::NonlinearSystem, args...; kwargs...) +function HomotopyContinuationProblem{false}(sys::System, args...; kwargs...) HomotopyContinuationProblem{false, SciMLBase.FullSpecialize}(sys, args...; kwargs...) end function HomotopyContinuationProblem{iip, spec}( - sys::NonlinearSystem, u0map, pmap = SciMLBase.NullParameters(); + sys::System, u0map, pmap = SciMLBase.NullParameters(); kwargs...) where {iip, spec} if !iscomplete(sys) - error("A completed `NonlinearSystem` is required. Call `complete` or `structural_simplify` on the system before creating a `HomotopyContinuationProblem`") + error("A completed `System` is required. Call `complete` or `structural_simplify` on the system before creating a `HomotopyContinuationProblem`") end f, u0, p = process_SciMLProblem( HomotopyNonlinearFunction{iip, spec}, sys, u0map, pmap; kwargs...) diff --git a/src/systems/nonlinear/initializesystem.jl b/src/systems/nonlinear/initializesystem.jl index 050333ca26..0e727cc2c0 100644 --- a/src/systems/nonlinear/initializesystem.jl +++ b/src/systems/nonlinear/initializesystem.jl @@ -10,7 +10,7 @@ end """ $(TYPEDSIGNATURES) -Generate `NonlinearSystem` which initializes a problem from specified initial conditions of an `AbstractTimeDependentSystem`. +Generate `System` of nonlinear equations which initializes a problem from specified initial conditions of an `AbstractTimeDependentSystem`. """ function generate_initializesystem_timevarying(sys::AbstractSystem; u0map = Dict(), @@ -153,8 +153,7 @@ function generate_initializesystem_timevarying(sys::AbstractSystem; for k in keys(defs) defs[k] = substitute(defs[k], paramsubs) end - - return NonlinearSystem(eqs_ics, + return System(eqs_ics, vars, pars; defaults = defs, @@ -168,7 +167,7 @@ end """ $(TYPEDSIGNATURES) -Generate `NonlinearSystem` which initializes a problem from specified initial conditions of an `AbstractTimeDependentSystem`. +Generate `System` of nonlinear equations which initializes a problem from specified initial conditions of an `AbstractTimeDependentSystem`. """ function generate_initializesystem_timeindependent(sys::AbstractSystem; u0map = Dict(), @@ -252,8 +251,7 @@ function generate_initializesystem_timeindependent(sys::AbstractSystem; for k in keys(defs) defs[k] = substitute(defs[k], paramsubs) end - - return NonlinearSystem(eqs_ics, + return System(eqs_ics, vars, pars; defaults = defs, diff --git a/src/systems/nonlinear/modelingtoolkitize.jl b/src/systems/nonlinear/modelingtoolkitize.jl index 2f12157884..4ce3769ef7 100644 --- a/src/systems/nonlinear/modelingtoolkitize.jl +++ b/src/systems/nonlinear/modelingtoolkitize.jl @@ -1,7 +1,7 @@ """ $(TYPEDSIGNATURES) -Generate `NonlinearSystem`, dependent variables, and parameters from an `NonlinearProblem`. +Generate `System`, dependent variables, and parameters from an `NonlinearProblem`. """ function modelingtoolkitize( prob::Union{NonlinearProblem, NonlinearLeastSquaresProblem}; @@ -76,7 +76,7 @@ function modelingtoolkitize( Dict() end - de = NonlinearSystem(eqs, sts, params, + de = System(eqs, sts, params, defaults = merge(default_u0, default_p); name = gensym(:MTKizedNonlinProb), kwargs...) From fd1cacbbe41459147d2f98be8ca0bafa7ece51b2 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Mon, 21 Apr 2025 16:25:24 +0530 Subject: [PATCH 082/185] refactor: remove references to `DiscreteSystem` --- src/ModelingToolkit.jl | 1 - src/systems/abstractsystem.jl | 2 +- src/systems/systems.jl | 7 ------- src/systems/systemstructure.jl | 9 +++++---- 4 files changed, 6 insertions(+), 13 deletions(-) diff --git a/src/ModelingToolkit.jl b/src/ModelingToolkit.jl index 33075ff63b..e0e1585625 100644 --- a/src/ModelingToolkit.jl +++ b/src/ModelingToolkit.jl @@ -129,7 +129,6 @@ abstract type AbstractTimeDependentSystem <: AbstractSystem end abstract type AbstractTimeIndependentSystem <: AbstractSystem end abstract type AbstractMultivariateSystem <: AbstractSystem end abstract type AbstractOptimizationSystem <: AbstractTimeIndependentSystem end -abstract type AbstractDiscreteSystem <: AbstractTimeDependentSystem end function independent_variable end diff --git a/src/systems/abstractsystem.jl b/src/systems/abstractsystem.jl index 09b09d82f8..61600c78cd 100644 --- a/src/systems/abstractsystem.jl +++ b/src/systems/abstractsystem.jl @@ -699,7 +699,7 @@ function add_initialization_parameters(sys::AbstractSystem; split = true) end # add derivatives of all variables for steady-state initial conditions - if is_time_dependent(sys) && !(sys isa AbstractDiscreteSystem) + if is_time_dependent(sys) && !is_discrete_system(sys) D = Differential(get_iv(sys)) union!(all_initialvars, [D(v) for v in all_initialvars if iscall(v)]) end diff --git a/src/systems/systems.jl b/src/systems/systems.jl index f75428a6dd..42399307dd 100644 --- a/src/systems/systems.jl +++ b/src/systems/systems.jl @@ -35,13 +35,6 @@ 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. Please construct \ - an ImplicitDiscreteSystem instead. - """) - end for pass in additional_passes newsys = pass(newsys) end diff --git a/src/systems/systemstructure.jl b/src/systems/systemstructure.jl index 3c500014f9..cba05226f7 100644 --- a/src/systems/systemstructure.jl +++ b/src/systems/systemstructure.jl @@ -474,11 +474,9 @@ function TearingState(sys; quick_cancel = false, check = true, sort_eqs = true) ts = TearingState(sys, fullvars, SystemStructure(complete(var_to_diff), complete(eq_to_diff), - complete(graph), nothing, var_types, sys isa AbstractDiscreteSystem), + complete(graph), nothing, var_types, false), Any[], param_derivative_map) - if sys isa DiscreteSystem - ts = shift_discrete_system(ts) - end + return ts end @@ -690,6 +688,9 @@ function structural_simplify!(state::TearingState, io = nothing; simplify = fals """)) end end + if continuous_id == 0 + state.structure.only_discrete = true + end sys, input_idxs = _structural_simplify!(state, io; simplify, check_consistency, fully_determined, kwargs...) From 1b9f91ce9f03fcc824d0d95b79c30b549dc1915e Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Tue, 22 Apr 2025 16:54:06 +0530 Subject: [PATCH 083/185] refactor: do not use `sys.substitutions` --- src/structural_transformation/symbolics_tearing.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/structural_transformation/symbolics_tearing.jl b/src/structural_transformation/symbolics_tearing.jl index 5558d27770..cb828a00a6 100644 --- a/src/structural_transformation/symbolics_tearing.jl +++ b/src/structural_transformation/symbolics_tearing.jl @@ -150,7 +150,7 @@ end function tearing_substitution(sys::AbstractSystem; kwargs...) neweqs = full_equations(sys::AbstractSystem; kwargs...) @set! sys.eqs = neweqs - @set! sys.substitutions = nothing + # @set! sys.substitutions = nothing @set! sys.schedule = nothing end @@ -753,7 +753,7 @@ function update_simplified_system!( @set! sys.eqs = neweqs @set! sys.observed = obs - @set! sys.substitutions = Substitutions(subeqs, deps) + # @set! sys.substitutions = Substitutions(subeqs, deps) # Only makes sense for time-dependent if ModelingToolkit.has_schedule(sys) From 694533768e30a5da5e9bfcb44022938957a1808e Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Tue, 22 Apr 2025 16:54:28 +0530 Subject: [PATCH 084/185] refactor: move `JumpType` definition to `utils.jl` --- src/utils.jl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/utils.jl b/src/utils.jl index 2d967ad45e..78cda2b42a 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -1355,3 +1355,5 @@ function flatten_equations(eqs::Vector{Equation}) end end end + +const JumpType = Union{VariableRateJump, ConstantRateJump, MassActionJump} From ff084c08dd57fac44b4342895d14c75238da36f8 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Tue, 22 Apr 2025 16:55:28 +0530 Subject: [PATCH 085/185] feat: `include` newly added files --- src/ModelingToolkit.jl | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/ModelingToolkit.jl b/src/ModelingToolkit.jl index e0e1585625..78e71c8679 100644 --- a/src/ModelingToolkit.jl +++ b/src/ModelingToolkit.jl @@ -156,10 +156,28 @@ include("systems/connectors.jl") include("systems/analysis_points.jl") include("systems/imperative_affect.jl") include("systems/callbacks.jl") +include("systems/system.jl") include("systems/codegen_utils.jl") +include("systems/codegen.jl") include("systems/problem_utils.jl") include("linearization.jl") +include("problems/compatibility.jl") +include("problems/odeproblem.jl") +include("problems/daeproblem.jl") +include("problems/ddeproblem.jl") +include("problems/sdeproblem.jl") +include("problems/sddeproblem.jl") +include("problems/bvproblem.jl") +include("problems/discreteproblem.jl") +include("problems/implicitdiscreteproblem.jl") +include("problems/nonlinearproblem.jl") +include("problems/intervalnonlinearproblem.jl") +include("problems/sccnonlinearproblem.jl") +include("problems/initializationproblem.jl") +include("problems/jumpproblem.jl") +include("problems/optimizationproblem.jl") + include("systems/optimization/modelingtoolkitize.jl") include("systems/nonlinear/homotopy_continuation.jl") From df03117bd0069f67600d1649b69b71bcf79bdc15 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Tue, 22 Apr 2025 16:55:45 +0530 Subject: [PATCH 086/185] feat: add fallback for `_eq_unordered` in non-array case --- src/utils.jl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/utils.jl b/src/utils.jl index 78cda2b42a..428a060a6d 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -1328,6 +1328,8 @@ function _eq_unordered(a::AbstractArray, b::AbstractArray) return true end +_eq_unordered(a, b) = isequal(a, b) + """ $(TYPEDSIGNATURES) From e1e8c2ad890ed8af2c7e3fb7924d4e19703d6a9b Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Tue, 22 Apr 2025 16:57:04 +0530 Subject: [PATCH 087/185] refactor: improve `System` constructors --- src/systems/system.jl | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/systems/system.jl b/src/systems/system.jl index 6d463733cd..0cc6bf8900 100644 --- a/src/systems/system.jl +++ b/src/systems/system.jl @@ -101,7 +101,7 @@ function default_consolidate(costs, subcosts) return sum(costs; init = 0.0) + sum(subcosts; init = 0.0) end -function System(eqs, iv, dvs, ps, brownians = []; +function System(eqs::Vector{Equation}, iv, dvs, ps, brownians = []; constraints = Union{Equation, Inequality}[], noise_eqs = nothing, jumps = [], costs = BasicSymbolic[], consolidate = default_consolidate, observed = Equation[], parameter_dependencies = Equation[], defaults = Dict(), @@ -176,7 +176,11 @@ function System(eqs, iv, dvs, ps, brownians = []; tstops, tearing_state, true, false, nothing, ignored_connections, parent, is_initializesystem; checks) end -function System(eqs, iv; kwargs...) +function System(eqs::Vector{Equation}, dvs, ps; kwargs...) + System(eqs, nothing, dvs, ps; kwargs...) +end + +function System(eqs::Vector{Equation}, iv; kwargs...) iv === nothing && return System(eqs; kwargs...) diffvars, allunknowns, ps, eqs = process_equations(eqs, iv) brownians = Set() @@ -232,7 +236,7 @@ function System(eqs, iv; kwargs...) collect(new_ps), brownians; kwargs...) end -function System(eqs; kwargs...) +function System(eqs::Vector{Equation}; kwargs...) eqs = collect(eqs) allunknowns = OrderedSet() From aaa96366ee8f2950c8c39c72ece7c923f88c53be Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Tue, 22 Apr 2025 16:57:49 +0530 Subject: [PATCH 088/185] fix: fix `==` and `hash` implementations for `System` --- src/systems/abstractsystem.jl | 17 ----- src/systems/system.jl | 118 ++++++++++++++++++++++++++++++++-- 2 files changed, 114 insertions(+), 21 deletions(-) diff --git a/src/systems/abstractsystem.jl b/src/systems/abstractsystem.jl index 61600c78cd..e8f46064ba 100644 --- a/src/systems/abstractsystem.jl +++ b/src/systems/abstractsystem.jl @@ -2643,23 +2643,6 @@ end ### ### Inheritance & composition ### -function Base.hash(sys::AbstractSystem, s::UInt) - s = hash(nameof(sys), s) - s = foldr(hash, get_systems(sys), init = s) - s = foldr(hash, get_unknowns(sys), init = s) - s = foldr(hash, get_ps(sys), init = s) - if sys isa OptimizationSystem - s = hash(get_op(sys), s) - else - s = foldr(hash, get_eqs(sys), init = s) - end - s = foldr(hash, get_observed(sys), init = s) - s = foldr(hash, get_continuous_events(sys), init = s) - s = foldr(hash, get_discrete_events(sys), init = s) - s = hash(independent_variables(sys), s) - return s -end - """ $(TYPEDSIGNATURES) diff --git a/src/systems/system.jl b/src/systems/system.jl index 0cc6bf8900..819874b38e 100644 --- a/src/systems/system.jl +++ b/src/systems/system.jl @@ -410,7 +410,50 @@ has_variableratejumps(js::System) = any(x -> x isa VariableRateJump, jumps(js)) # TODO: do we need this? it's kind of weird to keep has_equations(js::System) = !isempty(equations(js)) -# TODO: hash out the semantics of this +function noise_equations_equal(sys1::System, sys2::System) + neqs1 = get_noise_eqs(sys1) + neqs2 = get_noise_eqs(sys2) + if neqs1 === nothing && neqs2 === nothing + return true + elseif neqs1 === nothing || neqs2 === nothing + return false + end + ndims(neqs1) == ndims(neqs2) || return false + + eqs1 = get_eqs(sys1) + eqs2 = get_eqs(sys2) + + # get the permutation vector of `eqs2` in terms of `eqs1` + # eqs1_used tracks the elements of `eqs1` already used in the permutation + eqs1_used = falses(length(eqs1)) + # the permutation of `eqs1` that gives `eqs2` + eqs2_perm = Int[] + for eq in eqs2 + # find the first unused element of `eqs1` equal to `eq` + idx = findfirst(i -> isequal(eq, eqs1[i]) && !eqs1_used[i], eachindex(eqs1)) + # none found, so noise equations are not equal + idx === nothing && return false + push!(eqs2_perm, idx) + end + + if neqs1 isa Vector + return isequal(@view(neqs1[eqs2_perm]), neqs2) + else + return isequal(@view(neqs1[eqs2_perm, :]), neqs2) + end +end + +function ignored_connections_equal(sys1::System, sys2::System) + ic1 = get_ignored_connections(sys1) + ic2 = get_ignored_connections(sys2) + if ic1 === nothing && ic2 === nothing + return true + elseif ic1 === nothing || ic2 === nothing + return false + end + return _eq_unordered(ic1[1], ic2[1]) && _eq_unordered(ic1[2], ic2[2]) +end + function Base.:(==)(sys1::System, sys2::System) sys1 === sys2 && return true iv1 = get_iv(sys1) @@ -418,23 +461,90 @@ function Base.:(==)(sys1::System, sys2::System) isequal(iv1, iv2) && isequal(nameof(sys1), nameof(sys2)) && _eq_unordered(get_eqs(sys1), get_eqs(sys2)) && - _eq_unordered(get_noise_eqs(sys1), get_noise_eqs(sys2)) && + noise_equations_equal(sys1, sys2) && _eq_unordered(get_jumps(sys1), get_jumps(sys2)) && _eq_unordered(get_constraints(sys1), get_constraints(sys2)) && _eq_unordered(get_costs(sys1), get_costs(sys2)) && + isequal(get_consolidate(sys1), get_consolidate(sys2)) && _eq_unordered(get_unknowns(sys1), get_unknowns(sys2)) && _eq_unordered(get_ps(sys1), get_ps(sys2)) && _eq_unordered(get_brownians(sys1), get_brownians(sys2)) && _eq_unordered(get_observed(sys1), get_observed(sys2)) && _eq_unordered(get_parameter_dependencies(sys1), get_parameter_dependencies(sys2)) && + isequal(get_description(sys1), get_description(sys2)) && + isequal(get_defaults(sys1), get_defaults(sys2)) && + isequal(get_guesses(sys1), get_guesses(sys2)) && + _eq_unordered(get_initialization_eqs(sys1), get_initialization_eqs(sys2)) && _eq_unordered(get_continuous_events(sys1), get_continuous_events(sys2)) && _eq_unordered(get_discrete_events(sys1), get_discrete_events(sys2)) && - get_assertions(sys1) == get_assertions(sys2) && + isequal(get_connector_type(sys1), get_connector_type(sys2)) && + isequal(get_assertions(sys1), get_assertions(sys2)) && + isequal(get_metadata(sys1), get_metadata(sys2)) && + isequal(get_gui_metadata(sys1), get_gui_metadata(sys2)) && get_is_dde(sys1) == get_is_dde(sys2) && - get_tstops(sys1) == get_tstops(sys2) && + _eq_unordered(get_tstops(sys1), get_tstops(sys2)) && + # not comparing tearing states because checking if they're equal up to ordering + # is difficult + get_namespacing(sys1) == get_namespacing(sys2) && + get_complete(sys1) == get_complete(sys2) && + ignored_connections_equal(sys1, sys2) && + get_parent(sys1) == get_parent(sys2) && + get_isscheduled(sys1) == get_isscheduled(sys2) && all(s1 == s2 for (s1, s2) in zip(get_systems(sys1), get_systems(sys2))) end +function Base.hash(sys::System, h::UInt) + h = hash(nameof(sys), h) + h = hash(get_iv(sys), h) + # be considerate of things compared using `_eq_unordered` in `==` + eqs = get_eqs(sys) + eq_sortperm = sortperm(eqs; by = string) + h = hash(@view(eqs[eq_sortperm]), h) + neqs = get_noise_eqs(sys) + if neqs === nothing + h = hash(nothing, h) + elseif neqs isa Vector + h = hash(@view(neqs[eq_sortperm]), h) + else + h = hash(@view(neqs[eq_sortperm, :]), h) + end + h = hash(Set(get_jumps(sys)), h) + h = hash(Set(get_constraints(sys)), h) + h = hash(Set(get_costs(sys)), h) + h = hash(get_consolidate(sys), h) + h = hash(Set(get_unknowns(sys)), h) + h = hash(Set(get_ps(sys)), h) + h = hash(Set(get_brownians(sys)), h) + h = hash(Set(get_observed(sys)), h) + h = hash(Set(get_parameter_dependencies(sys)), h) + h = hash(get_description(sys), h) + h = hash(get_defaults(sys), h) + h = hash(get_guesses(sys), h) + h = hash(Set(get_initialization_eqs(sys)), h) + h = hash(Set(get_continuous_events(sys)), h) + h = hash(Set(get_discrete_events(sys)), h) + h = hash(get_connector_type(sys), h) + h = hash(get_assertions(sys), h) + h = hash(get_metadata(sys), h) + h = hash(get_gui_metadata(sys), h) + h = hash(get_is_dde(sys), h) + h = hash(Set(get_tstops(sys)), h) + h = hash(Set(getfield(sys, :namespacing)), h) + h = hash(Set(getfield(sys, :complete)), h) + ics = get_ignored_connections(sys) + if ics === nothing + h = hash(ics, h) + else + h = hash(Set(ics[1]), hash(Set(ics[2]), h), h) + end + h = hash(get_parent(sys), h) + h = hash(get_isscheduled(sys), h) + for s in get_systems(sys) + h = hash(s, h) + end + return h +end + """ $(TYPEDSIGNATURES) """ From cc984fb3fa1285f12fb843d07b7f55257499ce3c Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Tue, 22 Apr 2025 17:12:04 +0530 Subject: [PATCH 089/185] feat: add utility constructors for `OptimizationSystem` and `JumpSystem` --- src/ModelingToolkit.jl | 2 +- src/systems/system.jl | 20 ++++++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/src/ModelingToolkit.jl b/src/ModelingToolkit.jl index 78e71c8679..9f349f587c 100644 --- a/src/ModelingToolkit.jl +++ b/src/ModelingToolkit.jl @@ -267,7 +267,7 @@ export AbstractTimeDependentSystem, AbstractMultivariateSystem export ODEFunction, ODEFunctionExpr, ODEProblemExpr, convert_system, - add_accumulations, System + System, OptimizationSystem, JumpSystem export DAEFunctionExpr, DAEProblemExpr export SDEFunction, SDEFunctionExpr, SDEProblemExpr export SystemStructure diff --git a/src/systems/system.jl b/src/systems/system.jl index 819874b38e..838088ce31 100644 --- a/src/systems/system.jl +++ b/src/systems/system.jl @@ -552,6 +552,26 @@ function check_complete(sys::System, obj) iscomplete(sys) || throw(SystemNotCompleteError(obj)) end +######## +# Utility constructors +######## + +function OptimizationSystem(cost; kwargs...) + return System(Equation[]; costs = [cost], kwargs...) +end + +function OptimizationSystem(cost, dvs, ps; kwargs...) + return System(Equation[], nothing, dvs, ps; costs = [cost], kwargs...) +end + +function JumpSystem(jumps, iv; kwargs...) + return System(Equation[], iv; jumps, kwargs...) +end + +function JumpSystem(jumps, iv, dvs, ps; kwargs...) + return System(Equation[], iv, dvs, ps; jumps, kwargs...) +end + struct SystemNotCompleteError <: Exception obj::Any end From 3848460bceea438538a885919a4b66c271e6c316 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Tue, 22 Apr 2025 18:54:12 +0530 Subject: [PATCH 090/185] test: replace `ODESystem` with `System` --- test/accessor_functions.jl | 8 +- test/analysis_points.jl | 16 +- test/basic_transformations.jl | 24 +- test/causal_variables_connection.jl | 8 +- test/clock.jl | 18 +- test/code_generation.jl | 6 +- test/components.jl | 40 +-- test/constants.jl | 8 +- test/dae_jacobian.jl | 2 +- test/debugging.jl | 4 +- test/dep_graphs.jl | 2 +- test/distributed.jl | 2 +- test/domain_connectors.jl | 12 +- test/downstream/analysis_points.jl | 52 ++-- test/downstream/inversemodel.jl | 2 +- test/downstream/linearization_dd.jl | 2 +- test/downstream/linearize.jl | 22 +- test/downstream/test_disturbance_model.jl | 4 +- test/dq_units.jl | 38 +-- test/equation_type_accessors.jl | 6 +- test/error_handling.jl | 8 +- test/extensions/ad.jl | 10 +- test/extensions/bifurcationkit.jl | 2 +- test/fmi/fmi.jl | 12 +- test/funcaffect.jl | 32 +- test/function_registration.jl | 8 +- test/guess_propagation.jl | 16 +- test/hierarchical_initialization_eqs.jl | 12 +- test/index_cache.jl | 16 +- test/initial_values.jl | 38 +-- test/initializationsystem.jl | 82 ++--- test/input_output_handling.jl | 54 ++-- test/inputoutput.jl | 6 +- test/jacobiansparsity.jl | 4 +- test/labelledarrays.jl | 2 +- test/latexify.jl | 2 +- test/linearity.jl | 6 +- test/lowering_solving.jl | 10 +- test/mass_matrix.jl | 6 +- test/modelingtoolkitize.jl | 2 +- test/mtkparameters.jl | 22 +- test/namespacing.jl | 6 +- test/nonlinearsystem.jl | 14 +- test/odesystem.jl | 280 +++++++++--------- test/optimal_control.jl | 32 +- test/parameter_dependencies.jl | 38 +-- test/precompile_test/ODEPrecompileTest.jl | 2 +- test/problem_validation.jl | 4 +- test/reduction.jl | 36 +-- test/scc_nonlinear_problem.jl | 4 +- test/sciml_problem_inputs.jl | 2 +- test/sdesystem.jl | 22 +- test/serialization.jl | 4 +- test/split_parameters.jl | 20 +- test/state_selection.jl | 22 +- test/static_arrays.jl | 2 +- test/steadystatesystems.jl | 2 +- test/stream_connectors.jl | 50 ++-- .../index_reduction.jl | 14 +- test/structural_transformation/tearing.jl | 8 +- test/structural_transformation/utils.jl | 20 +- test/substitute_component.jl | 6 +- test/symbolic_events.jl | 110 +++---- test/symbolic_indexing_interface.jl | 18 +- test/symbolic_parameters.jl | 2 +- test/test_variable_metadata.jl | 6 +- test/units.jl | 36 +-- test/variable_scope.jl | 22 +- test/variable_utils.jl | 6 +- 69 files changed, 707 insertions(+), 707 deletions(-) diff --git a/test/accessor_functions.jl b/test/accessor_functions.jl index 7ce477155b..515e7fda42 100644 --- a/test/accessor_functions.jl +++ b/test/accessor_functions.jl @@ -56,13 +56,13 @@ let ] cevs = [[t ~ 1.0] => [Y ~ Y + 2.0]] devs = [(t == 2.0) => [Y ~ Y + 2.0]] - @named sys_bot = ODESystem( + @named sys_bot = System( eqs_bot, t; systems = [], continuous_events = cevs, discrete_events = devs) - @named sys_mid2 = ODESystem( + @named sys_mid2 = System( eqs_mid2, t; systems = [], continuous_events = cevs, discrete_events = devs) - @named sys_mid1 = ODESystem( + @named sys_mid1 = System( eqs_mid1, t; systems = [sys_bot], continuous_events = cevs, discrete_events = devs) - @named sys_top = ODESystem(eqs_top, t; systems = [sys_mid1, sys_mid2], + @named sys_top = System(eqs_top, t; systems = [sys_mid1, sys_mid2], continuous_events = cevs, discrete_events = devs) sys_bot_comp = complete(sys_bot) sys_mid2_comp = complete(sys_mid2) diff --git a/test/analysis_points.jl b/test/analysis_points.jl index e36afc02f7..54cf0004c6 100644 --- a/test/analysis_points.jl +++ b/test/analysis_points.jl @@ -12,14 +12,14 @@ using Symbolics: NAMESPACE_SEPARATOR ap = AnalysisPoint(:plant_input) eqs = [connect(P.output, C.input) connect(C.output, ap, P.input)] - sys_ap = ODESystem(eqs, t, systems = [P, C], name = :hej) + sys_ap = System(eqs, t, systems = [P, C], name = :hej) sys_ap2 = @test_nowarn expand_connections(sys_ap) @test all(eq -> !(eq.lhs isa AnalysisPoint), equations(sys_ap2)) eqs = [connect(P.output, C.input) connect(C.output, P.input)] - sys_normal = ODESystem(eqs, t, systems = [P, C], name = :hej) + sys_normal = System(eqs, t, systems = [P, C], name = :hej) sys_normal2 = @test_nowarn expand_connections(sys_normal) @test isequal(sys_ap2, sys_normal2) @@ -41,10 +41,10 @@ end @named C = Gain(; k = -1) eqs = [connect(P.output, C.input), connect(C.output, :plant_input, P.input)] - sys_ap = ODESystem(eqs, t, systems = [P, C], name = :hej) + sys_ap = System(eqs, t, systems = [P, C], name = :hej) ap2 = @test_nowarn sys_ap.plant_input @test nameof(ap2) == Symbol(join(["hej", "plant_input"], NAMESPACE_SEPARATOR)) - @named sys = ODESystem(Equation[], t; systems = [sys_ap]) + @named sys = System(Equation[], t; systems = [sys_ap]) ap3 = @test_nowarn sys.hej.plant_input @test nameof(ap3) == Symbol(join(["sys", "hej", "plant_input"], NAMESPACE_SEPARATOR)) csys = complete(sys) @@ -62,8 +62,8 @@ end ap = AnalysisPoint(:plant_input) eqs = [connect(P.output, C.input), connect(C.output, ap, P.input)] -sys = ODESystem(eqs, t, systems = [P, C], name = :hej) -@named nested_sys = ODESystem(Equation[], t; systems = [sys]) +sys = System(eqs, t, systems = [P, C], name = :hej) +@named nested_sys = System(Equation[], t; systems = [sys]) nonamespace_sys = toggle_namespacing(nested_sys, false) @testset "simplifies and solves" begin @@ -132,8 +132,8 @@ end eqs = [connect(P.output, :plant_output, C.input) connect(C.output, :plant_input, P.input)] -sys = ODESystem(eqs, t, systems = [P, C], name = :hej) -@named nested_sys = ODESystem(Equation[], t; systems = [sys]) +sys = System(eqs, t, systems = [P, C], name = :hej) +@named nested_sys = System(Equation[], t; systems = [sys]) test_cases = [ ("inner", sys, sys.plant_input), diff --git a/test/basic_transformations.jl b/test/basic_transformations.jl index 544a89cd29..7e83dd24e4 100644 --- a/test/basic_transformations.jl +++ b/test/basic_transformations.jl @@ -7,7 +7,7 @@ D = Differential(t) @parameters α β γ δ @variables x(t) y(t) eqs = [D(x) ~ α * x - β * x * y, D(y) ~ -δ * y + γ * x * y] - @named sys = ODESystem(eqs, t) + @named sys = System(eqs, t) sys = complete(sys) u0 = [x => 1.0, y => 1.0] @@ -28,7 +28,7 @@ end @testset "Change independent variable (trivial)" begin @variables x(t) y(t) eqs1 = [D(D(x)) ~ D(x) + x, D(y) ~ 1] - M1 = ODESystem(eqs1, t; name = :M) + M1 = System(eqs1, t; name = :M) M2 = change_independent_variable(M1, y) @variables y x(y) yˍt(y) Dy = Differential(y) @@ -47,7 +47,7 @@ end D(s) ~ 1 / (2 * s) ] initialization_eqs = [x ~ 1.0, y ~ 1.0, D(y) ~ 0.0] - M1 = ODESystem(eqs, t; initialization_eqs, name = :M) + M1 = System(eqs, t; initialization_eqs, name = :M) M2 = change_independent_variable(M1, s) M1 = structural_simplify(M1; allow_symbolic = true) @@ -67,7 +67,7 @@ end D = Differential(t) @variables a(t) ȧ(t) Ω(t) ϕ(t) a, ȧ = GlobalScope.([a, ȧ]) - species(w; kw...) = ODESystem([D(Ω) ~ -3(1 + w) * D(a) / a * Ω], t, [Ω], []; kw...) + species(w; kw...) = System([D(Ω) ~ -3(1 + w) * D(a) / a * Ω], t, [Ω], []; kw...) @named r = species(1 // 3) @named m = species(0) @named Λ = species(-1) @@ -77,7 +77,7 @@ end ȧ ~ √(Ω) * a^2, D(D(ϕ)) ~ -3 * D(a) / a * D(ϕ) ] - M1 = ODESystem(eqs, t, [Ω, a, ȧ, ϕ], []; name = :M) + M1 = System(eqs, t, [Ω, a, ȧ, ϕ], []; name = :M) M1 = compose(M1, r, m, Λ) # Apply in two steps, where derivatives are defined at each step: first t -> a, then a -> b @@ -108,7 +108,7 @@ end @testset "Change independent variable (simple)" begin @variables x(t) y1(t) # y(t)[1:1] # TODO: use array variables y(t)[1:2] when fixed: https://github.com/JuliaSymbolics/Symbolics.jl/issues/1383 - Mt = ODESystem([D(x) ~ 2 * x, D(y1) ~ y1], t; name = :M) + Mt = System([D(x) ~ 2 * x, D(y1) ~ y1], t; name = :M) Mx = change_independent_variable(Mt, x) @variables x xˍt(x) xˍtt(x) y1(x) # y(x)[1:1] # TODO: array variables Dx = Differential(x) @@ -118,7 +118,7 @@ end @testset "Change independent variable (free fall with 1st order horizontal equation)" begin @variables x(t) y(t) @parameters g=9.81 v # gravitational acceleration and constant horizontal velocity - Mt = ODESystem([D(D(y)) ~ -g, D(x) ~ v], t; name = :M) # gives (x, y) as function of t, ... + Mt = System([D(D(y)) ~ -g, D(x) ~ v], t; name = :M) # gives (x, y) as function of t, ... Mx = change_independent_variable(Mt, x; add_old_diff = true) # ... but we want y as a function of x Mx = structural_simplify(Mx; allow_symbolic = true) Dx = Differential(Mx.x) @@ -132,7 +132,7 @@ end @testset "Change independent variable (free fall with 2nd order horizontal equation)" begin @variables x(t) y(t) @parameters g = 9.81 # gravitational acceleration - Mt = ODESystem([D(D(y)) ~ -g, D(D(x)) ~ 0], t; name = :M) # gives (x, y) as function of t, ... + Mt = System([D(D(y)) ~ -g, D(D(x)) ~ 0], t; name = :M) # gives (x, y) as function of t, ... Mx = change_independent_variable(Mt, x; add_old_diff = true) # ... but we want y as a function of x Mx = structural_simplify(Mx; allow_symbolic = true) Dx = Differential(Mx.x) @@ -150,7 +150,7 @@ end (D^3)(y) ~ D(x)^2 + (D^2)(y^2) |> expand_derivatives, D(x)^2 + D(y)^2 ~ x^4 + y^5 + t^6 ] - M1 = ODESystem(eqs, t; name = :M) + M1 = System(eqs, t; name = :M) M2 = change_independent_variable(M1, x; add_old_diff = true) @test_nowarn structural_simplify(M2) @@ -184,7 +184,7 @@ end D(x) ~ 2t, D(y) ~ 1fc(t) + 2fc(x) + 3fc(y) + 1callme(f, t) + 2callme(f, x) + 3callme(f, y) ] - M1 = ODESystem(eqs, t; name = :M) + M1 = System(eqs, t; name = :M) # Ensure that interpolations are called with the same variables M2 = change_independent_variable(M1, x, [t ~ √(x)]) @@ -206,12 +206,12 @@ end @testset "Change independent variable (errors)" begin @variables x(t) y z(y) w(t) v(t) - M = ODESystem([D(x) ~ 1, v ~ x], t; name = :M) + M = System([D(x) ~ 1, v ~ x], t; name = :M) Ms = structural_simplify(M) @test_throws "structurally simplified" change_independent_variable(Ms, y) @test_throws "not a function of" change_independent_variable(M, y) @test_throws "not a function of" change_independent_variable(M, z) @variables x(..) # require explicit argument - M = ODESystem([D(x(t)) ~ x(t - 1)], t; name = :M) + M = System([D(x(t)) ~ x(t - 1)], t; name = :M) @test_throws "DDE" change_independent_variable(M, x(t)) end diff --git a/test/causal_variables_connection.jl b/test/causal_variables_connection.jl index c22e8319d4..222db540de 100644 --- a/test/causal_variables_connection.jl +++ b/test/causal_variables_connection.jl @@ -40,12 +40,12 @@ end eqs = [connect(P.output.u, C.input.u) connect(C.output.u, P.input.u)] - sys1 = ODESystem(eqs, t, systems = [P, C], name = :hej) + sys1 = System(eqs, t, systems = [P, C], name = :hej) sys = expand_connections(sys1) @test any(isequal(P.output.u ~ C.input.u), equations(sys)) @test any(isequal(C.output.u ~ P.input.u), equations(sys)) - @named sysouter = ODESystem(Equation[], t; systems = [sys1]) + @named sysouter = System(Equation[], t; systems = [sys1]) sys = expand_connections(sysouter) @test any(isequal(sys1.P.output.u ~ sys1.C.input.u), equations(sys)) @test any(isequal(sys1.C.output.u ~ sys1.P.input.u), equations(sys)) @@ -57,8 +57,8 @@ end ap = AnalysisPoint(:plant_input) eqs = [connect(P.output, C.input), connect(C.output.u, ap, P.input.u)] - sys = ODESystem(eqs, t, systems = [P, C], name = :hej) - @named nested_sys = ODESystem(Equation[], t; systems = [sys]) + sys = System(eqs, t, systems = [P, C], name = :hej) + @named nested_sys = System(Equation[], t; systems = [sys]) test_cases = [ ("inner", sys, sys.plant_input), diff --git a/test/clock.jl b/test/clock.jl index c6051a52a8..4a16b7f2be 100644 --- a/test/clock.jl +++ b/test/clock.jl @@ -22,7 +22,7 @@ eqs = [yd ~ Sample(dt)(y) u ~ Hold(ud) D(x) ~ -x + u y ~ x] -@named sys = ODESystem(eqs, t) +@named sys = System(eqs, t) # compute equation and variables' time domains #TODO: test linearize @@ -114,7 +114,7 @@ eqs = [yd ~ Sample(dt)(y) u ~ Hold(ud) D(x) ~ -x + u y ~ x] -@named sys = ODESystem(eqs, t) +@named sys = System(eqs, t) @test_throws ModelingToolkit.HybridSystemNotSupportedException ss=structural_simplify(sys); @test_skip begin @@ -189,7 +189,7 @@ eqs = [yd ~ Sample(dt)(y) u ~ Hold(ud1) + Hold(ud2) D(x) ~ -x + u y ~ x] - @named sys = ODESystem(eqs, t) + @named sys = System(eqs, t) ci, varmap = infer_clocks(sys) d = Clock(dt) @@ -217,7 +217,7 @@ eqs = [yd ~ Sample(dt)(y) @variables x(t)=1 u(t)=0 y(t)=0 eqs = [D(x) ~ -x + u y ~ x] - ODESystem(eqs, t; name = name) + System(eqs, t; name = name) end function filt(; name) @@ -225,7 +225,7 @@ eqs = [yd ~ Sample(dt)(y) a = 1 / exp(dt) eqs = [x ~ a * x(k - 1) + (1 - a) * u(k - 1) y ~ x] - ODESystem(eqs, t, name = name) + System(eqs, t, name = name) end function controller(kp; name) @@ -233,7 +233,7 @@ eqs = [yd ~ Sample(dt)(y) @parameters kp = kp eqs = [yd ~ Sample(y) ud ~ kp * (r - yd)] - ODESystem(eqs, t; name = name) + System(eqs, t; name = name) end @named f = filt() @@ -245,7 +245,7 @@ eqs = [yd ~ Sample(dt)(y) Hold(c.ud) ~ p.u # controller output to plant input p.y ~ c.y] - @named cl = ODESystem(connections, t, systems = [f, c, p]) + @named cl = System(connections, t, systems = [f, c, p]) ci, varmap = infer_clocks(cl) @@ -280,7 +280,7 @@ eqs = [yd ~ Sample(dt)(y) D(x) ~ -x + u y ~ x] - @named cl = ODESystem(eqs, t) + @named cl = System(eqs, t) d = Clock(dt) d2 = Clock(dt2) @@ -528,7 +528,7 @@ eqs = [yd ~ Sample(dt)(y) @variables x(t)=1.0 y(t)=1.0 eqs = [D(y) ~ Hold(x) x ~ x(k - 1) + x(k - 2)] - @mtkbuild sys = ODESystem(eqs, t) + @mtkbuild sys = System(eqs, t) prob = ODEProblem(sys, [], (0.0, 10.0)) int = init(prob, Tsit5(); kwargshandle = KeywordArgSilent) @test int.ps[x] == 2.0 diff --git a/test/code_generation.jl b/test/code_generation.jl index cf3d660b81..03abf9e588 100644 --- a/test/code_generation.jl +++ b/test/code_generation.jl @@ -5,7 +5,7 @@ using ModelingToolkit: t_nounits as t, D_nounits as D @variables x(t) y(t)[1:3] @parameters p1=1.0 p2[1:3]=[1.0, 2.0, 3.0] p3::Int=1 p4::Bool=false - sys = complete(ODESystem(Equation[], t, [x; y], [p1, p2, p3, p4]; name = :sys)) + sys = complete(System(Equation[], t, [x; y], [p1, p2, p3, p4]; name = :sys)) u0 = [1.0, 2.0, 3.0, 4.0] p = ModelingToolkit.MTKParameters(sys, []) @@ -58,7 +58,7 @@ end @testset "Non-standard array variables" begin @variables x(t) @parameters p[0:2] (f::Function)(..) - @mtkbuild sys = ODESystem(D(x) ~ p[0] * x + p[1] * t + p[2] + f(p), t) + @mtkbuild sys = System(D(x) ~ p[0] * x + p[1] * t + p[2] + f(p), t) prob = ODEProblem(sys, [x => 1.0], (0.0, 1.0), [p => [1.0, 2.0, 3.0], f => sum]) @test prob.ps[p] == [1.0, 2.0, 3.0] @test prob.ps[p[0]] == 1.0 @@ -68,7 +68,7 @@ end @testset "Array split across buffers" begin @variables x(t)[0:2] @parameters p[1:2] (f::Function)(..) - @named sys = ODESystem( + @named sys = System( [D(x[0]) ~ p[1] * x[0] + x[2], D(x[1]) ~ p[2] * f(x) + x[2]], t) sys, = structural_simplify(sys, ([x[2]], [])) @test is_parameter(sys, x[2]) diff --git a/test/components.jl b/test/components.jl index 0ae5327bde..c7e97dea81 100644 --- a/test/components.jl +++ b/test/components.jl @@ -66,7 +66,7 @@ let connect(capacitor.n, source.n) connect(capacitor.n, ground.g)] - @named _rc_model = ODESystem(rc_eqs, t) + @named _rc_model = System(rc_eqs, t) @named rc_model = compose(_rc_model, [resistor, capacitor, source, ground]) sys = structural_simplify(rc_model) @@ -90,7 +90,7 @@ let connect(capacitor.n, source.n) connect(capacitor.n, ground.g)] - @named _rc_model2 = ODESystem(rc_eqs2, t) + @named _rc_model2 = System(rc_eqs2, t) @named rc_model2 = compose(_rc_model2, [resistor, resistor2, capacitor, source, ground]) sys2 = structural_simplify(rc_model2) @@ -113,7 +113,7 @@ function rc_component(; name, R = 1, C = 1) eqs = [connect(p, resistor.p); connect(resistor.n, capacitor.p); connect(capacitor.n, n)] - @named sys = ODESystem(eqs, t, [], [R, C]) + @named sys = System(eqs, t, [], [R, C]) compose(sys, [p, n, resistor, capacitor]; name = name) end @@ -123,7 +123,7 @@ end eqs = [connect(source.p, rc_comp.p) connect(source.n, rc_comp.n) connect(source.n, ground.g)] -@named sys′ = ODESystem(eqs, t) +@named sys′ = System(eqs, t) @named sys_inner_outer = compose(sys′, [ground, source, rc_comp]) @test_nowarn show(IOBuffer(), MIME"text/plain"(), sys_inner_outer) expand_connections(sys_inner_outer, debug = true) @@ -166,16 +166,16 @@ prob = ODEProblem(sys, u0, (0, 10.0)) @test_nowarn sol = solve(prob, FBDF()) @variables x1(t) x2(t) x3(t) x4(t) -@named sys1_inner = ODESystem([D(x1) ~ x1], t) -@named sys1_partial = compose(ODESystem([D(x2) ~ x2], t; name = :foo), sys1_inner) -@named sys1 = extend(ODESystem([D(x3) ~ x3], t; name = :foo), sys1_partial) -@named sys2 = compose(ODESystem([D(x4) ~ x4], t; name = :foo), sys1) +@named sys1_inner = System([D(x1) ~ x1], t) +@named sys1_partial = compose(System([D(x2) ~ x2], t; name = :foo), sys1_inner) +@named sys1 = extend(System([D(x3) ~ x3], t; name = :foo), sys1_partial) +@named sys2 = compose(System([D(x4) ~ x4], t; name = :foo), sys1) @test_nowarn sys2.sys1.sys1_inner.x1 # test the correct nesting # compose tests function record_fun(; name) pars = @parameters a=10 b=100 - ODESystem(Equation[], t, [], pars; name) + System(Equation[], t, [], pars; name) end function first_model(; name) @@ -185,7 +185,7 @@ function first_model(; name) defs[foo.a] = 3 defs[foo.b] = 300 pars = @parameters x=2 y=20 - compose(ODESystem(Equation[], t, [], pars; name, defaults = defs), foo) + compose(System(Equation[], t, [], pars; name, defaults = defs), foo) end @named goo = first_model() @unpack foo = goo @@ -217,7 +217,7 @@ function Load(; name) @named resistor = Resistor(R = R) eqs = [connect(p, resistor.p); connect(resistor.n, n)] - @named sys = ODESystem(eqs, t) + @named sys = System(eqs, t) compose(sys, [p, n, resistor]; name = name) end @@ -228,7 +228,7 @@ function Circuit(; name) @named resistor = Resistor(R = R) eqs = [connect(load.p, ground.g); connect(resistor.p, ground.g)] - @named sys = ODESystem(eqs, t) + @named sys = System(eqs, t) compose(sys, [ground, resistor, load]; name = name) end @@ -247,7 +247,7 @@ function parallel_rc_model(i; name, source, ground, R, C) connect(capacitor.n, source.n, ground.g) connect(resistor.h, heat_capacitor.h)] - compose(ODESystem(rc_eqs, t, name = Symbol(name, i)), + compose(System(rc_eqs, t, name = Symbol(name, i)), [resistor, capacitor, source, ground, heat_capacitor]) end V = 2.0 @@ -264,7 +264,7 @@ eqs = [ D(E) ~ sum(((i, sys),) -> getproperty(sys, Symbol(:resistor, i)).h.Q_flow, enumerate(rc_systems)) ] -@named _big_rc = ODESystem(eqs, t, [E], []) +@named _big_rc = System(eqs, t, [E], []) @named big_rc = compose(_big_rc, rc_systems) ts = TearingState(expand_connections(big_rc)) @test istriu(but_ordered_incidence(ts)[1]) @@ -277,7 +277,7 @@ function FixedResistor(; name, R = 1.0) eqs = [ v ~ i * R ] - extend(ODESystem(eqs, t, [], []; name = name), oneport) + extend(System(eqs, t, [], []; name = name), oneport) end capacitor = Capacitor(; name = :c1) resistor = FixedResistor(; name = :r1) @@ -286,7 +286,7 @@ rc_eqs = [connect(capacitor.n, resistor.p) connect(resistor.n, capacitor.p) connect(capacitor.n, ground.g)] -@named _rc_model = ODESystem(rc_eqs, t) +@named _rc_model = System(rc_eqs, t) @named rc_model = compose(_rc_model, [resistor, capacitor, ground]) sys = structural_simplify(rc_model) @@ -300,7 +300,7 @@ sol = solve(prob, Tsit5()) @connector function Pin1(; name) @independent_variables t sts = @variables v(t)=1.0 i(t)=1.0 - ODESystem(Equation[], t, sts, []; name = name) + System(Equation[], t, sts, []; name = name) end @test string(Base.doc(Pin1)) == "Hey there, Pin1!\n" @@ -310,7 +310,7 @@ sol = solve(prob, Tsit5()) @component function Pin2(; name) @independent_variables t sts = @variables v(t)=1.0 i(t)=1.0 - ODESystem(Equation[], t, sts, []; name = name) + System(Equation[], t, sts, []; name = name) end @test string(Base.doc(Pin2)) == "Hey there, Pin2!\n" end @@ -374,8 +374,8 @@ end @testset "Issue#3275: Metadata retained on `complete`" begin @variables x(t) y(t) @testset "ODESystem" begin - @named inner = ODESystem(D(x) ~ x, t) - @named outer = ODESystem(D(y) ~ y, t; systems = [inner], metadata = "test") + @named inner = System(D(x) ~ x, t) + @named outer = System(D(y) ~ y, t; systems = [inner], metadata = "test") @test ModelingToolkit.get_metadata(outer) == "test" sys = complete(outer) @test ModelingToolkit.get_metadata(sys) == "test" diff --git a/test/constants.jl b/test/constants.jl index f2c4fdaa86..5e97d52d7f 100644 --- a/test/constants.jl +++ b/test/constants.jl @@ -10,7 +10,7 @@ UMT = ModelingToolkit.UnitfulUnitCheck @variables x(t) w(t) D = Differential(t) eqs = [D(x) ~ a] -@named sys = ODESystem(eqs, t) +@named sys = System(eqs, t) prob = ODEProblem(complete(sys), [0], [0.0, 1.0], []) sol = solve(prob, Tsit5()) @@ -20,7 +20,7 @@ newsys = MT.eliminate_constants(sys) # Test structural_simplify substitutions & observed values eqs = [D(x) ~ 1, w ~ a] -@named sys = ODESystem(eqs, t) +@named sys = System(eqs, t) # Now eliminate the constants first simp = structural_simplify(sys) @test equations(simp) == [D(x) ~ 1.0] @@ -33,7 +33,7 @@ UMT.get_unit(β) @variables x(t) [unit = u"m"] D = Differential(t) eqs = [D(x) ~ β] -@named sys = ODESystem(eqs, t) +@named sys = System(eqs, t) simp = structural_simplify(sys) @test isempty(MT.collect_constants(nothing)) @@ -44,7 +44,7 @@ simp = structural_simplify(sys) @variables x(MT.t_nounits) = h eqs = [MT.D_nounits(x) ~ (h - x) / τ] - @mtkbuild fol_model = ODESystem(eqs, MT.t_nounits) + @mtkbuild fol_model = System(eqs, MT.t_nounits) prob = ODEProblem(fol_model, [], (0.0, 10.0)) @test prob[x] ≈ 1 diff --git a/test/dae_jacobian.jl b/test/dae_jacobian.jl index 8c68df767a..94f15cbb7c 100644 --- a/test/dae_jacobian.jl +++ b/test/dae_jacobian.jl @@ -36,7 +36,7 @@ sol1 = solve(prob1, IDA(linear_solver = :KLU)) eqs = [D(u1) ~ p1 * u1 - u1 * u2, D(u2) ~ u1 * u2 - p2 * u2] -@named sys = ODESystem(eqs, t) +@named sys = System(eqs, t) u0 = [u1 => 1.0, u2 => 1.0] diff --git a/test/debugging.jl b/test/debugging.jl index a55684737c..de4420d08c 100644 --- a/test/debugging.jl +++ b/test/debugging.jl @@ -4,7 +4,7 @@ using ModelingToolkit: t_nounits as t, D_nounits as D, ASSERTION_LOG_VARIABLE @variables x(t) @brownian a -@named inner_ode = ODESystem(D(x) ~ -sqrt(x), t; assertions = [(x > 0) => "ohno"]) +@named inner_ode = System(D(x) ~ -sqrt(x), t; assertions = [(x > 0) => "ohno"]) @named inner_sde = System([D(x) ~ -sqrt(x) + a], t; assertions = [(x > 0) => "ohno"]) sys_ode = structural_simplify(inner_ode) sys_sde = structural_simplify(inner_sde) @@ -36,7 +36,7 @@ end @testset "Hierarchical system" begin @testset "$(typeof(inner))" for (ctor, Problem, inner, alg) in [ - (ODESystem, ODEProblem, inner_ode, Tsit5()), + (System, ODEProblem, inner_ode, Tsit5()), (System, SDEProblem, inner_sde, ImplicitEM())] @mtkbuild outer = ctor(Equation[], t; systems = [inner]) dsys = debug_system(outer; functions = []) diff --git a/test/dep_graphs.jl b/test/dep_graphs.jl index 3b02efb2d0..76cc216635 100644 --- a/test/dep_graphs.jl +++ b/test/dep_graphs.jl @@ -155,7 +155,7 @@ eqs = [D(S) ~ k1 - k1 * S - k2 * S * I - k1 * k2 / (1 + t) * S, D(I) ~ k2 * S * I, D(R) ~ -k2 * S^2 * R / 2 + k1 * I + k1 * k2 * S / (1 + t)] noiseeqs = [S, I, R] -@named os = ODESystem(eqs, t, [S, I, R], [k1, k2]) +@named os = System(eqs, t, [S, I, R], [k1, k2]) deps = equation_dependencies(os) S = value(S); I = value(I); diff --git a/test/distributed.jl b/test/distributed.jl index 0b75b8eeb3..e7edc17a66 100644 --- a/test/distributed.jl +++ b/test/distributed.jl @@ -13,7 +13,7 @@ addprocs(2) D(y) ~ x * (ρ - z) - y, D(z) ~ x * y - β * z] -@everywhere @named de = ODESystem(eqs, t) +@everywhere @named de = System(eqs, t) @everywhere de = complete(de) @everywhere u0 = [19.0, 20.0, 50.0] diff --git a/test/domain_connectors.jl b/test/domain_connectors.jl index 9a43d2938f..03d6ff264a 100644 --- a/test/domain_connectors.jl +++ b/test/domain_connectors.jl @@ -14,7 +14,7 @@ using Test dm(t), [connect = Flow] end - ODESystem(Equation[], t, vars, pars; name, defaults = [dm => 0]) + System(Equation[], t, vars, pars; name, defaults = [dm => 0]) end @connector function HydraulicFluid(; @@ -36,7 +36,7 @@ end dm ~ 0 ] - ODESystem(eqs, t, vars, pars; name, defaults = [dm => 0]) + System(eqs, t, vars, pars; name, defaults = [dm => 0]) end function FixedPressure(; p, name) @@ -54,7 +54,7 @@ function FixedPressure(; p, name) port.p ~ p ] - ODESystem(eqs, t, vars, pars; name, systems) + System(eqs, t, vars, pars; name, systems) end function FixedVolume(; vol, p_int, name) @@ -80,7 +80,7 @@ function FixedVolume(; vol, p_int, name) rho ~ port.ρ * (1 + p / port.β) dm ~ drho * vol] - ODESystem(eqs, t, vars, pars; name, systems) + System(eqs, t, vars, pars; name, systems) end function Valve2Port(; p_s_int, p_r_int, p_int, name) @@ -120,7 +120,7 @@ function Valve2Port(; p_s_int, p_r_int, p_int, name) HS.dm ~ ifelse(x >= 0, port.dm, 0) HR.dm ~ ifelse(x < 0, port.dm, 0)] - ODESystem(eqs, t, vars, pars; name, systems) + System(eqs, t, vars, pars; name, systems) end function System(; name) @@ -139,7 +139,7 @@ function System(; name) connect(vol.port, valve.port) valve.x ~ sin(2π * t * 10)] - return ODESystem(eqs, t, vars, pars; systems, name) + return System(eqs, t, vars, pars; systems, name) end @named odesys = System() diff --git a/test/downstream/analysis_points.jl b/test/downstream/analysis_points.jl index 29b9aad512..b84444cdf1 100644 --- a/test/downstream/analysis_points.jl +++ b/test/downstream/analysis_points.jl @@ -22,7 +22,7 @@ import ControlSystemsBase as CS connect(inertia2.flange_a, spring.flange_b, damper.flange_b)] if u !== nothing push!(eqs, connect(torque.tau, u.output)) - return ODESystem(eqs, t; + return System(eqs, t; systems = [ torque, inertia1, @@ -33,7 +33,7 @@ import ControlSystemsBase as CS ], name) end - ODESystem(eqs, t; systems = [torque, inertia1, inertia2, spring, damper], name) + System(eqs, t; systems = [torque, inertia1, inertia2, spring, damper], name) end @named r = Step(start_time = 0) @@ -50,7 +50,7 @@ import ControlSystemsBase as CS connect(sensor.phi, :y, er.input2) connect(er.output, :e, pid.err_input)] - closed_loop = ODESystem(connections, t, systems = [model, pid, filt, sensor, r, er], + closed_loop = System(connections, t, systems = [model, pid, filt, sensor, r, er], name = :closed_loop, defaults = [ model.inertia1.phi => 0.0, model.inertia2.phi => 0.0, @@ -89,7 +89,7 @@ end connect(add.output, C.input) connect(C.output, P.input)] - sys_inner = ODESystem(eqs, t, systems = [P, C, add], name = :inner) + sys_inner = System(eqs, t, systems = [P, C, add], name = :inner) @named r = Constant(k = 1) @named F = FirstOrder(k = 1, T = 3) @@ -98,7 +98,7 @@ end connect(sys_inner.P.output, sys_inner.add.input2) connect(sys_inner.C.output, :plant_input, sys_inner.P.input) connect(F.output, sys_inner.add.input1)] - sys_outer = ODESystem(eqs, t, systems = [F, sys_inner, r], name = :outer) + sys_outer = System(eqs, t, systems = [F, sys_inner, r], name = :outer) # test first that the structural_simplify works correctly ssys = structural_simplify(sys_outer) @@ -125,7 +125,7 @@ end connect(add.output, C.input) connect(C.output.u, P.input.u)] - sys_inner = ODESystem(eqs, t, systems = [P, C, add], name = :inner) + sys_inner = System(eqs, t, systems = [P, C, add], name = :inner) @named r = Constant(k = 1) @named F = FirstOrder(k = 1, T = 3) @@ -134,7 +134,7 @@ end connect(sys_inner.P.output.u, sys_inner.add.input2.u) connect(sys_inner.C.output.u, :plant_input, sys_inner.P.input.u) connect(F.output, sys_inner.add.input1)] - sys_outer = ODESystem(eqs, t, systems = [F, sys_inner, r], name = :outer) + sys_outer = System(eqs, t, systems = [F, sys_inner, r], name = :outer) # test first that the structural_simplify works correctly ssys = structural_simplify(sys_outer) @@ -161,7 +161,7 @@ end connect(add.output, C.input) connect(C.output, P.input)] - sys_inner = ODESystem(eqs, t, systems = [P, C, add], name = :inner) + sys_inner = System(eqs, t, systems = [P, C, add], name = :inner) @named r = Constant(k = 1) @named F = FirstOrder(k = 1, T = 3) @@ -170,7 +170,7 @@ end connect(sys_inner.P.output, sys_inner.add.input2) connect(sys_inner.C.output.u, :plant_input, sys_inner.P.input.u) connect(F.output, sys_inner.add.input1)] - sys_outer = ODESystem(eqs, t, systems = [F, sys_inner, r], name = :outer) + sys_outer = System(eqs, t, systems = [F, sys_inner, r], name = :outer) # test first that the structural_simplify works correctly ssys = structural_simplify(sys_outer) @@ -192,7 +192,7 @@ end @named P_inner = FirstOrder(k = 1, T = 1) @named feedback = Feedback() @named ref = Step() - @named sys_inner = ODESystem( + @named sys_inner = System( [connect(P_inner.output, :y, feedback.input2) connect(feedback.output, :u, P_inner.input) connect(ref.output, :r, feedback.input1)], @@ -208,7 +208,7 @@ end Sinner = sminreal(ss(get_sensitivity(sys_inner, :u)[1]...)) - @named sys_inner = ODESystem( + @named sys_inner = System( [connect(P_inner.output, :y, feedback.input2) connect(feedback.output, :u, P_inner.input)], t, @@ -216,7 +216,7 @@ end @named P_outer = FirstOrder(k = rand(), T = rand()) - @named sys_outer = ODESystem( + @named sys_outer = System( [connect(sys_inner.P_inner.output, :y2, P_outer.input) connect(P_outer.output, :u2, sys_inner.feedback.input1)], t, @@ -249,7 +249,7 @@ end eqs = [connect(P.output, :plant_output, K.input) connect(K.output, :plant_input, P.input)] - sys = ODESystem(eqs, t, systems = [P, K], name = :hej) + sys = System(eqs, t, systems = [P, K], name = :hej) matrices, _ = get_sensitivity(sys, :plant_input) S = CS.feedback(I(2), Kss * Pss, pos_feedback = true) @@ -281,14 +281,14 @@ end connect(add.output, C.input) connect(C.output, :plant_input, P.input)] - sys_inner = ODESystem(eqs, t, systems = [P, C, add], name = :inner) + sys_inner = System(eqs, t, systems = [P, C, add], name = :inner) @named r = Constant(k = 1) @named F = FirstOrder(k = 1, T = 3) eqs = [connect(r.output, F.input) connect(F.output, sys_inner.add.input1)] - sys_outer = ODESystem(eqs, t, systems = [F, sys_inner, r], name = :outer) + sys_outer = System(eqs, t, systems = [F, sys_inner, r], name = :outer) matrices, _ = get_sensitivity( sys_outer, [sys_outer.inner.plant_input, sys_outer.inner.plant_output]) @@ -352,13 +352,13 @@ function normal_test_system() connect(F1.output, add.input1) connect(F2.output, add.input2) connect(add.output, back.input2)] - @named normal_inner = ODESystem(eqs_normal, t; systems = [F1, F2, add, back]) + @named normal_inner = System(eqs_normal, t; systems = [F1, F2, add, back]) @named step = Step() eqs2_normal = [ connect(step.output, normal_inner.back.input1) ] - @named sys_normal = ODESystem(eqs2_normal, t; systems = [normal_inner, step]) + @named sys_normal = System(eqs2_normal, t; systems = [normal_inner, step]) end sys_normal = normal_test_system() @@ -377,12 +377,12 @@ matrices_normal, _ = get_sensitivity(sys_normal, sys_normal.normal_inner.ap) connect(F1.output, add.input1) connect(F2.output, add.input2) connect(add.output, back.input2)] - @named inner = ODESystem(eqs, t; systems = [F1, F2, add, back]) + @named inner = System(eqs, t; systems = [F1, F2, add, back]) @named step = Step() eqs2 = [connect(step.output, inner.back.input1) connect(inner.back.output, :ap, inner.F1.input)] - @named sys = ODESystem(eqs2, t; systems = [inner, step]) + @named sys = System(eqs2, t; systems = [inner, step]) prob = ODEProblem(structural_simplify(sys), [], (0.0, 10.0)) @test SciMLBase.successful_retcode(solve(prob, Rodas5P())) @@ -401,12 +401,12 @@ end connect(F1.output, add.input1) connect(F2.output, add.input2) connect(add.output, back.input2)] - @named inner = ODESystem(eqs, t; systems = [F1, F2, add, back]) + @named inner = System(eqs, t; systems = [F1, F2, add, back]) @named step = Step() eqs2 = [connect(step.output, inner.back.input1) connect(inner.back.output.u, :ap, inner.F1.input.u)] - @named sys = ODESystem(eqs2, t; systems = [inner, step]) + @named sys = System(eqs2, t; systems = [inner, step]) prob = ODEProblem(structural_simplify(sys), [], (0.0, 10.0)) @test SciMLBase.successful_retcode(solve(prob, Rodas5P())) @@ -425,12 +425,12 @@ end connect(F1.output, add.input1) connect(F2.output, add.input2) connect(add.output, back.input2)] - @named inner = ODESystem(eqs, t; systems = [F1, F2, add, back]) + @named inner = System(eqs, t; systems = [F1, F2, add, back]) @named step = Step() eqs2 = [connect(step.output, inner.back.input1) connect(inner.back.output.u, :ap, inner.F1.input.u)] - @named sys = ODESystem(eqs2, t; systems = [inner, step]) + @named sys = System(eqs2, t; systems = [inner, step]) prob = ODEProblem(structural_simplify(sys), [], (0.0, 10.0)) @test SciMLBase.successful_retcode(solve(prob, Rodas5P())) @@ -459,10 +459,10 @@ end connect(inertia2.flange_a, spring.flange_b, damper.flange_b)] if u !== nothing push!(eqs, connect(torque.tau, u.output)) - return @named model = ODESystem( + return @named model = System( eqs, t; systems = [torque, inertia1, inertia2, spring, damper, u]) end - ODESystem(eqs, t; systems = [torque, inertia1, inertia2, spring, damper], name) + System(eqs, t; systems = [torque, inertia1, inertia2, spring, damper], name) end @named r = Step(start_time = 1) @@ -481,7 +481,7 @@ end connect(add.output, :u, model.torque.tau) # Name connection u to form an analysis point connect(model.inertia1.flange_b, sensor.flange) connect(sensor.phi, :y, pid.measurement)] - closed_loop = ODESystem(connections, t, + closed_loop = System(connections, t, systems = [model, inverse_model, pid, filt, sensor, inverse_sensor, r, add], name = :closed_loop) # just ensure the system simplifies diff --git a/test/downstream/inversemodel.jl b/test/downstream/inversemodel.jl index 32d5ee87ec..6efe48705c 100644 --- a/test/downstream/inversemodel.jl +++ b/test/downstream/inversemodel.jl @@ -64,7 +64,7 @@ begin Fss = ss(Ftf) # Create an MTK-compatible constructor function RefFilter(; name) - sys = ODESystem(Fss; name) + sys = System(Fss; name) "Compute initial state that yields y0 as output" empty!(ModelingToolkit.get_defaults(sys)) return sys diff --git a/test/downstream/linearization_dd.jl b/test/downstream/linearization_dd.jl index c4076b6aad..3e94a02469 100644 --- a/test/downstream/linearization_dd.jl +++ b/test/downstream/linearization_dd.jl @@ -24,7 +24,7 @@ eqs = [connect(link1.TX1, cart.flange) connect(cart.flange, force.flange) connect(link1.TY1, fixed.flange)] -@named model = ODESystem(eqs, t, [], []; systems = [link1, cart, force, fixed]) +@named model = System(eqs, t, [], []; systems = [link1, cart, force, fixed]) lin_outputs = [cart.s, cart.v, link1.A, link1.dA] lin_inputs = [force.f.u] diff --git a/test/downstream/linearize.jl b/test/downstream/linearize.jl index 16df29f834..ddff9fed0a 100644 --- a/test/downstream/linearize.jl +++ b/test/downstream/linearize.jl @@ -12,7 +12,7 @@ eqs = [u ~ kp * (r - y) D(x) ~ -x + u y ~ x] -@named sys = ODESystem(eqs, t) +@named sys = System(eqs, t) lsys, ssys = linearize(sys, [r], [y]) lprob = LinearizationProblem(sys, [r], [y]) @@ -56,7 +56,7 @@ function plant(; name) D = Differential(t) eqs = [D(x) ~ -x + u y ~ x] - ODESystem(eqs, t; name = name) + System(eqs, t; name = name) end function filt_(; name) @@ -65,7 +65,7 @@ function filt_(; name) D = Differential(t) eqs = [D(x) ~ -2 * x + u y ~ x] - ODESystem(eqs, t, name = name) + System(eqs, t, name = name) end function controller(kp; name) @@ -74,7 +74,7 @@ function controller(kp; name) eqs = [ u ~ kp * (r - y) ] - ODESystem(eqs, t; name = name) + System(eqs, t; name = name) end @named f = filt_() @@ -85,7 +85,7 @@ connections = [f.y ~ c.r # filtered reference to controller reference c.u ~ p.u # controller output to plant input p.y ~ c.y] -@named cl = ODESystem(connections, t, systems = [f, c, p]) +@named cl = System(connections, t, systems = [f, c, p]) lsys0, ssys = linearize(cl, [f.u], [p.x]) desired_order = [f.x, p.x] @@ -181,7 +181,7 @@ function saturation(; y_max, y_min = y_max > 0 ? -y_max : -Inf, name) # The equation below is equivalent to y ~ clamp(u, y_min, y_max) y ~ ie(u > y_max, y_max, ie((y_min < u) & (u < y_max), u, y_min)) ] - ODESystem(eqs, t, name = name) + System(eqs, t, name = name) end @named sat = saturation(; y_max = 1) # inside the linear region, the function is identity @@ -228,7 +228,7 @@ function SystemModel(u = nothing; name = :model) connect(inertia2.flange_a, spring.flange_b, damper.flange_b)] if u !== nothing push!(eqs, connect(torque.tau, u.output)) - return ODESystem(eqs, t; + return System(eqs, t; systems = [ torque, inertia1, @@ -239,7 +239,7 @@ function SystemModel(u = nothing; name = :model) ], name) end - ODESystem(eqs, t; systems = [torque, inertia1, inertia2, spring, damper], name) + System(eqs, t; systems = [torque, inertia1, inertia2, spring, damper], name) end @named r = Step(start_time = 0) @@ -256,7 +256,7 @@ connections = [connect(r.output, :r, filt.input) connect(sensor.phi, :y, er.input2) connect(er.output, :e, pid.err_input)] -closed_loop = ODESystem(connections, t, systems = [model, pid, filt, sensor, r, er], +closed_loop = System(connections, t, systems = [model, pid, filt, sensor, r, er], name = :closed_loop, defaults = [ model.inertia1.phi => 0.0, model.inertia2.phi => 0.0, @@ -303,7 +303,7 @@ m_ss = 2.4000000003229878 @variables x(t) y(t) u(t)=1.0 @parameters p = 1.0 eqs = [D(x) ~ p * u, x ~ y] -@named sys = ODESystem(eqs, t) +@named sys = System(eqs, t) matrices1, _ = linearize(sys, [u], []; op = Dict(x => 2.0)) matrices2, _ = linearize(sys, [u], []; op = Dict(y => 2.0)) @@ -325,7 +325,7 @@ end @variables x(t) y(t) @parameters p eqs = [0 ~ x * log(y) - p] - @named sys = ODESystem(eqs, t; defaults = [p => 1.0]) + @named sys = System(eqs, t; defaults = [p => 1.0]) sys = complete(sys) @test_throws ModelingToolkit.MissingVariablesError linearize( sys, [x], []; op = Dict(x => 1.0), allow_input_derivatives = true) diff --git a/test/downstream/test_disturbance_model.jl b/test/downstream/test_disturbance_model.jl index 97276437e2..fbcca23f0c 100644 --- a/test/downstream/test_disturbance_model.jl +++ b/test/downstream/test_disturbance_model.jl @@ -71,7 +71,7 @@ lsys = named_ss( # If we now want to add a disturbance model, we cannot do that since we have already connected a constant to the disturbance input, we thus create a new wrapper model with inputs s = tf("s") -dist(; name) = ODESystem(1 / s; name) +dist(; name) = System(1 / s; name) @mtkmodel SystemModelWithDisturbanceModel begin @components begin @@ -106,7 +106,7 @@ sol = solve(prob, Tsit5()) ## # Now we only have an integrating disturbance affecting inertia1, what if we want both integrating and direct Gaussian? We'd need a "PI controller" disturbancemodel. If we add the disturbance model (s+1)/s we get the integrating and non-integrating noises being correlated which is fine, it reduces the dimensions of the sigma point by 1. -dist3(; name) = ODESystem(ss(1 + 10 / s, balance = false); name) +dist3(; name) = System(ss(1 + 10 / s, balance = false); name) @mtkmodel SystemModelWithDisturbanceModel begin @components begin diff --git a/test/dq_units.jl b/test/dq_units.jl index 3c59c479c1..267e993971 100644 --- a/test/dq_units.jl +++ b/test/dq_units.jl @@ -17,63 +17,63 @@ using ModelingToolkit: t, D eqs = [D(E) ~ P - E / τ 0 ~ P] @test MT.validate(eqs) -@named sys = ODESystem(eqs, t) +@named sys = System(eqs, t) @test !MT.validate(D(D(E)) ~ P) @test !MT.validate(0 ~ P + E * τ) # Disabling unit validation/checks selectively -@test_throws MT.ArgumentError ODESystem(eqs, t, [E, P, t], [τ], name = :sys) -ODESystem(eqs, t, [E, P, t], [τ], name = :sys, checks = MT.CheckUnits) +@test_throws MT.ArgumentError System(eqs, t, [E, P, t], [τ], name = :sys) +System(eqs, t, [E, P, t], [τ], name = :sys, checks = MT.CheckUnits) eqs = [D(E) ~ P - E / τ 0 ~ P + E * τ] -@test_throws MT.ValidationError ODESystem(eqs, t, name = :sys, checks = MT.CheckAll) -@test_throws MT.ValidationError ODESystem(eqs, t, name = :sys, checks = true) -ODESystem(eqs, t, name = :sys, checks = MT.CheckNone) -ODESystem(eqs, t, name = :sys, checks = false) -@test_throws MT.ValidationError ODESystem(eqs, t, name = :sys, +@test_throws MT.ValidationError System(eqs, t, name = :sys, checks = MT.CheckAll) +@test_throws MT.ValidationError System(eqs, t, name = :sys, checks = true) +System(eqs, t, name = :sys, checks = MT.CheckNone) +System(eqs, t, name = :sys, checks = false) +@test_throws MT.ValidationError System(eqs, t, name = :sys, checks = MT.CheckComponents | MT.CheckUnits) -@named sys = ODESystem(eqs, t, checks = MT.CheckComponents) -@test_throws MT.ValidationError ODESystem(eqs, t, [E, P, t], [τ], name = :sys, +@named sys = System(eqs, t, checks = MT.CheckComponents) +@test_throws MT.ValidationError System(eqs, t, [E, P, t], [τ], name = :sys, checks = MT.CheckUnits) # connection validation @connector function Pin(; name) sts = @variables(v(t)=1.0, [unit = u"V"], i(t)=1.0, [unit = u"A", connect = Flow]) - ODESystem(Equation[], t, sts, []; name = name) + System(Equation[], t, sts, []; name = name) end @connector function OtherPin(; name) sts = @variables(v(t)=1.0, [unit = u"mV"], i(t)=1.0, [unit = u"mA", connect = Flow]) - ODESystem(Equation[], t, sts, []; name = name) + System(Equation[], t, sts, []; name = name) end @connector function LongPin(; name) sts = @variables(v(t)=1.0, [unit = u"V"], i(t)=1.0, [unit = u"A", connect = Flow], x(t)=1.0) - ODESystem(Equation[], t, sts, []; name = name) + System(Equation[], t, sts, []; name = name) end @named p1 = Pin() @named p2 = Pin() @named lp = LongPin() good_eqs = [connect(p1, p2)] @test MT.validate(good_eqs) -@named sys = ODESystem(good_eqs, t, [], []) +@named sys = System(good_eqs, t, [], []) @named op = OtherPin() bad_eqs = [connect(p1, op)] @test !MT.validate(bad_eqs) -@test_throws MT.ValidationError @named sys = ODESystem(bad_eqs, t, [], []) +@test_throws MT.ValidationError @named sys = System(bad_eqs, t, [], []) @named op2 = OtherPin() good_eqs = [connect(op, op2)] @test MT.validate(good_eqs) -@named sys = ODESystem(good_eqs, t, [], []) +@named sys = System(good_eqs, t, [], []) # Array variables @variables x(t)[1:3] [unit = u"m"] @parameters v[1:3]=[1, 2, 3] [unit = u"m/s"] eqs = D.(x) .~ v -ODESystem(eqs, t, name = :sys) +System(eqs, t, name = :sys) # Nonlinear system @parameters a [unit = u"kg"^-1] @@ -112,12 +112,12 @@ noiseeqs = [0.1us"W" 0.1us"W" @parameters v [unit = u"m/s"] r [unit = u"m"^3 / u"s"] eqs = [D(L) ~ v, V ~ L^3] -@named sys = ODESystem(eqs, t) +@named sys = System(eqs, t) sys_simple = structural_simplify(sys) eqs = [D(V) ~ r, V ~ L^3] -@named sys = ODESystem(eqs, t) +@named sys = System(eqs, t) sys_simple = structural_simplify(sys) @variables V [unit = u"m"^3] L [unit = u"m"] diff --git a/test/equation_type_accessors.jl b/test/equation_type_accessors.jl index f118784f44..1bf92743ac 100644 --- a/test/equation_type_accessors.jl +++ b/test/equation_type_accessors.jl @@ -44,9 +44,9 @@ eqs2 = [X + Y + c ~ b * X^(X + Z + a) eqs3 = [D(X) ~ sqrt(X + b) + sqrt(Z + c) 2Z * (Z + Y) ~ D(Y) * log(a) D(Z) + c * X ~ b / (X + Y^d) + D(Z)] -@named osys1 = ODESystem(eqs1, t) -@named osys2 = ODESystem(eqs2, t) -@named osys3 = ODESystem(eqs3, t) +@named osys1 = System(eqs1, t) +@named osys2 = System(eqs2, t) +@named osys3 = System(eqs3, t) # Test `has...` for non-composed systems. @test has_alg_equations(osys1) diff --git a/test/error_handling.jl b/test/error_handling.jl index 59aa6b79a1..e90f909069 100644 --- a/test/error_handling.jl +++ b/test/error_handling.jl @@ -13,7 +13,7 @@ function UnderdefinedConstantVoltage(; name, V = 1.0) V ~ p.v - n.v # Remove equation # 0 ~ p.i + n.i ] - ODESystem(eqs, t, [], [V], systems = [p, n], defaults = Dict(V => val), name = name) + System(eqs, t, [], [V], systems = [p, n], defaults = Dict(V => val), name = name) end function OverdefinedConstantVoltage(; name, V = 1.0, I = 1.0) @@ -26,7 +26,7 @@ function OverdefinedConstantVoltage(; name, V = 1.0, I = 1.0) # Overdefine p.i and n.i n.i ~ I p.i ~ I] - ODESystem(eqs, t, [], [V], systems = [p, n], defaults = Dict(V => val, I => val2), + System(eqs, t, [], [V], systems = [p, n], defaults = Dict(V => val, I => val2), name = name) end @@ -41,7 +41,7 @@ rc_eqs = [connect(source.p, resistor.p) connect(resistor.n, capacitor.p) connect(capacitor.n, source.n)] -@named rc_model = ODESystem(rc_eqs, t, systems = [resistor, capacitor, source]) +@named rc_model = System(rc_eqs, t, systems = [resistor, capacitor, source]) @test_throws ModelingToolkit.ExtraVariablesSystemException structural_simplify(rc_model) @named source2 = OverdefinedConstantVoltage(V = V, I = V / R) @@ -49,5 +49,5 @@ rc_eqs2 = [connect(source2.p, resistor.p) connect(resistor.n, capacitor.p) connect(capacitor.n, source2.n)] -@named rc_model2 = ODESystem(rc_eqs2, t, systems = [resistor, capacitor, source2]) +@named rc_model2 = System(rc_eqs2, t, systems = [resistor, capacitor, source2]) @test_throws ModelingToolkit.ExtraEquationsSystemException structural_simplify(rc_model2) diff --git a/test/extensions/ad.jl b/test/extensions/ad.jl index adaf6117c6..e7efa40f74 100644 --- a/test/extensions/ad.jl +++ b/test/extensions/ad.jl @@ -20,7 +20,7 @@ u0 = [x => zeros(3), ps = [p => zeros(3, 3), q => 1.0] tspan = (0.0, 10.0) -@mtkbuild sys = ODESystem(eqs, t) +@mtkbuild sys = System(eqs, t) prob = ODEProblem(sys, u0, tspan, ps) sol = solve(prob, Tsit5()) @@ -36,7 +36,7 @@ end @testset "Issue#2997" begin pars = @parameters y0 mh Tγ0 Th0 h ργ0 vars = @variables x(t) - @named sys = ODESystem([D(x) ~ y0], + @named sys = System([D(x) ~ y0], t, vars, pars; @@ -57,7 +57,7 @@ end end @parameters a b[1:3] c(t) d::Integer e[1:3] f[1:3, 1:3]::Int g::Vector{AbstractFloat} h::String -@named sys = ODESystem( +@named sys = System( Equation[], t, [], [a, b, c, d, e, f, g, h], continuous_events = [[a ~ 0] => [c ~ 0]]) sys = complete(sys) @@ -110,7 +110,7 @@ fwd, back = ChainRulesCore.rrule(remake_buffer, sys, ps, idxs, vals) @variables y(t) eqs = [D(D(y)) ~ -9.81] initialization_eqs = [y^2 ~ 0] # initialize y = 0 in a way that builds an initialization problem - @named sys = ODESystem(eqs, t; initialization_eqs) + @named sys = System(eqs, t; initialization_eqs) sys = structural_simplify(sys) # Find initial throw velocity that reaches exactly 10 m after 1 s @@ -127,7 +127,7 @@ end @testset "`sys.var` is non-differentiable" begin @variables x(t) - @mtkbuild sys = ODESystem(D(x) ~ x, t) + @mtkbuild sys = System(D(x) ~ x, t) prob = ODEProblem(sys, [x => 1.0], (0.0, 1.0)) grad = Zygote.gradient(prob) do prob diff --git a/test/extensions/bifurcationkit.jl b/test/extensions/bifurcationkit.jl index 629edf46a6..227cd175e0 100644 --- a/test/extensions/bifurcationkit.jl +++ b/test/extensions/bifurcationkit.jl @@ -60,7 +60,7 @@ let @variables x(t) y(t) z(t) eqs = [D(x) ~ -x + a * y + x^2 * y, D(y) ~ b - a * y - x^2 * y] - @named sys = ODESystem(eqs, t) + @named sys = System(eqs, t) sys = complete(sys) # Creates BifurcationProblem bprob = BifurcationProblem(sys, diff --git a/test/fmi/fmi.jl b/test/fmi/fmi.jl index 98c93398ff..2e503c26e1 100644 --- a/test/fmi/fmi.jl +++ b/test/fmi/fmi.jl @@ -34,7 +34,7 @@ const FMU_DIR = joinpath(@__DIR__, "fmus") @named inner = MTK.FMIComponent( Val(2); fmu, communication_step_size = 1e-5, type = :CS) @variables x(t) = 1.0 - @mtkbuild sys = ODESystem([D(x) ~ x], t; systems = [inner]) + @mtkbuild sys = System([D(x) ~ x], t; systems = [inner]) test_no_inputs_outputs(sys) prob = ODEProblem{true, SciMLBase.FullSpecialize}( @@ -68,7 +68,7 @@ const FMU_DIR = joinpath(@__DIR__, "fmus") @named inner = MTK.FMIComponent( Val(3); fmu, communication_step_size = 1e-5, type = :CS) @variables x(t) = 1.0 - @mtkbuild sys = ODESystem([D(x) ~ x], t; systems = [inner]) + @mtkbuild sys = System([D(x) ~ x], t; systems = [inner]) test_no_inputs_outputs(sys) prob = ODEProblem{true, SciMLBase.FullSpecialize}( @@ -120,7 +120,7 @@ end @testset "IO Model" begin function build_simple_adder(adder) @variables a(t) b(t) c(t) [guess = 1.0] - @mtkbuild sys = ODESystem( + @mtkbuild sys = System( [adder.a ~ a, adder.b ~ b, D(a) ~ t, D(b) ~ adder.out + adder.c, c^2 ~ adder.out + adder.value], t; @@ -176,7 +176,7 @@ end function build_sspace_model(sspace) @variables u(t)=1.0 x(t)=1.0 y(t) [guess = 1.0] - @mtkbuild sys = ODESystem( + @mtkbuild sys = System( [sspace.u ~ u, D(u) ~ t, D(x) ~ sspace.x + sspace.y, y^2 ~ sspace.y + sspace.x], t; systems = [sspace] ) @@ -229,7 +229,7 @@ end @testset "FMUs in a loop" begin function build_looped_adders(adder1, adder2) @variables x(t) = 1 - @mtkbuild sys = ODESystem( + @mtkbuild sys = System( [D(x) ~ x, adder1.a ~ adder2.out2, adder2.a ~ adder1.out2, adder1.b ~ 1.0, adder2.b ~ 2.0], t; @@ -274,7 +274,7 @@ end function build_looped_sspace(sspace1, sspace2) @variables x(t) = 1 - @mtkbuild sys = ODESystem([D(x) ~ x, sspace1.u ~ sspace2.x, sspace2.u ~ sspace1.y], + @mtkbuild sys = System([D(x) ~ x, sspace1.u ~ sspace2.x, sspace2.u ~ sspace1.y], t; systems = [sspace1, sspace2]) prob = ODEProblem(sys, [sspace1.x => 1.0, sspace2.x => 1.0], (0.0, 1.0)) return sys, prob diff --git a/test/funcaffect.jl b/test/funcaffect.jl index 3004044d61..77ce327230 100644 --- a/test/funcaffect.jl +++ b/test/funcaffect.jl @@ -8,7 +8,7 @@ eqs = [D(u) ~ -u] affect1!(integ, u, p, ctx) = integ.u[u.u] += 10 -@named sys = ODESystem(eqs, t, [u], [], +@named sys = System(eqs, t, [u], [], discrete_events = [[4.0] => (affect1!, [u], [], [], nothing)]) prob = ODEProblem(complete(sys), [u => 10.0], (0, 10.0)) sol = solve(prob, Tsit5()) @@ -36,7 +36,7 @@ cb1 = ModelingToolkit.SymbolicContinuousCallback([t ~ zr], (affect1!, [], [], [] @test hash(cb) == hash(cb1) # named tuple -sys1 = ODESystem(eqs, t, [u], [], name = :sys, +sys1 = System(eqs, t, [u], [], name = :sys, discrete_events = [ [4.0] => (f = affect1!, sts = [u], pars = [], discretes = [], ctx = nothing) ]) @@ -49,7 +49,7 @@ de = de[1] @test ModelingToolkit.condition(de) == [4.0] @test ModelingToolkit.has_functional_affect(de) -sys2 = ODESystem(eqs, t, [u], [], name = :sys, +sys2 = System(eqs, t, [u], [], name = :sys, discrete_events = [[4.0] => [u ~ -u * h]]) @test !ModelingToolkit.has_functional_affect(ModelingToolkit.get_discrete_events(sys2)[1]) @@ -59,7 +59,7 @@ function affect2!(integ, u, p, ctx) ctx[1] *= 2 end ctx1 = [10.0] -@named sys = ODESystem(eqs, t, [u], [], +@named sys = System(eqs, t, [u], [], discrete_events = [[4.0, 8.0] => (affect2!, [u], [], [], ctx1)]) prob = ODEProblem(complete(sys), [u => 10.0], (0, 10.0)) sol = solve(prob, Tsit5()) @@ -76,7 +76,7 @@ function affect3!(integ, u, p, ctx) end @parameters a = 10.0 -@named sys = ODESystem(eqs, t, [u], [a], +@named sys = System(eqs, t, [u], [a], discrete_events = [[4.0, 8.0] => (affect3!, [u], [a], [a], nothing)]) prob = ODEProblem(complete(sys), [u => 10.0], (0, 10.0)) @@ -92,7 +92,7 @@ function affect3!(integ, u, p, ctx) integ.ps[p.b] *= 2 end -@named sys = ODESystem(eqs, t, [u], [a], +@named sys = System(eqs, t, [u], [a], discrete_events = [ [4.0, 8.0] => (affect3!, [u], [a => :b], [a], nothing) ]) @@ -106,18 +106,18 @@ i8 = findfirst(==(8.0), sol[:t]) # same name @variables v(t) -@test_throws ErrorException ODESystem(eqs, t, [u], [a], +@test_throws ErrorException System(eqs, t, [u], [a], discrete_events = [ [4.0, 8.0] => (affect3!, [u, v => :u], [a], [a], nothing) ]; name = :sys) -@test_nowarn ODESystem(eqs, t, [u], [a], +@test_nowarn System(eqs, t, [u], [a], discrete_events = [ [4.0, 8.0] => (affect3!, [u], [a => :u], [a], nothing) ]; name = :sys) -@named resistor = ODESystem(D(v) ~ v, t, [v], []) +@named resistor = System(D(v) ~ v, t, [v], []) # nested namespace ctx = [0] @@ -126,7 +126,7 @@ function affect4!(integ, u, p, ctx) @test u.resistor₊v == 1 end s1 = compose( - ODESystem(Equation[], t, [], [], name = :s1, + System(Equation[], t, [], [], name = :s1, discrete_events = 1.0 => (affect4!, [resistor.v], [], [], ctx)), resistor) s2 = structural_simplify(s1) @@ -141,7 +141,7 @@ function affect5!(integ, u, p, ctx) integ.ps[p.C] *= 200 end -@named rc_model = ODESystem(rc_eqs, t, +@named rc_model = System(rc_eqs, t, continuous_events = [ [capacitor.v ~ 0.3] => (affect5!, [capacitor.v], [capacitor.C => :C], [capacitor.C], nothing) @@ -172,7 +172,7 @@ function Capacitor2(; name, C = 1.0) D(v) ~ i / C ] extend( - ODESystem(eqs, t, [], ps; name = name, + System(eqs, t, [], ps; name = name, continuous_events = [[v ~ 0.3] => (affect6!, [v], [C], [C], nothing)]), oneport) end @@ -184,7 +184,7 @@ rc_eqs2 = [connect(source.p, resistor.p) connect(capacitor2.n, source.n) connect(capacitor2.n, ground.g)] -@named rc_model2 = ODESystem(rc_eqs2, t) +@named rc_model2 = System(rc_eqs2, t) rc_model2 = compose(rc_model2, [resistor, capacitor2, source, ground]) sys2 = structural_simplify(rc_model2) @@ -212,14 +212,14 @@ function Ball(; name, g = 9.8, anti_gravity_time = 1.0) pars = @parameters g = g sts = @variables x(t), v(t) eqs = [D(x) ~ v, D(v) ~ g] - ODESystem(eqs, t, sts, pars; name = name, + System(eqs, t, sts, pars; name = name, discrete_events = [[anti_gravity_time] => (affect7!, [], [g], [g], a7_ctx)]) end @named ball1 = Ball(anti_gravity_time = 1.0) @named ball2 = Ball(anti_gravity_time = 2.0) -@named balls = ODESystem(Equation[], t) +@named balls = System(Equation[], t) balls = compose(balls, [ball1, ball2]) @test ModelingToolkit.has_discrete_events(balls) @@ -271,7 +271,7 @@ function bb_affect!(integ, u, p, ctx) integ.u[u.v] = -integ.u[u.v] end -@named bb_model = ODESystem(bb_eqs, t, sts, par, +@named bb_model = System(bb_eqs, t, sts, par, continuous_events = [ [y ~ zr] => (bb_affect!, [v], [], [], nothing) ]) diff --git a/test/function_registration.jl b/test/function_registration.jl index a1d9041127..7ab9835433 100644 --- a/test/function_registration.jl +++ b/test/function_registration.jl @@ -17,7 +17,7 @@ end @register_symbolic do_something(a) eq = Dt(u) ~ do_something(x) + MyModule.do_something(x) -@named sys = ODESystem([eq], t, [u], [x]) +@named sys = System([eq], t, [u], [x]) sys = complete(sys) fun = ODEFunction(sys) @@ -40,7 +40,7 @@ end @register_symbolic do_something_2(a) eq = Dt(u) ~ do_something_2(x) + MyNestedModule.do_something_2(x) -@named sys = ODESystem([eq], t, [u], [x]) +@named sys = System([eq], t, [u], [x]) sys = complete(sys) fun = ODEFunction(sys) @@ -62,7 +62,7 @@ end @register_symbolic do_something_3(a) eq = Dt(u) ~ do_something_3(x) + (@__MODULE__).do_something_3(x) -@named sys = ODESystem([eq], t, [u], [x]) +@named sys = System([eq], t, [u], [x]) sys = complete(sys) fun = ODEFunction(sys) @@ -99,7 +99,7 @@ function build_ode() @parameters x @variables u(t) eq = Dt(u) ~ do_something_4(x) + (@__MODULE__).do_something_4(x) - @named sys = ODESystem([eq], t, [u], [x]) + @named sys = System([eq], t, [u], [x]) sys = complete(sys) fun = ODEFunction(sys, eval_expression = false) end diff --git a/test/guess_propagation.jl b/test/guess_propagation.jl index 738e930adc..f2131e28cb 100644 --- a/test/guess_propagation.jl +++ b/test/guess_propagation.jl @@ -10,7 +10,7 @@ eqs = [D(x) ~ 1 x ~ y] initialization_eqs = [1 ~ exp(1 + x)] -@named sys = ODESystem(eqs, t; initialization_eqs) +@named sys = System(eqs, t; initialization_eqs) sys = complete(structural_simplify(sys)) tspan = (0.0, 0.2) prob = ODEProblem(sys, [], tspan, []) @@ -27,7 +27,7 @@ eqs = [D(x) ~ 1 x ~ y] initialization_eqs = [1 ~ exp(1 + x)] -@named sys = ODESystem(eqs, t; initialization_eqs) +@named sys = System(eqs, t; initialization_eqs) sys = complete(structural_simplify(sys)) tspan = (0.0, 0.2) prob = ODEProblem(sys, [], tspan, []) @@ -45,7 +45,7 @@ eqs = [D(x) ~ a] initialization_eqs = [1 ~ exp(1 + x)] -@named sys = ODESystem(eqs, t; initialization_eqs) +@named sys = System(eqs, t; initialization_eqs) sys = complete(structural_simplify(sys)) tspan = (0.0, 0.2) @@ -65,7 +65,7 @@ eqs = [D(x) ~ a, initialization_eqs = [1 ~ exp(1 + x)] -@named sys = ODESystem(eqs, t; initialization_eqs) +@named sys = System(eqs, t; initialization_eqs) sys = complete(structural_simplify(sys)) tspan = (0.0, 0.2) @@ -80,7 +80,7 @@ sol = solve(prob.f.initializeprob; show_trace = Val(true)) @parameters x0 @variables x(t) @variables y(t) = x -@mtkbuild sys = ODESystem([x ~ x0, D(y) ~ x], t) +@mtkbuild sys = System([x ~ x0, D(y) ~ x], t) prob = ODEProblem(sys, [], (0.0, 1.0), [x0 => 1.0]) @test prob[x] == 1.0 @test prob[y] == 1.0 @@ -88,7 +88,7 @@ prob = ODEProblem(sys, [], (0.0, 1.0), [x0 => 1.0]) @parameters x0 @variables x(t) @variables y(t) = x0 -@mtkbuild sys = ODESystem([x ~ x0, D(y) ~ x], t) +@mtkbuild sys = System([x ~ x0, D(y) ~ x], t) prob = ODEProblem(sys, [], (0.0, 1.0), [x0 => 1.0]) @test prob[x] == 1.0 @test prob[y] == 1.0 @@ -96,7 +96,7 @@ prob = ODEProblem(sys, [], (0.0, 1.0), [x0 => 1.0]) @parameters x0 @variables x(t) @variables y(t) = x0 -@mtkbuild sys = ODESystem([x ~ y, D(y) ~ x], t) +@mtkbuild sys = System([x ~ y, D(y) ~ x], t) prob = ODEProblem(sys, [], (0.0, 1.0), [x0 => 1.0]) @test prob[x] == 1.0 @test prob[y] == 1.0 @@ -104,7 +104,7 @@ prob = ODEProblem(sys, [], (0.0, 1.0), [x0 => 1.0]) @parameters x0 @variables x(t) = x0 @variables y(t) = x -@mtkbuild sys = ODESystem([x ~ y, D(y) ~ x], t) +@mtkbuild sys = System([x ~ y, D(y) ~ x], t) prob = ODEProblem(sys, [], (0.0, 1.0), [x0 => 1.0]) @test prob[x] == 1.0 @test prob[y] == 1.0 diff --git a/test/hierarchical_initialization_eqs.jl b/test/hierarchical_initialization_eqs.jl index 1e3109a66e..e9ea36947e 100644 --- a/test/hierarchical_initialization_eqs.jl +++ b/test/hierarchical_initialization_eqs.jl @@ -24,7 +24,7 @@ A simple linear resistor model p.i + n.i ~ 0 # Ohm's Law v ~ i * R] - return ODESystem(eqs, t, vars, params; systems, name) + return System(eqs, t, vars, params; systems, name) end @connector Pin begin v(t) @@ -46,7 +46,7 @@ end i ~ p.i p.i + n.i ~ 0 v ~ V] - return ODESystem(eqs, t, vars, params; systems, name) + return System(eqs, t, vars, params; systems, name) end @component function Capacitor(; name, C = 1.0) @@ -68,7 +68,7 @@ end i ~ p.i p.i + n.i ~ 0 C * D(v) ~ i] - return ODESystem(eqs, t, vars, params; systems, name, initialization_eqs) + return System(eqs, t, vars, params; systems, name, initialization_eqs) end @component function Ground(; name) @@ -78,7 +78,7 @@ end eqs = [ g.v ~ 0 ] - return ODESystem(eqs, t, [], []; systems, name) + return System(eqs, t, [], []; systems, name) end @component function Inductor(; name, L = 1.0) @@ -97,7 +97,7 @@ end i ~ p.i p.i + n.i ~ 0 L * D(i) ~ v] - return ODESystem(eqs, t, vars, params; systems, name) + return System(eqs, t, vars, params; systems, name) end """ @@ -118,7 +118,7 @@ HTML as well. eqs = [connect(source.p, inductor.n) connect(inductor.p, resistor.p, capacitor.p) connect(resistor.n, ground.g, capacitor.n, source.n)] - return ODESystem(eqs, t, [], []; systems, name, initialization_eqs) + return System(eqs, t, [], []; systems, name, initialization_eqs) end """Run model RLCModel from 0 to 10""" function simple() diff --git a/test/index_cache.jl b/test/index_cache.jl index 455203d759..3048084d96 100644 --- a/test/index_cache.jl +++ b/test/index_cache.jl @@ -3,9 +3,9 @@ using ModelingToolkit: t_nounits as t # Ensure indexes of array symbolics are cached appropriately @variables x(t)[1:2] -@named sys = ODESystem(Equation[], t, [x], []) +@named sys = System(Equation[], t, [x], []) sys1 = complete(sys) -@named sys = ODESystem(Equation[], t, [x...], []) +@named sys = System(Equation[], t, [x...], []) sys2 = complete(sys) for sys in [sys1, sys2] for (sym, idx) in [(x, 1:2), (x[1], 1), (x[2], 2)] @@ -15,9 +15,9 @@ for sys in [sys1, sys2] end @variables x(t)[1:2, 1:2] -@named sys = ODESystem(Equation[], t, [x], []) +@named sys = System(Equation[], t, [x], []) sys1 = complete(sys) -@named sys = ODESystem(Equation[], t, [x...], []) +@named sys = System(Equation[], t, [x...], []) sys2 = complete(sys) for sys in [sys1, sys2] @test is_variable(sys, x) @@ -32,7 +32,7 @@ end @parameters p1 p2[1:2] p3::String @variables x(t) y(t)[1:2] z(t) -@named sys = ODESystem(Equation[], t, [x, y, z], [p1, p2, p3]) +@named sys = System(Equation[], t, [x, y, z], [p1, p2, p3]) sys = complete(sys) ic = ModelingToolkit.get_index_cache(sys) @@ -46,7 +46,7 @@ ic = ModelingToolkit.get_index_cache(sys) @testset "tunable_parameters is ordered" begin @parameters p q[1:3] r[1:2, 1:2] s [tunable = false] - @named sys = ODESystem(Equation[], t, [], [p, q, r, s]) + @named sys = System(Equation[], t, [], [p, q, r, s]) sys = complete(sys) @test all(splat(isequal), zip(tunable_parameters(sys), parameters(sys)[1:3])) @@ -65,7 +65,7 @@ end @testset "reorder_dimension_by_tunables" begin @parameters p q[1:3] r[1:2, 1:2] s [tunable = false] - @named sys = ODESystem(Equation[], t, [], [p, q, r, s]) + @named sys = System(Equation[], t, [], [p, q, r, s]) src = ones(8) dst = zeros(8) # system must be complete... @@ -108,7 +108,7 @@ end event1 = [1.0, 2, 3] => (update_affect!, [], [p_1], [p_1], nothing) - @named sys = ODESystem([ + @named sys = System([ ModelingToolkit.D_nounits(x) ~ p_1(x) ], ModelingToolkit.t_nounits; diff --git a/test/initial_values.jl b/test/initial_values.jl index 79b6b8e067..d6c194cc33 100644 --- a/test/initial_values.jl +++ b/test/initial_values.jl @@ -6,13 +6,13 @@ using SymbolicIndexingInterface: getu @variables x(t)[1:3]=[1.0, 2.0, 3.0] y(t) z(t)[1:2] -@mtkbuild sys=ODESystem([D(x) ~ t * x], t) simplify=false +@mtkbuild sys=System([D(x) ~ t * x], t) simplify=false @test get_u0(sys, [])[1] == [1.0, 2.0, 3.0] @test get_u0(sys, [x => [2.0, 3.0, 4.0]])[1] == [2.0, 3.0, 4.0] @test get_u0(sys, [x[1] => 2.0, x[2] => 3.0, x[3] => 4.0])[1] == [2.0, 3.0, 4.0] @test get_u0(sys, [2.0, 3.0, 4.0])[1] == [2.0, 3.0, 4.0] -@mtkbuild sys=ODESystem([ +@mtkbuild sys=System([ D(x) ~ 3x, D(y) ~ t, D(z[1]) ~ z[2] + t, @@ -55,7 +55,7 @@ vals = ModelingToolkit.varmap_to_vars(var_vals, desired_values; defaults = defau @parameters k1 k2 Γ[1:1]=X1 + X2 eq = D(X1) ~ -k1 * X1 + k2 * (-X1 + Γ[1]) obs = X2 ~ Γ[1] - X1 -@mtkbuild osys_m = ODESystem([eq], t, [X1], [k1, k2, Γ[1]]; observed = [X2 ~ Γ[1] - X1]) +@mtkbuild osys_m = System([eq], t, [X1], [k1, k2, Γ[1]]; observed = [X2 ~ Γ[1] - X1]) # Creates ODEProblem. u0 = [X1 => 1.0, X2 => 2.0] @@ -76,14 +76,14 @@ target_varmap = Dict(p => ones(3), q => 2ones(3), q[1] => 2.0, q[2] => 2.0, q[3] # Issue#1283 @variables z(t)[1:2, 1:2] eqs = [D(D(z)) ~ ones(2, 2)] -@mtkbuild sys = ODESystem(eqs, t) +@mtkbuild sys = System(eqs, t) @test_nowarn ODEProblem(sys, [z => zeros(2, 2), D(z) => ones(2, 2)], (0.0, 10.0)) # Initialization with defaults involving parameters that are not part of the system # Issue#2817 @parameters A1 A2 B1 B2 @variables x1(t) x2(t) -@mtkbuild sys = ODESystem( +@mtkbuild sys = System( [ x1 ~ B1, x2 ~ B2 @@ -100,7 +100,7 @@ prob = ODEProblem(sys, [], (0.0, 1.0), [A1 => 0.3]) @parameters p = nothing @variables x(t)=nothing y(t) for sys in [ - ODESystem(Equation[], t, [x, y], [p]; defaults = [y => nothing], name = :osys), + System(Equation[], t, [x, y], [p]; defaults = [y => nothing], name = :osys), SDESystem(Equation[], [], t, [x, y], [p]; defaults = [y => nothing], name = :ssys), JumpSystem(Equation[], t, [x, y], [p]; defaults = [y => nothing], name = :jsys), NonlinearSystem(Equation[], [x, y], [p]; defaults = [y => nothing], name = :nsys), @@ -117,14 +117,14 @@ end # Issue#2799 @variables x(t) @parameters p -@mtkbuild sys = ODESystem([D(x) ~ p], t; defaults = [x => t, p => 2t]) +@mtkbuild sys = System([D(x) ~ p], t; defaults = [x => t, p => 2t]) prob = ODEProblem(sys, [], (1.0, 2.0), []) @test prob[x] == 1.0 @test prob.ps[p] == 2.0 @testset "Array of symbolics is unwrapped" begin @variables x(t)[1:2] y(t) - @mtkbuild sys = ODESystem([D(x) ~ x, D(y) ~ t], t; defaults = [x => [y, 3.0]]) + @mtkbuild sys = System([D(x) ~ x, D(y) ~ t], t; defaults = [x => [y, 3.0]]) prob = ODEProblem(sys, [y => 1.0], (0.0, 1.0)) @test eltype(prob.u0) <: Float64 prob = ODEProblem(sys, [x => [y, 4.0], y => 2.0], (0.0, 1.0)) @@ -134,7 +134,7 @@ end @testset "split=false systems with all parameter defaults" begin @variables x(t) = 1.0 @parameters p=1.0 q=2.0 r=3.0 - @mtkbuild sys=ODESystem(D(x) ~ p * x + q * t + r, t) split=false + @mtkbuild sys=System(D(x) ~ p * x + q * t + r, t) split=false prob = @test_nowarn ODEProblem(sys, [], (0.0, 1.0)) @test prob.p isa Vector{Float64} end @@ -146,7 +146,7 @@ end y ~ ifelse(t < c1, 0.0, (-c1 + t)^(c3))] sps = [x, y] ps = [c1, c2, c3] - @mtkbuild osys = ODESystem(eqs, t, sps, ps) + @mtkbuild osys = System(eqs, t, sps, ps) u0map = [x => 1.0] pmap = [c1 => 5.0, c2 => 1.0, c3 => 1.2] oprob = ODEProblem(osys, u0map, (0.0, 10.0), pmap) @@ -154,7 +154,7 @@ end @testset "Cyclic dependency checking and substitution limits" begin @variables x(t) y(t) - @mtkbuild sys = ODESystem( + @mtkbuild sys = System( [D(x) ~ x, D(y) ~ y], t; initialization_eqs = [x ~ 2y + 3, y ~ 2x], guesses = [x => 2y, y => 2x]) @test_warn ["Cycle", "unknowns", "x", "y"] try @@ -165,7 +165,7 @@ end sys, [x => 2y + 1, y => 2x], (0.0, 1.0); build_initializeprob = false) @parameters p q - @mtkbuild sys = ODESystem( + @mtkbuild sys = System( [D(x) ~ x * p, D(y) ~ y * q], t; guesses = [p => 1.0, q => 2.0]) # "unknowns" because they are initialization unknowns @test_warn ["Cycle", "unknowns", "p", "q"] try @@ -180,7 +180,7 @@ end @testset "`add_fallbacks!` checks scalarized array parameters correctly" begin @variables x(t)[1:2] @parameters p[1:2, 1:2] - @mtkbuild sys = ODESystem(D(x) ~ p * x, t) + @mtkbuild sys = System(D(x) ~ p * x, t) # used to throw a `MethodError` complaining about `getindex(::Nothing, ::CartesianIndex{2})` @test_throws ModelingToolkit.MissingParametersError ODEProblem( sys, [x => ones(2)], (0.0, 1.0)) @@ -194,7 +194,7 @@ end y[1] ~ x[3], y[2] ~ x[4] ] - @mtkbuild sys = ODESystem(eqs, t; defaults = [x => vcat(ones(2), y), y => x[1:2] ./ 2]) + @mtkbuild sys = System(eqs, t; defaults = [x => vcat(ones(2), y), y => x[1:2] ./ 2]) prob = ODEProblem(sys, [], (0.0, 1.0)) sol = solve(prob) @test SciMLBase.successful_retcode(sol) @@ -207,7 +207,7 @@ end eqs = [D(D(x)) ~ λ * x D(D(y)) ~ λ * y - g x^2 + y^2 ~ 1] - @mtkbuild pend = ODESystem(eqs, t) + @mtkbuild pend = System(eqs, t) @test_throws ModelingToolkit.MissingGuessError ODEProblem( pend, [x => 1], (0, 1), [g => 1], guesses = [y => λ, λ => y + 1]) @@ -216,7 +216,7 @@ end # Throw multiple if multiple are missing @variables a(t) b(t) c(t) d(t) e(t) eqs = [D(a) ~ b, D(b) ~ c, D(c) ~ d, D(d) ~ e, D(e) ~ 1] - @mtkbuild sys = ODESystem(eqs, t) + @mtkbuild sys = System(eqs, t) @test_throws ["a(t)", "c(t)"] ODEProblem( sys, [e => 2, a => b, b => a + 1, c => d, d => c + 1], (0, 1)) end @@ -228,7 +228,7 @@ end @variables x(t) @parameters (interp::Tspline)(..) - @mtkbuild sys = ODESystem(D(x) ~ interp(t), t) + @mtkbuild sys = System(D(x) ~ interp(t), t) prob = ODEProblem(sys, [x => 0.0], (0.0, 1.0), [interp => spline]) spline2 = LinearInterpolation(ts .^ 2, ts .^ 2) @@ -257,7 +257,7 @@ end @parameters p d @variables X(t) eqs = [D(X) ~ p - d * X] - @mtkbuild osys = ODESystem(eqs, t) + @mtkbuild osys = System(eqs, t) u0 = [X => 1.0f0] ps = [p => 1.0f0, d => 2.0f0] oprob = ODEProblem(osys, u0, (0.0f0, 1.0f0), ps) @@ -269,7 +269,7 @@ end @testset "Array initials and scalar parameters with `split = false`" begin @variables x(t)[1:2] @parameters p - @mtkbuild sys=ODESystem([D(x[1]) ~ x[1], D(x[2]) ~ x[2] + p], t) split=false + @mtkbuild sys=System([D(x[1]) ~ x[1], D(x[2]) ~ x[2] + p], t) split=false ps = Set(parameters(sys; initial_parameters = true)) @test length(ps) == 5 for i in 1:2 diff --git a/test/initializationsystem.jl b/test/initializationsystem.jl index 5c36fcba3e..f484108983 100644 --- a/test/initializationsystem.jl +++ b/test/initializationsystem.jl @@ -11,7 +11,7 @@ using DynamicQuantities eqs = [D(D(x)) ~ λ * x D(D(y)) ~ λ * y - g x^2 + y^2 ~ 1] -@mtkbuild pend = ODESystem(eqs, t) +@mtkbuild pend = System(eqs, t) initprob = ModelingToolkit.InitializationProblem(pend, 0.0, [], [g => 1]; guesses = [ModelingToolkit.missing_variable_defaults(pend); x => 1; y => 0.2]) @@ -347,7 +347,7 @@ eqs = [D(D(x)) ~ σ * (y - x), D(y) ~ x * (ρ - z) - y, D(z) ~ x * y - β * z] -@mtkbuild sys = ODESystem(eqs, t) +@mtkbuild sys = System(eqs, t) u0 = [D(x) => 2.0, y => 0.0, @@ -374,7 +374,7 @@ function System2(; name) end eqs = [D(dx) ~ ddx 0 ~ ddx + dx + 1] - return ODESystem(eqs, t, vars, []; name) + return System(eqs, t, vars, []; name) end @mtkbuild sys = System2() @@ -396,7 +396,7 @@ function System3(; name) initialization_eqs = [ ddx ~ -2 ] - return ODESystem(eqs, t, vars, []; name, initialization_eqs) + return System(eqs, t, vars, []; name, initialization_eqs) end @mtkbuild sys = System3() @@ -414,7 +414,7 @@ sol = solve(prob, Tsit5()) D(y) ~ x * (ρ - z) - y, D(z) ~ x * y - β * z] - @named sys = ODESystem(eqs, t) + @named sys = System(eqs, t) sys = structural_simplify(sys) u0 = [D(x) => 2.0, @@ -443,7 +443,7 @@ eqs = [D(x) ~ α * x - β * x * y D(y) ~ -γ * y + δ * x * y z ~ x + y] -@named sys = ODESystem(eqs, t) +@named sys = System(eqs, t) simpsys = structural_simplify(sys) tspan = (0.0, 10.0) @@ -471,7 +471,7 @@ sol = solve(prob, Tsit5()) eqs = [D(D(x)) ~ λ * x D(D(y)) ~ λ * y - g x^2 + y^2 ~ 1] -@mtkbuild pend = ODESystem(eqs, t) +@mtkbuild pend = System(eqs, t) prob = ODEProblem(pend, [x => 1], (0.0, 1.5), [g => 1], guesses = [λ => 0, y => 1], initialization_eqs = [y ~ 1]) @@ -483,8 +483,8 @@ sys = structural_simplify(unsimp; fully_determined = false) # Extend two systems with initialization equations and guesses # https://github.com/SciML/ModelingToolkit.jl/issues/2845 @variables x(t) y(t) -@named sysx = ODESystem([D(x) ~ 0], t; initialization_eqs = [x ~ 1]) -@named sysy = ODESystem([D(y) ~ 0], t; initialization_eqs = [y^2 ~ 2], guesses = [y => 1]) +@named sysx = System([D(x) ~ 0], t; initialization_eqs = [x ~ 1]) +@named sysy = System([D(y) ~ 0], t; initialization_eqs = [y^2 ~ 2], guesses = [y => 1]) sys = extend(sysx, sysy) @test length(equations(generate_initializesystem(sys))) == 2 @test length(ModelingToolkit.guesses(sys)) == 1 @@ -492,7 +492,7 @@ sys = extend(sysx, sysy) # https://github.com/SciML/ModelingToolkit.jl/issues/2873 @testset "Error on missing defaults" begin @variables x(t) y(t) - @named sys = ODESystem([x^2 + y^2 ~ 25, D(x) ~ 1], t) + @named sys = System([x^2 + y^2 ~ 25, D(x) ~ 1], t) ssys = structural_simplify(sys) @test_throws ModelingToolkit.MissingVariablesError ODEProblem( ssys, [x => 3], (0, 1), []) # y should have a guess @@ -504,7 +504,7 @@ end # system 1 should solve to x = 1 ics1 = [x => 1] - sys1 = ODESystem([D(x) ~ 0], t; defaults = ics1, name = :sys1) |> structural_simplify + sys1 = System([D(x) ~ 0], t; defaults = ics1, name = :sys1) |> structural_simplify prob1 = ODEProblem(sys1, [], (0.0, 1.0), []) sol1 = solve(prob1, Tsit5()) @test all(sol1[x] .== 1) @@ -512,7 +512,7 @@ end # system 2 should solve to x = y = 2 sys2 = extend( sys1, - ODESystem([D(y) ~ 0], t; initialization_eqs = [y ~ 2], name = :sys2) + System([D(y) ~ 0], t; initialization_eqs = [y ~ 2], name = :sys2) ) |> structural_simplify ics2 = unknowns(sys1) .=> 2 # should be equivalent to "ics2 = [x => 2]" prob2 = ODEProblem(sys2, ics2, (0.0, 1.0), []; fully_determined = true) @@ -523,12 +523,12 @@ end # https://github.com/SciML/ModelingToolkit.jl/issues/3029 @testset "Derivatives in initialization equations" begin @variables x(t) - sys = ODESystem( + sys = System( [D(D(x)) ~ 0], t; initialization_eqs = [x ~ 0, D(x) ~ 1], name = :sys) |> structural_simplify @test_nowarn ODEProblem(sys, [], (0.0, 1.0), []) - sys = ODESystem( + sys = System( [D(D(x)) ~ 0], t; initialization_eqs = [x ~ 0, D(D(x)) ~ 0], name = :sys) |> structural_simplify @test_nowarn ODEProblem(sys, [D(x) => 1.0], (0.0, 1.0), []) @@ -538,7 +538,7 @@ end @testset "Derivatives in initialization guesses" begin for sign in [-1.0, +1.0] @variables x(t) - sys = ODESystem( + sys = System( [D(D(x)) ~ 0], t; initialization_eqs = [D(x)^2 ~ 1, x ~ 0], guesses = [D(x) => sign], name = :sys ) |> structural_simplify @@ -555,8 +555,8 @@ eqs_1st_order = [D(Y) + Y - ω ~ 0, X + k1 ~ Y + k2] eqs_2nd_order = [D(D(Y)) + 2ω * D(Y) + (ω^2) * Y ~ 0, X + k1 ~ Y + k2] -@mtkbuild sys_1st_order = ODESystem(eqs_1st_order, t) -@mtkbuild sys_2nd_order = ODESystem(eqs_2nd_order, t) +@mtkbuild sys_1st_order = System(eqs_1st_order, t) +@mtkbuild sys_2nd_order = System(eqs_2nd_order, t) u0_1st_order_1 = [X => 1.0, Y => 2.0] u0_1st_order_2 = [Y => 2.0] @@ -581,7 +581,7 @@ sol = solve(oprob_2nd_order_2, Rosenbrock23()) # retcode: Success @testset "Vector in initial conditions" begin @variables x(t)[1:5] y(t)[1:5] - @named sys = ODESystem([D(x) ~ x, D(y) ~ y], t; initialization_eqs = [y ~ -x]) + @named sys = System([D(x) ~ x, D(y) ~ y], t; initialization_eqs = [y ~ -x]) sys = structural_simplify(sys) prob = ODEProblem(sys, [sys.x => ones(5)], (0.0, 1.0), []) sol = solve(prob, Tsit5(), reltol = 1e-4) @@ -723,7 +723,7 @@ end @testset "Null system" begin @variables x(t) y(t) s(t) @parameters x0 y0 - @mtkbuild sys = ODESystem([x ~ x0, y ~ y0, s ~ x + y], t; guesses = [y0 => 0.0]) + @mtkbuild sys = System([x ~ x0, y ~ y0, s ~ x + y], t; guesses = [y0 => 0.0]) prob = ODEProblem(sys, [s => 1.0], (0.0, 1.0), [x0 => 0.3, y0 => missing]) # trivial initialization run immediately @test prob.ps[y0] ≈ 0.7 @@ -743,7 +743,7 @@ end @named gravity = Force() @named constant = Constant(; k = 9.81) @named damper = TM.Damper(; d = 0.1) - @mtkbuild sys = ODESystem( + @mtkbuild sys = System( [connect(fixed.flange, spring.flange_a), connect(spring.flange_b, mass.flange_a), connect(mass.flange_a, gravity.flange), connect(constant.output, gravity.f), connect(fixed.flange, damper.flange_a), connect(damper.flange_b, mass.flange_a)], @@ -1035,7 +1035,7 @@ end @testset "Nonnumeric parameter dependencies are retained" begin @variables x(t) y(t) @parameters foo(::Real, ::Real) p - @mtkbuild sys = ODESystem([D(x) ~ t, 0 ~ foo(x, y)], t; + @mtkbuild sys = System([D(x) ~ t, 0 ~ foo(x, y)], t; parameter_dependencies = [foo ~ Multiplier(p, 2p)], guesses = [y => -1.0]) prob = ODEProblem(sys, [x => 1.0], (0.0, 1.0), [p => 1.0]) integ = init(prob, Rosenbrock23()) @@ -1044,7 +1044,7 @@ end @testset "Use observed equations for guesses of observed variables" begin @variables x(t) y(t) [state_priority = 100] - @mtkbuild sys = ODESystem( + @mtkbuild sys = System( [D(x) ~ x + t, y ~ 2x + 1], t; initialization_eqs = [x^3 + y^3 ~ 1]) isys = ModelingToolkit.generate_initializesystem(sys) @test isequal(defaults(isys)[y], 2x + 1) @@ -1052,14 +1052,14 @@ end @testset "Create initializeprob when unknown has dependent value" begin @variables x(t) y(t) - @mtkbuild sys = ODESystem([D(x) ~ x, D(y) ~ t * y], t; defaults = [x => 2y]) + @mtkbuild sys = System([D(x) ~ x, D(y) ~ t * y], t; defaults = [x => 2y]) prob = ODEProblem(sys, [y => 1.0], (0.0, 1.0)) @test prob.f.initializeprob !== nothing integ = init(prob) @test integ[x] ≈ 2.0 @variables x(t)[1:2] y(t) - @mtkbuild sys = ODESystem([D(x) ~ x, D(y) ~ t], t; defaults = [x => [y, 3.0]]) + @mtkbuild sys = System([D(x) ~ x, D(y) ~ t], t; defaults = [x => [y, 3.0]]) prob = ODEProblem(sys, [y => 1.0], (0.0, 1.0)) @test prob.f.initializeprob !== nothing integ = init(prob) @@ -1074,7 +1074,7 @@ end eqs = [D(D(x)) ~ λ * x D(D(y)) ~ λ * y - g x^2 + y^2 ~ L] - @mtkbuild pend = ODESystem(eqs, t) + @mtkbuild pend = System(eqs, t) prob = ODEProblem(pend, [x => 1, y => 0], (0.0, 1.5), [g => 1], guesses = ModelingToolkit.missing_variable_defaults(pend)) @@ -1125,7 +1125,7 @@ end connect(L1.n, emf.p) connect(emf.n, source.n, ground.g)] - @named model = ODESystem(connections, t, + @named model = System(connections, t, systems = [ ground, ref, @@ -1158,7 +1158,7 @@ end eqs = [D(D(x)) ~ λ * x D(D(y)) ~ λ * y - g x^2 + y^2 ~ 1] - @mtkbuild pend = ODESystem(eqs, t) + @mtkbuild pend = System(eqs, t) @test_warn ["structurally singular", "initialization", "Guess", "heuristic"] ODEProblem( pend, [x => 1, y => 0], (0.0, 1.5), [g => 1], guesses = [λ => 1]) end @@ -1166,7 +1166,7 @@ end @testset "DAEProblem initialization" begin @variables x(t) [guess = 1.0] y(t) [guess = 1.0] @parameters p=missing [guess = 1.0] q=missing [guess = 1.0] - @mtkbuild sys = ODESystem( + @mtkbuild sys = System( [D(x) ~ p * y + q * t, x^3 + y^3 ~ 5], t; initialization_eqs = [p^2 + q^3 ~ 3]) # FIXME: solve for du0 @@ -1183,7 +1183,7 @@ end @testset "Guesses provided to `ODEProblem` are used in `remake`" begin @variables x(t) y(t)=2x @parameters p q=3x - @mtkbuild sys = ODESystem([D(x) ~ x * p + q, x^3 + y^3 ~ 3], t) + @mtkbuild sys = System([D(x) ~ x * p + q, x^3 + y^3 ~ 3], t) prob = ODEProblem( sys, [], (0.0, 1.0), [p => 1.0]; guesses = [x => 1.0, y => 1.0, q => 1.0]) @test prob[x] == 0.0 @@ -1213,7 +1213,7 @@ end @testset "Remake problem with no initializeprob" begin @variables x(t) [guess = 1.0] y(t) [guess = 1.0] @parameters p [guess = 1.0] q [guess = 1.0] - @mtkbuild sys = ODESystem( + @mtkbuild sys = System( [D(x) ~ p * x + q * y, y ~ 2x], t; parameter_dependencies = [q ~ 2p]) prob = ODEProblem(sys, [x => 1.0], (0.0, 1.0), [p => 1.0]) test_dummy_initialization_equation(prob, x) @@ -1234,7 +1234,7 @@ end @testset "Variables provided as symbols" begin @variables x(t) [guess = 1.0] y(t) [guess = 1.0] @parameters p [guess = 1.0] q [guess = 1.0] - @mtkbuild sys = ODESystem( + @mtkbuild sys = System( [D(x) ~ p * x + q * y, y ~ 2x], t; parameter_dependencies = [q ~ 2p]) prob = ODEProblem(sys, [:x => 1.0], (0.0, 1.0), [p => 1.0]) test_dummy_initialization_equation(prob, x) @@ -1248,7 +1248,7 @@ end @testset "Issue#3246: type promotion with parameter dependent initialization_eqs" begin @variables x(t)=1 y(t)=1 @parameters a = 1 - @named sys = ODESystem([D(x) ~ 0, D(y) ~ x + a], t; initialization_eqs = [y ~ a]) + @named sys = System([D(x) ~ 0, D(y) ~ x + a], t; initialization_eqs = [y ~ a]) ssys = structural_simplify(sys) prob = ODEProblem(ssys, [], (0, 1), []) @@ -1269,7 +1269,7 @@ end D(X) ~ p - d * X, D(Y) ~ p - d * Y ] - @mtkbuild osys = ODESystem(eqs, t) + @mtkbuild osys = System(eqs, t) # Make problem. u0_vals = [X => 4, Y => 5.0] @@ -1315,7 +1315,7 @@ end @testset "Solvable array parameters with scalarized guesses" begin @variables x(t) @parameters p[1:2] q - @mtkbuild sys = ODESystem( + @mtkbuild sys = System( D(x) ~ p[1] + p[2] + q, t; defaults = [p[1] => q, p[2] => 2q], guesses = [p[1] => q, p[2] => 2q]) @test ModelingToolkit.is_parameter_solvable(p, Dict(), defaults(sys), guesses(sys)) @@ -1329,7 +1329,7 @@ end @testset "Issue#3318: Mutating `Initial` parameters works" begin @variables x(t) y(t)[1:2] [guess = ones(2)] @parameters p[1:2, 1:2] - @mtkbuild sys = ODESystem( + @mtkbuild sys = System( [D(x) ~ x, D(y) ~ p * y], t; initialization_eqs = [x^2 + y[1]^2 + y[2]^2 ~ 4]) prob = ODEProblem(sys, [x => 1.0, y[1] => 1], (0.0, 1.0), [p => 2ones(2, 2)]) integ = init(prob, Tsit5()) @@ -1348,8 +1348,8 @@ end @testset "Issue#3342" begin @variables x(t) y(t) stop!(integrator, _, _, _) = terminate!(integrator) - @named sys = ODESystem([D(x) ~ 1.0 - D(y) ~ 1.0], t; initialization_eqs = [ + @named sys = System([D(x) ~ 1.0 + D(y) ~ 1.0], t; initialization_eqs = [ y ~ 0.0 ], continuous_events = [ @@ -1370,7 +1370,7 @@ end @testset "Issue#3330: Initialization for unsimplified systems" begin @variables x(t) [guess = 1.0] - @mtkbuild sys = ODESystem(D(x) ~ x, t; initialization_eqs = [x^2 ~ 4]) + @mtkbuild sys = System(D(x) ~ x, t; initialization_eqs = [x^2 ~ 4]) prob = ODEProblem(sys, [], (0.0, 1.0)) @test prob.f.initialization_data !== nothing end @@ -1378,7 +1378,7 @@ end @testset "`ReconstructInitializeprob` with `nothing` state" begin @parameters p @variables x(t) - @mtkbuild sys = ODESystem(x ~ p * t, t) + @mtkbuild sys = System(x ~ p * t, t) prob = @test_nowarn ODEProblem(sys, [], (0.0, 1.0), [p => 1.0]) @test_nowarn remake(prob, p = [p => 1.0]) @test_nowarn remake(prob, p = [p => ForwardDiff.Dual(1.0)]) @@ -1392,7 +1392,7 @@ end D(X1) ~ k1 * (Γ[1] - X1) - k2 * X1 ] obs = [X2 ~ Γ[1] - X1] - @mtkbuild osys = ODESystem(eqs, t, [X1, X2], [k1, k2, Γ]; observed = obs) + @mtkbuild osys = System(eqs, t, [X1, X2], [k1, k2, Γ]; observed = obs) u0 = [X1 => 1.0, X2 => 2.0] ps = [k1 => 0.1, k2 => 0.2] @@ -1490,7 +1490,7 @@ end @testset "Issue#3504: Update initials when `remake` called with non-symbolic `u0`" begin @variables x(t) y(t) @parameters c1 c2 - @mtkbuild sys = ODESystem([D(x) ~ -c1 * x + c2 * y, D(y) ~ c1 * x - c2 * y], t) + @mtkbuild sys = System([D(x) ~ -c1 * x + c2 * y, D(y) ~ c1 * x - c2 * y], t) prob1 = ODEProblem(sys, [1.0, 2.0], (0.0, 1.0), [c1 => 1.0, c2 => 2.0]) prob2 = remake(prob1, u0 = [2.0, 3.0]) prob3 = remake(prob1, u0 = [2.0, 3.0], p = [c1 => 2.0]) diff --git a/test/input_output_handling.jl b/test/input_output_handling.jl index 693a00b9ad..54ff390267 100644 --- a/test/input_output_handling.jl +++ b/test/input_output_handling.jl @@ -6,7 +6,7 @@ using ModelingToolkit: t_nounits as t, D_nounits as D @variables xx(t) some_input(t) [input = true] eqs = [D(xx) ~ some_input] -@named model = ODESystem(eqs, t) +@named model = System(eqs, t) @test_throws ExtraVariablesSystemException structural_simplify(model, ((), ())) if VERSION >= v"1.8" err = "In particular, the unset input(s) are:\n some_input(t)" @@ -17,14 +17,14 @@ end @variables x(t) u(t) [input = true] v(t)[1:2] [input = true] @test isinput(u) -@named sys = ODESystem([D(x) ~ -x + u], t) # both u and x are unbound -@named sys1 = ODESystem([D(x) ~ -x + v[1] + v[2]], t) # both v and x are unbound -@named sys2 = ODESystem([D(x) ~ -sys.x], t, systems = [sys]) # this binds sys.x in the context of sys2, sys2.x is still unbound -@named sys21 = ODESystem([D(x) ~ -sys1.x], t, systems = [sys1]) # this binds sys.x in the context of sys2, sys2.x is still unbound -@named sys3 = ODESystem([D(x) ~ -sys.x + sys.u], t, systems = [sys]) # This binds both sys.x and sys.u -@named sys31 = ODESystem([D(x) ~ -sys1.x + sys1.v[1]], t, systems = [sys1]) # This binds both sys.x and sys1.v[1] +@named sys = System([D(x) ~ -x + u], t) # both u and x are unbound +@named sys1 = System([D(x) ~ -x + v[1] + v[2]], t) # both v and x are unbound +@named sys2 = System([D(x) ~ -sys.x], t, systems = [sys]) # this binds sys.x in the context of sys2, sys2.x is still unbound +@named sys21 = System([D(x) ~ -sys1.x], t, systems = [sys1]) # this binds sys.x in the context of sys2, sys2.x is still unbound +@named sys3 = System([D(x) ~ -sys.x + sys.u], t, systems = [sys]) # This binds both sys.x and sys.u +@named sys31 = System([D(x) ~ -sys1.x + sys1.v[1]], t, systems = [sys1]) # This binds both sys.x and sys1.v[1] -@named sys4 = ODESystem([D(x) ~ -sys.x, u ~ sys.u], t, systems = [sys]) # This binds both sys.x and sys3.u, this system is one layer deeper than the previous. u is directly forwarded to sys.u, and in this case sys.u is bound while u is not +@named sys4 = System([D(x) ~ -sys.x, u ~ sys.u], t, systems = [sys]) # This binds both sys.x and sys3.u, this system is one layer deeper than the previous. u is directly forwarded to sys.u, and in this case sys.u is bound while u is not @test has_var(x ~ 1, x) @test has_var(1 ~ x, x) @@ -87,10 +87,10 @@ fsys4 = flatten(sys4) # Test output handling @variables x(t) y(t) [output = true] @test isoutput(y) -@named sys = ODESystem([D(x) ~ -x, y ~ x], t) # both y and x are unbound +@named sys = System([D(x) ~ -x, y ~ x], t) # both y and x are unbound syss = structural_simplify(sys) # This makes y an observed variable -@named sys2 = ODESystem([D(x) ~ -sys.x, y ~ sys.y], t, systems = [sys]) +@named sys2 = System([D(x) ~ -sys.x, y ~ sys.y], t, systems = [sys]) @test !is_bound(sys, y) @test !is_bound(sys, x) @@ -127,7 +127,7 @@ eqs = [connect(torque.flange, inertia1.flange_a) connect(inertia1.flange_b, spring.flange_a, damper.flange_a) connect(inertia2.flange_a, spring.flange_b, damper.flange_b) y ~ inertia2.w + torque.tau.u] -model = ODESystem(eqs, t; systems = [torque, inertia1, inertia2, spring, damper], +model = System(eqs, t; systems = [torque, inertia1, inertia2, spring, damper], name = :name, guesses = [spring.flange_a.phi => 0.0]) model_outputs = [inertia1.w, inertia2.w, inertia1.phi, inertia2.phi] model_inputs = [torque.tau.u] @@ -164,7 +164,7 @@ end D(x) ~ -x + u ] - @named sys = ODESystem(eqs, t) + @named sys = System(eqs, t) f, dvs, ps, io_sys = ModelingToolkit.generate_control_function(sys; simplify, split) @test isequal(dvs[], x) @@ -181,7 +181,7 @@ end D(x) ~ -x + u + d^2 ] - @named sys = ODESystem(eqs, t) + @named sys = System(eqs, t) f, dvs, ps, io_sys = ModelingToolkit.generate_control_function( sys, [u], [d]; simplify, split) @@ -199,7 +199,7 @@ end D(x) ~ -x + u + d^2 ] - @named sys = ODESystem(eqs, t) + @named sys = System(eqs, t) f, dvs, ps, io_sys = ModelingToolkit.generate_control_function( sys, [u], [d]; simplify, split, disturbance_argument = true) @@ -224,25 +224,25 @@ function Mass(; name, m = 1.0, p = 0, v = 0) sts = @variables pos(t)=p vel(t)=v eqs = [D(pos) ~ vel y ~ pos] - ODESystem(eqs, t, [pos, vel, y], ps; name) + System(eqs, t, [pos, vel, y], ps; name) end function MySpring(; name, k = 1e4) ps = @parameters k = k @variables x(t) = 0 # Spring deflection - ODESystem(Equation[], t, [x], ps; name) + System(Equation[], t, [x], ps; name) end function MyDamper(; name, c = 10) ps = @parameters c = c @variables vel(t) = 0 - ODESystem(Equation[], t, [vel], ps; name) + System(Equation[], t, [vel], ps; name) end function SpringDamper(; name, k = false, c = false) spring = MySpring(; name = :spring, k) damper = MyDamper(; name = :damper, c) - compose(ODESystem(Equation[], t; name), + compose(System(Equation[], t; name), spring, damper) end @@ -262,7 +262,7 @@ c = 10 eqs = [connect_sd(sd, mass1, mass2) D(mass1.vel) ~ (sd_force(sd) + u) / mass1.m D(mass2.vel) ~ (-sd_force(sd)) / mass2.m] -@named _model = ODESystem(eqs, t) +@named _model = System(eqs, t) @named model = compose(_model, mass1, mass2, sd); f, dvs, ps, io_sys = ModelingToolkit.generate_control_function(model, simplify = true) @@ -280,7 +280,7 @@ i = findfirst(isequal(u[1]), out) @variables x(t) u(t) [input = true] eqs = [D(x) ~ u] -@named sys = ODESystem(eqs, t) +@named sys = System(eqs, t) @test_nowarn structural_simplify(sys, ([u], [])) #= @@ -312,7 +312,7 @@ function SystemModel(u = nothing; name = :model) connect(inertia2.flange_a, spring.flange_b, damper.flange_b)] if u !== nothing push!(eqs, connect(torque.tau, u.output)) - return @named model = ODESystem(eqs, t; + return @named model = System(eqs, t; systems = [ torque, inertia1, @@ -322,7 +322,7 @@ function SystemModel(u = nothing; name = :model) u ]) end - ODESystem(eqs, t; systems = [torque, inertia1, inertia2, spring, damper], + System(eqs, t; systems = [torque, inertia1, inertia2, spring, damper], name, guesses = [spring.flange_a.phi => 0.0]) end @@ -363,7 +363,7 @@ eqs = [D(y₁) ~ -k₁ * y₁ + k₃ * y₂ * y₃ + u1 D(y₂) ~ k₁ * y₁ - k₃ * y₂ * y₃ - k₂ * y₂^2 + u2 y₁ + y₂ + y₃ ~ 1] -@named sys = ODESystem(eqs, t) +@named sys = System(eqs, t) m_inputs = [u[1], u[2]] m_outputs = [y₂] sys_simp, input_idxs = structural_simplify(sys, (; inputs = m_inputs, outputs = m_outputs)) @@ -375,7 +375,7 @@ sys_simp, input_idxs = structural_simplify(sys, (; inputs = m_inputs, outputs = @named gain = Gain(1;) @named int = Integrator(; k = 1) @named fb = Feedback(;) -@named model = ODESystem( +@named model = System( [ connect(c.output, fb.input1), connect(fb.input2, int.output), @@ -432,7 +432,7 @@ matrices = ModelingToolkit.reorder_unknowns( D(x) ~ -x + u ] - @named sys = ODESystem(eqs, t) + @named sys = System(eqs, t) (; io_sys,) = ModelingToolkit.generate_control_function(sys, simplify = true) obsfn = ModelingToolkit.build_explicit_observed_function( io_sys, [x + u * t]; inputs = [u]) @@ -444,7 +444,7 @@ end @constants c = 2.0 @variables x(t) eqs = [D(x) ~ c * x] - @named sys = ODESystem(eqs, t, [x], []) + @named sys = System(eqs, t, [x], []) f, dvs, ps, io_sys = ModelingToolkit.generate_control_function(sys, simplify = true) @test f[1]([0.5], nothing, MTKParameters(io_sys, []), 0.0) ≈ [1.0] @@ -454,7 +454,7 @@ end @variables x(t)=0 u(t)=0 [input = true] @parameters p(::Real) = (x -> 2x) eqs = [D(x) ~ -x + p(u)] - @named sys = ODESystem(eqs, t) + @named sys = System(eqs, t) f, dvs, ps, io_sys = ModelingToolkit.generate_control_function(sys, simplify = true) p = MTKParameters(io_sys, []) u = [1.0] diff --git a/test/inputoutput.jl b/test/inputoutput.jl index 2a81a0e315..28f112425f 100644 --- a/test/inputoutput.jl +++ b/test/inputoutput.jl @@ -10,12 +10,12 @@ eqs = [D(x) ~ σ * (y - x) + F, D(z) ~ x * y - β * z] aliases = [u ~ x + y - z] -lorenz1 = ODESystem(eqs, pins = [F], observed = aliases, name = :lorenz1) -lorenz2 = ODESystem(eqs, pins = [F], observed = aliases, name = :lorenz2) +lorenz1 = System(eqs, pins = [F], observed = aliases, name = :lorenz1) +lorenz2 = System(eqs, pins = [F], observed = aliases, name = :lorenz2) connections = [lorenz1.F ~ lorenz2.u, lorenz2.F ~ lorenz1.u] -connected = ODESystem(Equation[], t, [], [], observed = connections, +connected = System(Equation[], t, [], [], observed = connections, systems = [lorenz1, lorenz2]) sys = connected diff --git a/test/jacobiansparsity.jl b/test/jacobiansparsity.jl index 1f936bc878..d10ec66c44 100644 --- a/test/jacobiansparsity.jl +++ b/test/jacobiansparsity.jl @@ -91,7 +91,7 @@ prob = ODEProblem(sys, u0, (0, 11.5), sparse = true, jac = true) eqs = [D(D(x)) ~ λ * x D(D(y)) ~ λ * y - g x^2 + y^2 ~ 1] - @mtkbuild pend = ODESystem(eqs, t) + @mtkbuild pend = System(eqs, t) u0 = [x => 1, y => 0] prob = ODEProblem( @@ -125,7 +125,7 @@ end eqs = [D(D(x)) ~ λ * x D(D(y)) ~ λ * y - g x^2 + y^2 ~ 1] - @mtkbuild pend = ODESystem(eqs, t) + @mtkbuild pend = System(eqs, t) prob = ODEProblem(pend, [x => 0.0, D(x) => 1.0], (0.0, 1.0), [g => 1.0]; guesses = [y => 1.0, λ => 1.0], jac = true, sparse = true) J = deepcopy(prob.f.jac_prototype) diff --git a/test/labelledarrays.jl b/test/labelledarrays.jl index c9ee7ee50b..59c269dff7 100644 --- a/test/labelledarrays.jl +++ b/test/labelledarrays.jl @@ -12,7 +12,7 @@ eqs = [D(x) ~ σ * (y - x), D(y) ~ t * x * (ρ - z) - y, D(z) ~ x * y - β * z] -@named de = ODESystem(eqs, t) +@named de = System(eqs, t) de = complete(de) ff = ODEFunction(de, [x, y, z], [σ, ρ, β], jac = true) diff --git a/test/latexify.jl b/test/latexify.jl index 105a17ca6e..de5c195610 100644 --- a/test/latexify.jl +++ b/test/latexify.jl @@ -55,6 +55,6 @@ eqs = [D(x) ~ (1 + cos(t)) / (1 + 2 * x)] ap = AnalysisPoint(:plant_input) eqs = [connect(P.output, C.input) connect(C.output, ap, P.input)] -sys_ap = ODESystem(eqs, t, systems = [P, C], name = :hej) +sys_ap = System(eqs, t, systems = [P, C], name = :hej) @test_reference "latexify/50.tex" latexify(sys_ap) diff --git a/test/linearity.jl b/test/linearity.jl index aed9a256d2..d472cdb087 100644 --- a/test/linearity.jl +++ b/test/linearity.jl @@ -12,16 +12,16 @@ eqs = [D(x) ~ σ * (y - x), D(y) ~ -z - y, D(z) ~ y - β * z] -@test ModelingToolkit.islinear(@named sys = ODESystem(eqs, t)) +@test ModelingToolkit.islinear(@named sys = System(eqs, t)) eqs2 = [D(x) ~ σ * (y - x), D(y) ~ -z - 1 / y, D(z) ~ y - β * z] -@test !ModelingToolkit.islinear(@named sys = ODESystem(eqs2, t)) +@test !ModelingToolkit.islinear(@named sys = System(eqs2, t)) eqs3 = [D(x) ~ σ * (y - x), D(y) ~ -z - y, D(z) ~ y - β * z + 1] -@test ModelingToolkit.isaffine(@named sys = ODESystem(eqs, t)) +@test ModelingToolkit.isaffine(@named sys = System(eqs, t)) diff --git a/test/lowering_solving.jl b/test/lowering_solving.jl index 300d505ab0..8c4db26287 100644 --- a/test/lowering_solving.jl +++ b/test/lowering_solving.jl @@ -8,14 +8,14 @@ eqs = [D(D(x)) ~ σ * (y - x), D(y) ~ x * (ρ - z) - y, D(z) ~ x * y - β * z] -@named sys′ = ODESystem(eqs, t) +@named sys′ = System(eqs, t) sys = ode_order_lowering(sys′) eqs2 = [0 ~ x * y - k, D(D(x)) ~ σ * (y - x), D(y) ~ x * (ρ - z) - y, D(z) ~ x * y - β * z] -@named sys2 = ODESystem(eqs2, t, [x, y, z, k], parameters(sys′)) +@named sys2 = System(eqs2, t, [x, y, z, k], parameters(sys′)) sys2 = ode_order_lowering(sys2) # test equation/variable ordering ModelingToolkit.calculate_massmatrix(sys2) == Diagonal([1, 1, 1, 1, 0]) @@ -46,13 +46,13 @@ eqs = [D(x) ~ σ * (y - x), D(y) ~ x * (ρ - z) - y, D(z) ~ x * y - β * z] -lorenz1 = ODESystem(eqs, t, name = :lorenz1) -lorenz2 = ODESystem(eqs, t, name = :lorenz2) +lorenz1 = System(eqs, t, name = :lorenz1) +lorenz2 = System(eqs, t, name = :lorenz2) @variables α(t) @parameters γ connections = [0 ~ lorenz1.x + lorenz2.y + α * γ] -@named connected = ODESystem(connections, t, [α], [γ], systems = [lorenz1, lorenz2]) +@named connected = System(connections, t, [α], [γ], systems = [lorenz1, lorenz2]) connected = complete(connected) u0 = [lorenz1.x => 1.0, lorenz1.y => 0.0, diff --git a/test/mass_matrix.jl b/test/mass_matrix.jl index 5183b4ab3f..25027c16c0 100644 --- a/test/mass_matrix.jl +++ b/test/mass_matrix.jl @@ -8,9 +8,9 @@ eqs = [D(y[1]) ~ -k[1] * y[1] + k[3] * y[2] * y[3], D(y[2]) ~ k[1] * y[1] - k[3] * y[2] * y[3] - k[2] * y[2]^2, 0 ~ y[1] + y[2] + y[3] - 1] -@named sys = ODESystem(eqs, t, collect(y), [k]) +@named sys = System(eqs, t, collect(y), [k]) sys = complete(sys) -@test_throws ArgumentError ODESystem(eqs, y[1]) +@test_throws ArgumentError System(eqs, y[1]) M = calculate_massmatrix(sys) @test M == [1 0 0 0 1 0 @@ -40,6 +40,6 @@ sol2 = solve(prob_mm2, Rodas5(), reltol = 1e-8, abstol = 1e-8, tstops = sol.t, # Test mass matrix in the identity case eqs = [D(y[1]) ~ y[1], D(y[2]) ~ y[2], D(y[3]) ~ y[3]] -@named sys = ODESystem(eqs, t, collect(y), [k]) +@named sys = System(eqs, t, collect(y), [k]) @test calculate_massmatrix(sys) === I diff --git a/test/modelingtoolkitize.jl b/test/modelingtoolkitize.jl index db99cc91a4..d146fa95c9 100644 --- a/test/modelingtoolkitize.jl +++ b/test/modelingtoolkitize.jl @@ -375,7 +375,7 @@ sys = modelingtoolkitize(prob) @testset "ODE" begin @variables x(t)=1.0 y(t)=2.0 @parameters p=3.0 q=4.0 - @mtkbuild sys = ODESystem([D(x) ~ p * y, D(y) ~ q * x], t) + @mtkbuild sys = System([D(x) ~ p * y, D(y) ~ q * x], t) prob1 = ODEProblem(sys, [], (0.0, 5.0)) newsys = complete(modelingtoolkitize(prob1)) @test is_variable(newsys, newsys.x) diff --git a/test/mtkparameters.jl b/test/mtkparameters.jl index 55de0768e0..fe2449f0ed 100644 --- a/test/mtkparameters.jl +++ b/test/mtkparameters.jl @@ -8,7 +8,7 @@ using ForwardDiff using JET @parameters a b c(t) d::Integer e[1:3] f[1:3, 1:3]::Int g::Vector{AbstractFloat} h::String -@named sys = ODESystem( +@named sys = System( Equation[], t, [], [a, c, d, e, f, g, h], parameter_dependencies = [b ~ 2a], continuous_events = [[a ~ 0] => [c ~ 0]], defaults = Dict(a => 0.0)) sys = complete(sys) @@ -130,7 +130,7 @@ end @parameters p::Vector{Float64} @variables X(t) eq = D(X) ~ p[1] - p[2] * X -@mtkbuild osys = ODESystem([eq], t) +@mtkbuild osys = System([eq], t) u0 = [X => 1.0] ps = [p => [2.0, 0.1]] @@ -139,7 +139,7 @@ p = MTKParameters(osys, ps, u0) # Ensure partial update promotes the buffer @parameters p q r -@named sys = ODESystem(Equation[], t, [], [p, q, r]) +@named sys = System(Equation[], t, [], [p, q, r]) sys = complete(sys) ps = MTKParameters(sys, [p => 1.0, q => 2.0, r => 3.0]) newps = remake_buffer(sys, ps, (p,), (1.0f0,)) @@ -150,7 +150,7 @@ newps = remake_buffer(sys, ps, (p,), (1.0f0,)) @parameters p d @variables X(t) eqs = [D(X) ~ p - d * X] -@mtkbuild sys = ODESystem(eqs, t) +@mtkbuild sys = System(eqs, t) u0 = [X => 1.0] tspan = (0.0, 100.0) @@ -170,7 +170,7 @@ function level1() eqs = [D(x) ~ p1 * x - p2 * x * y D(y) ~ -p3 * y + p4 * x * y] - sys = structural_simplify(complete(ODESystem( + sys = structural_simplify(complete(System( eqs, t, tspan = (0, 3.0), name = :sys, parameter_dependencies = [y0 => 2p4]))) prob = ODEProblem{true, SciMLBase.FullSpecialize}(sys) end @@ -184,7 +184,7 @@ function level2() eqs = [D(x) ~ p1 * x - p23[1] * x * y D(y) ~ -p23[2] * y + p4 * x * y] - sys = structural_simplify(complete(ODESystem( + sys = structural_simplify(complete(System( eqs, t, tspan = (0, 3.0), name = :sys, parameter_dependencies = [y0 => 2p4]))) prob = ODEProblem{true, SciMLBase.FullSpecialize}(sys) end @@ -198,7 +198,7 @@ function level3() eqs = [D(x) ~ p1 * x - p23[1] * x * y D(y) ~ -p23[2] * y + p4 * x * y] - sys = structural_simplify(complete(ODESystem( + sys = structural_simplify(complete(System( eqs, t, tspan = (0, 3.0), name = :sys, parameter_dependencies = [y0 => 2p4]))) prob = ODEProblem{true, SciMLBase.FullSpecialize}(sys) end @@ -238,7 +238,7 @@ end @variables x(t) y(t) eqs = [D(x) ~ (α - β * y) * x D(y) ~ (δ * x - γ) * y] -@mtkbuild odesys = ODESystem(eqs, t) +@mtkbuild odesys = System(eqs, t) odeprob = ODEProblem( odesys, [x => 1.0, y => 1.0], (0.0, 10.0), [α => 1.5, β => 1.0, γ => 3.0, δ => 1.0]) tunables, _... = canonicalize(Tunable(), odeprob.p) @@ -263,7 +263,7 @@ VVDual = Vector{<:Vector{<:ForwardDiff.Dual}} end @parameters a b::Int c::Vector{Float64} d[1:2, 1:2]::Int e::Foo{Int} f::Foo - @named sys = ODESystem(Equation[], t, [], [a, b, c, d, e, f]) + @named sys = System(Equation[], t, [], [a, b, c, d, e, f]) sys = complete(sys) ps = MTKParameters(sys, Dict(a => 1.0, b => 2, c => 3ones(2), @@ -301,7 +301,7 @@ end @testset "Error on missing parameter defaults" begin @parameters a b c - @named sys = ODESystem(Equation[], t, [], [a, b]; defaults = Dict(b => 2c)) + @named sys = System(Equation[], t, [], [a, b]; defaults = Dict(b => 2c)) sys = complete(sys) @test_throws ["Could not evaluate", "b", "Missing", "2c"] MTKParameters(sys, [a => 1.0]) end @@ -313,7 +313,7 @@ end D(V[1]) ~ k[1] - k[2] * V[1], D(V[2]) ~ k[3] - k[4] * V[2] ] - @mtkbuild osys_scal = ODESystem(eqs, t, [V[1], V[2]], [k[1], k[2], k[3], k[4]]) + @mtkbuild osys_scal = System(eqs, t, [V[1], V[2]], [k[1], k[2], k[3], k[4]]) u0 = [V => [10.0, 20.0]] ps_vec = [k => [2.0, 3.0, 4.0, 5.0]] diff --git a/test/namespacing.jl b/test/namespacing.jl index b6305a1776..3871398144 100644 --- a/test/namespacing.jl +++ b/test/namespacing.jl @@ -1,10 +1,10 @@ using ModelingToolkit using ModelingToolkit: t_nounits as t, D_nounits as D, iscomplete, does_namespacing -@testset "ODESystem" begin +@testset "System" begin @variables x(t) @parameters p - sys = ODESystem(D(x) ~ p * x, t; name = :inner) + sys = System(D(x) ~ p * x, t; name = :inner) @test !iscomplete(sys) @test does_namespacing(sys) @@ -23,7 +23,7 @@ using ModelingToolkit: t_nounits as t, D_nounits as D, iscomplete, does_namespac @test isequal(p, nsys.p) @test !isequal(p, sys.p) - @test_throws ["namespacing", "inner"] ODESystem( + @test_throws ["namespacing", "inner"] System( Equation[], t; systems = [nsys], name = :a) end diff --git a/test/nonlinearsystem.jl b/test/nonlinearsystem.jl index a315371141..4aac92ee64 100644 --- a/test/nonlinearsystem.jl +++ b/test/nonlinearsystem.jl @@ -119,14 +119,14 @@ lorenz2 = lorenz(:lorenz2) using OrdinaryDiffEq @independent_variables t D = Differential(t) -@named subsys = convert_system(ODESystem, lorenz1, t) -@named sys = ODESystem([D(subsys.x) ~ subsys.x + subsys.x], t, systems = [subsys]) +@named subsys = convert_system(System, lorenz1, t) +@named sys = System([D(subsys.x) ~ subsys.x + subsys.x], t, systems = [subsys]) sys = structural_simplify(sys) u0 = [subsys.x => 1, subsys.z => 2.0, subsys.y => 1.0] prob = ODEProblem(sys, u0, (0, 1.0), [subsys.σ => 1, subsys.ρ => 2, subsys.β => 3]) sol = solve(prob, FBDF(), reltol = 1e-7, abstol = 1e-7) @test sol[subsys.x] + sol[subsys.y] - sol[subsys.z]≈sol[subsys.u] atol=1e-7 -@test_throws ArgumentError convert_system(ODESystem, sys, t) +@test_throws ArgumentError convert_system(System, sys, t) @parameters σ ρ β @variables x y z @@ -197,7 +197,7 @@ eq = [v1 ~ sin(2pi * t * h) v1 - v2 ~ i1 v2 ~ i2 i1 ~ i2] -@named sys = ODESystem(eq, t) +@named sys = System(eq, t) @test length(equations(structural_simplify(sys))) == 0 @testset "Issue: 1504" begin @@ -393,10 +393,10 @@ end @test is_parameter(sys, p) end -@testset "Can convert from `ODESystem`" begin +@testset "Can convert from `System`" begin @variables x(t) y(t) @parameters p q r - @named sys = ODESystem([D(x) ~ p * x^3 + q, 0 ~ -y + q * x - r], t; + @named sys = System([D(x) ~ p * x^3 + q, 0 ~ -y + q * x - r], t; defaults = [x => 1.0, p => missing], guesses = [p => 1.0], initialization_eqs = [p^3 + q^3 ~ 4r], parameter_dependencies = [r ~ 3p]) nlsys = NonlinearSystem(sys) @@ -435,7 +435,7 @@ end @test sol.ps[p^3 + q^3]≈sol.ps[4r] atol=1e-10 @testset "Differential inside expression also substituted" begin - @named sys = ODESystem([0 ~ y * D(x) + x^2 - p, 0 ~ x * D(y) + y * p], t) + @named sys = System([0 ~ y * D(x) + x^2 - p, 0 ~ x * D(y) + y * p], t) nlsys = NonlinearSystem(sys) vs = ModelingToolkit.vars(equations(nlsys)) @test !in(D(x), vs) diff --git a/test/odesystem.jl b/test/odesystem.jl index f5fef1fd6f..d4c5365c3b 100644 --- a/test/odesystem.jl +++ b/test/odesystem.jl @@ -23,7 +23,7 @@ eqs = [D(x) ~ σ * (y - x), D(z) ~ x * y - β * z * κ] ModelingToolkit.toexpr.(eqs)[1] -@named de = ODESystem(eqs, t; defaults = Dict(x => 1)) +@named de = System(eqs, t; defaults = Dict(x => 1)) subed = substitute(de, [σ => k]) ssort(eqs) = sort(eqs, by = string) @test isequal(ssort(parameters(subed)), [k, β, ρ]) @@ -31,7 +31,7 @@ ssort(eqs) = sort(eqs, by = string) [D(x) ~ k * (y - x) D(y) ~ (ρ - z) * x - y D(z) ~ x * y - β * κ * z]) -@named des[1:3] = ODESystem(eqs, t) +@named des[1:3] = System(eqs, t) @test length(unique(x -> ModelingToolkit.get_tag(x), des)) == 1 @test eval(toexpr(de)) == de @@ -40,7 +40,7 @@ ssort(eqs) = sort(eqs, by = string) generate_function(de) function test_diffeq_inference(name, sys, iv, dvs, ps) - @testset "ODESystem construction: $name" begin + @testset "System construction: $name" begin @test isequal(independent_variables(sys)[1], value(iv)) @test length(independent_variables(sys)) == 1 @test isempty(setdiff(Set(unknowns(sys)), Set(value.(dvs)))) @@ -123,7 +123,7 @@ f = eval(ODEFunctionExpr(de, [x, y, z], [σ, ρ, β], sparsity = false)) eqs = [D(x) ~ σ * (y - x), D(y) ~ x * (ρ - z) - y * t, D(z) ~ x * y - β * z * κ] -@named de = ODESystem(eqs, t) +@named de = System(eqs, t) de = complete(de) ModelingToolkit.calculate_tgrad(de) @@ -140,7 +140,7 @@ tgrad_iip(du, u, p, t) eqs = [D(x) ~ σ(t - 1) * (y - x), D(y) ~ x * (ρ - z) - y, D(z) ~ x * y - β * z * κ] -@named de = ODESystem(eqs, t) +@named de = System(eqs, t) test_diffeq_inference("single internal iv-varying", de, t, (x, y, z), (σ, ρ, β)) f = generate_function(de, [x, y, z], [σ, ρ, β], expression = Val{false})[2] du = [0.0, 0.0, 0.0] @@ -148,7 +148,7 @@ f(du, [1.0, 2.0, 3.0], [x -> x + 7, 2, 3], 5.0) @test du ≈ [11, -3, -7] eqs = [D(x) ~ x + 10σ(t - 1) + 100σ(t - 2) + 1000σ(t^2)] -@named de = ODESystem(eqs, t) +@named de = System(eqs, t) test_diffeq_inference("many internal iv-varying", de, t, (x,), (σ,)) f = generate_function(de, [x], [σ], expression = Val{false})[2] du = [0.0] @@ -161,7 +161,7 @@ D2 = D^2 @variables u(t) uˍtt(t) uˍt(t) xˍt(t) eqs = [D3(u) ~ 2(D2(u)) + D(u) + D(x) + 1 D2(x) ~ D(x) + 2] -@named de = ODESystem(eqs, t) +@named de = System(eqs, t) de1 = ode_order_lowering(de) lowered_eqs = [D(uˍtt) ~ 2uˍtt + uˍt + xˍt + 1 D(xˍt) ~ xˍt + 2 @@ -169,13 +169,13 @@ lowered_eqs = [D(uˍtt) ~ 2uˍtt + uˍt + xˍt + 1 D(u) ~ uˍt D(x) ~ xˍt] -#@test de1 == ODESystem(lowered_eqs) +#@test de1 == System(lowered_eqs) # issue #219 @test all(isequal.( [ModelingToolkit.var_from_nested_derivative(eq.lhs)[1] for eq in equations(de1)], - unknowns(@named lowered = ODESystem(lowered_eqs, t)))) + unknowns(@named lowered = System(lowered_eqs, t)))) test_diffeq_inference("first-order transform", de1, t, [uˍtt, xˍt, uˍt, u, x], []) du = zeros(5) @@ -188,7 +188,7 @@ a = y - x eqs = [D(x) ~ σ * a, D(y) ~ x * (ρ - z) - y, D(z) ~ x * y - β * z * κ] -@named de = ODESystem(eqs, t) +@named de = System(eqs, t) generate_function(de, [x, y, z], [σ, ρ, β]) jac = calculate_jacobian(de) @test ModelingToolkit.jacobian_sparsity(de).colptr == sparse(jac).colptr @@ -200,7 +200,7 @@ f = ODEFunction(complete(de), [x, y, z], [σ, ρ, β]) _x = y / C eqs = [D(x) ~ -A * x, D(y) ~ A * x - B * _x] -@named de = ODESystem(eqs, t) +@named de = System(eqs, t) @test begin local f, du f = generate_function(de, [x, y], [A, B, C], expression = Val{false})[2] @@ -241,7 +241,7 @@ ODEFunction(de)(similar(prob.u0), prob.u0, prob.p, 0.1) eqs = [D(y₁) ~ -k₁ * y₁ + k₃ * y₂ * y₃, 0 ~ y₁ + y₂ + y₃ - 1, D(y₂) ~ k₁ * y₁ - k₂ * y₂^2 - k₃ * y₂ * y₃ * κ] -@named sys = ODESystem(eqs, t, defaults = [k₁ => 100, k₂ => 3e7, y₁ => 1.0]) +@named sys = System(eqs, t, defaults = [k₁ => 100, k₂ => 3e7, y₁ => 1.0]) sys = complete(sys) u0 = Pair[] push!(u0, y₂ => 0.0) @@ -288,14 +288,14 @@ sol_dpmap = solve(prob_dpmap, Rodas5()) function makesys(name) @parameters a = 1.0 @variables x(t) = 0.0 - ODESystem([D(x) ~ -a * x], t; name) + System([D(x) ~ -a * x], t; name) end function makecombinedsys() sys1 = makesys(:sys1) sys2 = makesys(:sys2) @parameters b = 1.0 - complete(ODESystem(Equation[], t, [], [b]; systems = [sys1, sys2], name = :foo)) + complete(System(Equation[], t, [], [b]; systems = [sys1, sys2], name = :foo)) end sys = makecombinedsys() @@ -340,7 +340,7 @@ end eqs = [D(x) ~ σ * (y - x), D(y) ~ x - β * y, x + z ~ y] -@named sys = ODESystem(eqs, t) +@named sys = System(eqs, t) @test all(isequal.(unknowns(sys), [x, y, z])) @test all(isequal.(parameters(sys), [σ, β])) @test equations(sys) == eqs @@ -350,13 +350,13 @@ eqs = [D(x) ~ σ * (y - x), using ModelingToolkit @parameters a @variables x(t) -@named sys = ODESystem([D(x) ~ a], t) +@named sys = System([D(x) ~ a], t) @test issym(equations(sys)[1].rhs) # issue 708 @parameters a @variables x(t) y(t) z(t) -@named sys = ODESystem([D(x) ~ y, 0 ~ x + z, 0 ~ x - y], t, [z, y, x], []) +@named sys = System([D(x) ~ y, 0 ~ x + z, 0 ~ x - y], t, [z, y, x], []) asys = add_accumulations(sys) @variables accumulation_x(t) accumulation_y(t) accumulation_z(t) eqs = [0 ~ x + z @@ -385,16 +385,16 @@ eqs = [ D(x1) ~ -x1, 0 ~ x1 - x2 ] -@named sys = ODESystem(eqs, t) +@named sys = System(eqs, t) @test isequal(ModelingToolkit.get_iv(sys), t) @test isequal(unknowns(sys), [x1, x2]) @test isempty(parameters(sys)) -# one equation ODESystem test +# one equation System test @parameters r @variables x(t) eq = D(x) ~ r * x -@named ode = ODESystem(eq, t) +@named ode = System(eq, t) @test equations(ode) == [eq] # issue #808 @testset "Combined system name collisions" begin @@ -402,14 +402,14 @@ eq = D(x) ~ r * x @parameters a @variables x(t) f(t) - ODESystem([D(x) ~ -a * x + f], t; name) + System([D(x) ~ -a * x + f], t; name) end function issue808() sys1 = makesys(:sys1) sys2 = makesys(:sys1) - @test_throws ArgumentError ODESystem([sys2.f ~ sys1.x, D(sys1.f) ~ 0], t, + @test_throws ArgumentError System([sys2.f ~ sys1.x, D(sys1.f) ~ 0], t, systems = [sys1, sys2], name = :foo) end issue808() @@ -421,19 +421,19 @@ vars = @variables((u1,)) eqs = [ D(u1) ~ 1 ] -@test_throws ArgumentError ODESystem(eqs, t, vars, pars, name = :foo) +@test_throws ArgumentError System(eqs, t, vars, pars, name = :foo) #Issue 1063/998 pars = [t] vars = @variables((u1(t),)) -@test_throws ArgumentError ODESystem(eqs, t, vars, pars, name = :foo) +@test_throws ArgumentError System(eqs, t, vars, pars, name = :foo) @parameters w der = Differential(w) eqs = [ der(u1) ~ t ] -@test_throws ArgumentError ModelingToolkit.ODESystem(eqs, t, vars, pars, name = :foo) +@test_throws ArgumentError ModelingToolkit.System(eqs, t, vars, pars, name = :foo) @variables x(t) @parameters M b k @@ -441,7 +441,7 @@ eqs = [D(D(x)) ~ -b / M * D(x) - k / M * x] ps = [M, b, k] default_u0 = [D(x) => 0.0, x => 10.0] default_p = [M => 1.0, b => 1.0, k => 1.0] -@named sys = ODESystem(eqs, t, [x], ps; defaults = [default_u0; default_p], tspan) +@named sys = System(eqs, t, [x], ps; defaults = [default_u0; default_p], tspan) sys = ode_order_lowering(sys) sys = complete(sys) prob = ODEProblem(sys) @@ -454,7 +454,7 @@ prob = ODEProblem{false}(sys; u0_constructor = x -> SVector(x...)) # check_eqs_u0 kwarg test @variables x1(t) x2(t) eqs = [D(x1) ~ -x1] -@named sys = ODESystem(eqs, t, [x1, x2], []) +@named sys = System(eqs, t, [x1, x2], []) sys = complete(sys) @test_throws ArgumentError ODEProblem(sys, [1.0, 1.0], (0.0, 1.0)) @test_nowarn ODEProblem(sys, [1.0, 1.0], (0.0, 1.0), check_length = false) @@ -466,7 +466,7 @@ let δ = D eqs = [δ(x) ~ ẋ, δ(ẋ) ~ f - k * x - d * ẋ] - @named sys = ODESystem(eqs, t, [x, ẋ], [f, d, k]; controls = [f]) + @named sys = System(eqs, t, [x, ẋ], [f, d, k]; controls = [f]) calculate_control_jacobian(sys) @@ -477,7 +477,7 @@ end # issue 1109 let @variables x(t)[1:3, 1:3] - @named sys = ODESystem(D.(x) .~ x, t) + @named sys = System(D.(x) .~ x, t) @test_nowarn structural_simplify(sys) end @@ -488,7 +488,7 @@ sts = @variables x(t)[1:3]=[1, 2, 3.0] y(t)=1.0 ps = @parameters p[1:3] = [1, 2, 3] eqs = [collect(D.(x) .~ x) D(y) ~ norm(collect(x)) * y - x[1]] -@named sys = ODESystem(eqs, t, sts, ps) +@named sys = System(eqs, t, sts, ps) sys = structural_simplify(sys) @test isequal(@nonamespace(sys.x), x) @test isequal(@nonamespace(sys.y), y) @@ -512,14 +512,14 @@ function submodel(; name) @variables y(t) @parameters A[1:5] A = collect(A) - ODESystem(D(y) ~ sum(A) * y, t; name = name) + System(D(y) ~ sum(A) * y, t; name = name) end # Build system @named sys1 = submodel() @named sys2 = submodel() -@named sys = ODESystem([0 ~ sys1.y + sys2.y], t; systems = [sys1, sys2]) +@named sys = System([0 ~ sys1.y + sys2.y], t; systems = [sys1, sys2]) # DelayDiffEq using ModelingToolkit: hist @@ -527,7 +527,7 @@ using ModelingToolkit: hist xₜ₋₁ = hist(x, t - 1) eqs = [D(x) ~ x * y D(y) ~ y * x - xₜ₋₁] -@named sys = ODESystem(eqs, t) +@named sys = System(eqs, t) # register using StaticArrays @@ -538,8 +538,8 @@ foo(a, ms::AbstractVector) = a + sum(ms) @register_symbolic foo(a, ms::AbstractVector) @variables x(t) ms(t)[1:3] eqs = [D(x) ~ foo(x, ms); D(ms) ~ ones(3)] -@named sys = ODESystem(eqs, t, [x; ms], []) -@named emptysys = ODESystem(Equation[], t) +@named sys = System(eqs, t, [x; ms], []) +@named emptysys = System(Equation[], t) @mtkbuild outersys = compose(emptysys, sys) prob = ODEProblem( outersys, [outersys.sys.x => 1.0; collect(outersys.sys.ms .=> 1:3)], (0, 1.0)) @@ -553,8 +553,8 @@ bar(x, p) = p * x end @parameters p[1:3, 1:3] eqs = [D(x) ~ foo(x, ms); D(ms) ~ bar(ms, p)] -@named sys = ODESystem(eqs, t) -@named emptysys = ODESystem(Equation[], t) +@named sys = System(eqs, t) +@named emptysys = System(Equation[], t) @mtkbuild outersys = compose(emptysys, sys) prob = ODEProblem( outersys, [sys.x => 1.0, sys.ms => 1:3], (0.0, 1.0), [sys.p => ones(3, 3)]) @@ -565,13 +565,13 @@ obsfn = ModelingToolkit.build_explicit_observed_function( # x/x @variables x(t) -@named sys = ODESystem([D(x) ~ x / x], t) +@named sys = System([D(x) ~ x / x], t) @test equations(alias_elimination(sys)) == [D(x) ~ 1] # observed variable handling @variables x(t) RHS(t) @parameters τ -@named fol = ODESystem([D(x) ~ (1 - x) / τ], t; observed = [RHS ~ (1 - x) / τ]) +@named fol = System([D(x) ~ (1 - x) / τ], t; observed = [RHS ~ (1 - x) / τ]) @test isequal(RHS, @nonamespace fol.RHS) RHS2 = RHS @unpack RHS = fol @@ -587,13 +587,13 @@ eqs = [ z ~ α * x - β * y ] -@named sys = ODESystem(eqs, t, [x, y, z], [α, β]) +@named sys = System(eqs, t, [x, y, z], [α, β]) sys = complete(sys) @test_throws Any ODEFunction(sys) eqs = copy(eqs) eqs[end] = D(D(z)) ~ α * x - β * y -@named sys = ODESystem(eqs, t, [x, y, z], [α, β]) +@named sys = System(eqs, t, [x, y, z], [α, β]) sys = complete(sys) @test_throws Any ODEFunction(sys) @@ -645,7 +645,7 @@ sys = complete(sys) D(us[i]) ~ dummy_identity(buffer[i], us[i]) end - @named sys = ODESystem(eqs, t, us, ps; defaults = defs, preface = preface) + @named sys = System(eqs, t, us, ps; defaults = defs, preface = preface) sys = complete(sys) prob = ODEProblem(sys, [], (0.0, 1.0)) sol = solve(prob, Euler(); dt = 0.1) @@ -660,7 +660,7 @@ let eqs = [D(x[1]) ~ x[2] D(x[2]) ~ -x[1] - 0.5 * x[2] + k y ~ 0.9 * x[1] + x[2]] - @named sys = ODESystem(eqs, t, vcat(x, [y]), [k], defaults = Dict(x .=> 0)) + @named sys = System(eqs, t, vcat(x, [y]), [k], defaults = Dict(x .=> 0)) sys = structural_simplify(sys) u0 = [0.5, 0] @@ -706,7 +706,7 @@ let @parameters k1 k2::Int @variables A(t) eqs = [D(A) ~ -k1 * k2 * A] - @named sys = ODESystem(eqs, t) + @named sys = System(eqs, t) sys = complete(sys) u0map = [A => 1.0] pmap = (k1 => 1.0, k2 => 1) @@ -742,7 +742,7 @@ let D(p) ~ q / C 0 ~ q / C - R * F] - @named sys = ODESystem(eqs, t) + @named sys = System(eqs, t) @test length(equations(structural_simplify(sys))) == 2 end @@ -775,11 +775,11 @@ let D(z2) ~ y2 - beta * z2 # missing x2 term ] - @named sys1 = ODESystem(eqs, t) - @named sys2 = ODESystem(eqs2, t) - @named sys3 = ODESystem(eqs3, t) + @named sys1 = System(eqs, t) + @named sys2 = System(eqs2, t) + @named sys3 = System(eqs3, t) ssys3 = structural_simplify(sys3) - @named sys4 = ODESystem(eqs4, t) + @named sys4 = System(eqs4, t) @test ModelingToolkit.isisomorphic(sys1, sys2) @test !ModelingToolkit.isisomorphic(sys1, sys3) @@ -788,7 +788,7 @@ let # 1281 iv2 = only(independent_variables(sys2)) - @test isequal(only(independent_variables(convert_system(ODESystem, sys1, iv2))), iv2) + @test isequal(only(independent_variables(convert_system(System, sys1, iv2))), iv2) end let @@ -800,7 +800,7 @@ let sph ~ b spm ~ 0 sph ~ a] - @named sys = ODESystem(eqs, t, vars, pars) + @named sys = System(eqs, t, vars, pars) @test_throws ModelingToolkit.ExtraEquationsSystemException structural_simplify(sys) end @@ -824,7 +824,7 @@ let ps = [] - @named sys = ODESystem(eqs, t, u, ps) + @named sys = System(eqs, t, u, ps) @test_nowarn simpsys = structural_simplify(sys) sys = structural_simplify(sys) @@ -845,7 +845,7 @@ let @parameters k @variables A(t) eqs = [D(A) ~ -k * A] - @named osys = ODESystem(eqs, t) + @named osys = System(eqs, t) osys = complete(osys) oprob = ODEProblem(osys, [A => 1.0], (0.0, 10.0), [k => 1.0]; check_length = false) @test_nowarn sol = solve(oprob, Tsit5()) @@ -855,13 +855,13 @@ let function sys1(; name) vars = @variables x(t)=0.0 dx(t)=0.0 - ODESystem([D(x) ~ dx], t, vars, []; name, defaults = [D(x) => x]) + System([D(x) ~ dx], t, vars, []; name, defaults = [D(x) => x]) end function sys2(; name) @named s1 = sys1() - ODESystem(Equation[], t, [], []; systems = [s1], name) + System(Equation[], t, [], []; systems = [s1], name) end s1′ = sys1(; name = :s1) @@ -890,7 +890,7 @@ let @variables u(t) x(t) v(t) eqs = [u ~ kx * x + kv * v] - ODESystem(eqs, t; name) + System(eqs, t; name) end @named ctrl = pd_ctrl() @@ -901,7 +901,7 @@ let @variables u(t) x(t) v(t) eqs = [D(x) ~ v, D(v) ~ u] - ODESystem(eqs, t; name) + System(eqs, t; name) end @named sys = double_int() @@ -910,7 +910,7 @@ let connections = [sys.u ~ ctrl.u, ctrl.x ~ sys.x, ctrl.v ~ sys.v] - @named connected = ODESystem(connections, t) + @named connected = System(connections, t) @named sys_con = compose(connected, sys, ctrl) sys_simp = structural_simplify(sys_con) @@ -923,7 +923,7 @@ let @variables x(t) = 1 @variables y(t) = 1 @parameters pp = -1 - @named sys4 = ODESystem([D(x) ~ -y; D(y) ~ 1 + pp * y + x], t) + @named sys4 = System([D(x) ~ -y; D(y) ~ 1 + pp * y + x], t) sys4s = structural_simplify(sys4) prob = ODEProblem(sys4s, [x => 1.0, D(x) => 1.0], (0, 1.0)) @test string.(unknowns(prob.f.sys)) == ["x(t)", "y(t)"] @@ -936,7 +936,7 @@ let ∂t = D eqs = [∂t(Q) ~ 0.2P ∂t(P) ~ -80.0sin(Q)] - @test_throws ArgumentError @named sys = ODESystem(eqs, t) + @test_throws ArgumentError @named sys = System(eqs, t) end @parameters C L R @@ -946,12 +946,12 @@ eqs = [D(q) ~ -p / L - F D(p) ~ q / C 0 ~ q / C - R * F] testdict = Dict([:name => "test"]) -@named sys = ODESystem(eqs, t, metadata = testdict) +@named sys = System(eqs, t, metadata = testdict) @test get_metadata(sys) == testdict @variables P(t)=NaN Q(t)=NaN eqs = [D(Q) ~ 1 / sin(P), D(P) ~ log(-cos(Q))] -@named sys = ODESystem(eqs, t, [P, Q], []) +@named sys = System(eqs, t, [P, Q], []) sys = complete(debug_system(sys)) prob = ODEProblem(sys, [], (0.0, 1.0)) @test_throws "log(-cos(Q(t))) errors" prob.f([1, 0], prob.p, 0.0) @@ -962,7 +962,7 @@ let @variables y(t) = 1 @parameters pp = -1 der = Differential(t) - @named sys4 = ODESystem([der(x) ~ -y; der(y) ~ 1 + pp * y + x], t) + @named sys4 = System([der(x) ~ -y; der(y) ~ 1 + pp * y + x], t) sys4s = structural_simplify(sys4) prob = ODEProblem(sys4s, [x => 1.0, D(x) => 1.0], (0, 1.0)) @test !isnothing(prob.f.sys) @@ -970,15 +970,15 @@ end # SYS 1: vars_sub1 = @variables s1(t) -@named sub = ODESystem(Equation[], t, vars_sub1, []) +@named sub = System(Equation[], t, vars_sub1, []) vars1 = @variables x1(t) -@named sys1 = ODESystem(Equation[], t, vars1, [], systems = [sub]) -@named sys2 = ODESystem(Equation[], t, vars1, [], systems = [sys1, sub]) +@named sys1 = System(Equation[], t, vars1, [], systems = [sub]) +@named sys2 = System(Equation[], t, vars1, [], systems = [sys1, sub]) # SYS 2: Extension to SYS 1 vars_sub2 = @variables s2(t) -@named partial_sub = ODESystem(Equation[], t, vars_sub2, []) +@named partial_sub = System(Equation[], t, vars_sub2, []) @named sub = extend(partial_sub, sub) # no warnings for systems without events @@ -997,7 +997,7 @@ let # Issue https://github.com/SciML/ModelingToolkit.jl/issues/2322 eqs = [Dt(x) ~ -b * (x - z), 0 ~ z - c * x] - sys = ODESystem(eqs, t; name = :kjshdf) + sys = System(eqs, t; name = :kjshdf) sys_simp = structural_simplify(sys) @@ -1012,7 +1012,7 @@ end # Issue#2599 @variables x(t) y(t) eqs = [D(x) ~ x * t, y ~ 2x] -@mtkbuild sys = ODESystem(eqs, t; continuous_events = [[y ~ 3] => [x ~ 2]]) +@mtkbuild sys = System(eqs, t; continuous_events = [[y ~ 3] => [x ~ 2]]) prob = ODEProblem(sys, [x => 1.0], (0.0, 10.0)) @test_nowarn solve(prob, Tsit5()) @@ -1022,13 +1022,13 @@ prob = ODEProblem(sys, [x => 1.0], (0.0, 10.0)) eqs = [ D(x) ~ p * x ] -@mtkbuild sys = ODESystem(eqs, t; continuous_events = [[norm(x) ~ 3.0] => [x ~ ones(3)]]) +@mtkbuild sys = System(eqs, t; continuous_events = [[norm(x) ~ 3.0] => [x ~ ones(3)]]) # array affect equations used to not work prob1 = @test_nowarn ODEProblem(sys, [x => ones(3)], (0.0, 10.0), [p => ones(3, 3)]) sol1 = @test_nowarn solve(prob1, Tsit5()) # array condition equations also used to not work -@mtkbuild sys = ODESystem( +@mtkbuild sys = System( eqs, t; continuous_events = [[x ~ sqrt(3) * ones(3)] => [x ~ ones(3)]]) # array affect equations used to not work prob2 = @test_nowarn ODEProblem(sys, [x => ones(3)], (0.0, 10.0), [p => ones(3, 3)]) @@ -1040,7 +1040,7 @@ sol2 = @test_nowarn solve(prob2, Tsit5()) @test_skip begin @variables x(t)[1:3] y(t) @parameters p[1:3, 1:3] - @test_nowarn @mtkbuild sys = ODESystem([D(x) ~ p * x, D(y) ~ x' * p * x], t) + @test_nowarn @mtkbuild sys = System([D(x) ~ p * x, D(y) ~ x' * p * x], t) @test_nowarn ODEProblem(sys, [x => ones(3), y => 2], (0.0, 10.0), [p => ones(3, 3)]) end @@ -1052,7 +1052,7 @@ eqs = [D(D(q₁)) ~ -λ * q₁, q₁ ~ L * sin(θ), q₂ ~ L * cos(θ)] -@named pend = ODESystem(eqs, t) +@named pend = System(eqs, t) @test_nowarn generate_initializesystem( pend, u0map = [q₁ => 1.0, q₂ => 0.0], guesses = [λ => 1]) @@ -1064,7 +1064,7 @@ eqs = [D(D(x)) ~ σ * (y - x), D(y) ~ x * (ρ - z) - y, D(z) ~ x * y - β * z] -@mtkbuild sys = ODESystem(eqs, t) +@mtkbuild sys = System(eqs, t) u0 = [D(x) => 2.0, x => 1.0, @@ -1096,7 +1096,7 @@ function FML2(; name) eqs = [ D(x) ~ constant.output.u + k2[1] ] - ODESystem(eqs, t; systems, name) + System(eqs, t; systems, name) end @mtkbuild model = FML2() @@ -1112,7 +1112,7 @@ function RealExpression(; name, y) eqns = [ u ~ y ] - sys = ODESystem(eqns, t, vars, []; name) + sys = System(eqns, t, vars, []; name) end function RealExpressionSystem(; name) @@ -1123,7 +1123,7 @@ function RealExpressionSystem(; name) @named e1 = RealExpression(y = x) # This works perfectly. @named e2 = RealExpression(y = z[1]) # This bugs. However, `full_equations(e2)` works as expected. systems = [e1, e2] - ODESystem(Equation[], t, Iterators.flatten(vars), []; systems, name) + System(Equation[], t, Iterators.flatten(vars), []; systems, name) end @named sys = RealExpressionSystem() @@ -1136,8 +1136,8 @@ orig_vars = unknowns(sys) # Guesses in hierarchical systems @variables x(t) y(t) -@named sys = ODESystem(Equation[], t, [x], []; guesses = [x => 1.0]) -@named outer = ODESystem( +@named sys = System(Equation[], t, [x], []; guesses = [x => 1.0]) +@named outer = System( [D(y) ~ sys.x + t, 0 ~ t + y - sys.x * y], t, [y], []; systems = [sys]) @test ModelingToolkit.guesses(outer)[sys.x] == 1.0 outer = structural_simplify(outer) @@ -1148,9 +1148,9 @@ int = init(prob, Rodas4()) # Ensure indexes of array symbolics are cached appropriately @variables x(t)[1:2] -@named sys = ODESystem(Equation[], t, [x], []) +@named sys = System(Equation[], t, [x], []) sys1 = complete(sys) -@named sys = ODESystem(Equation[], t, [x...], []) +@named sys = System(Equation[], t, [x...], []) sys2 = complete(sys) for sys in [sys1, sys2] for (sym, idx) in [(x, 1:2), (x[1], 1), (x[2], 2)] @@ -1160,9 +1160,9 @@ for sys in [sys1, sys2] end @variables x(t)[1:2, 1:2] -@named sys = ODESystem(Equation[], t, [x], []) +@named sys = System(Equation[], t, [x], []) sys1 = complete(sys) -@named sys = ODESystem(Equation[], t, [x...], []) +@named sys = System(Equation[], t, [x...], []) sys2 = complete(sys) for sys in [sys1, sys2] @test is_variable(sys, x) @@ -1175,14 +1175,14 @@ end @testset "Non-1-indexed variable array (issue #2670)" begin @variables x(t)[0:1] # 0-indexed variable array - @named sys = ODESystem([x[0] ~ 0.0, D(x[1]) ~ x[0]], t, [x], []) + @named sys = System([x[0] ~ 0.0, D(x[1]) ~ x[0]], t, [x], []) @test_nowarn sys = structural_simplify(sys) @test equations(sys) == [D(x[1]) ~ 0.0] end # Namespacing of array variables @variables x(t)[1:2] -@named sys = ODESystem(Equation[], t) +@named sys = System(Equation[], t) @test getname(unknowns(sys, x)) == :sys₊x @test size(unknowns(sys, x)) == size(x) @@ -1190,7 +1190,7 @@ end @testset "ForwardDiff through ODEProblem constructor" begin @parameters P @variables x(t) - sys = structural_simplify(ODESystem([D(x) ~ P], t, [x], [P]; name = :sys)) + sys = structural_simplify(System([D(x) ~ P], t, [x], [P]; name = :sys)) function x_at_1(P) prob = ODEProblem(sys, [x => P], (0.0, 1.0), [sys.P => P]) @@ -1203,7 +1203,7 @@ end @testset "Inplace observed functions" begin @parameters P @variables x(t) - sys = structural_simplify(ODESystem([D(x) ~ P], t, [x], [P]; name = :sys)) + sys = structural_simplify(System([D(x) ~ P], t, [x], [P]; name = :sys)) obsfn = ModelingToolkit.build_explicit_observed_function( sys, [x + 1, x + P, x + t], return_inplace = true)[2] ps = ModelingToolkit.MTKParameters(sys, [P => 2.0]) @@ -1216,7 +1216,7 @@ end @testset "Custom independent variable" begin @independent_variables x @variables y(x) - @test_nowarn @named sys = ODESystem([y ~ 0], x) + @test_nowarn @named sys = System([y ~ 0], x) # the same, but with @mtkmodel @independent_variables x @@ -1231,7 +1231,7 @@ end @test_nowarn @mtkbuild sys = MyModel() @variables x y(x) - @test_logs (:warn,) @named sys = ODESystem([y ~ 0], x) + @test_logs (:warn,) @named sys = System([y ~ 0], x) @parameters T D = Differential(T) @@ -1239,7 +1239,7 @@ end eqs = [D(x) ~ 0.0] initialization_eqs = [x ~ T] guesses = [x => 0.0] - @named sys2 = ODESystem(eqs, T; initialization_eqs, guesses) + @named sys2 = System(eqs, T; initialization_eqs, guesses) prob2 = ODEProblem(structural_simplify(sys2), [], (1.0, 2.0), []) sol2 = solve(prob2) @test all(sol2[x] .== 1.0) @@ -1249,10 +1249,10 @@ end @testset "Extend systems with a field that can be nothing" begin A = Dict(:a => 1) B = Dict(:b => 2) - @named A1 = ODESystem(Equation[], t, [], []) - @named B1 = ODESystem(Equation[], t, [], []) - @named A2 = ODESystem(Equation[], t, [], []; metadata = A) - @named B2 = ODESystem(Equation[], t, [], []; metadata = B) + @named A1 = System(Equation[], t, [], []) + @named B1 = System(Equation[], t, [], []) + @named A2 = System(Equation[], t, [], []; metadata = A) + @named B2 = System(Equation[], t, [], []; metadata = B) @test ModelingToolkit.get_metadata(extend(A1, B1)) == nothing @test ModelingToolkit.get_metadata(extend(A1, B2)) == B @test ModelingToolkit.get_metadata(extend(A2, B1)) == A @@ -1264,7 +1264,7 @@ end @variables x(t) y(t) z(t) eqs = [D(x) ~ 0, y ~ x, D(z) ~ 0] defaults = [x => 1, z => y] - @named sys = ODESystem(eqs, t; defaults) + @named sys = System(eqs, t; defaults) ssys = structural_simplify(sys) prob = ODEProblem(ssys, [], (0.0, 1.0), []) @test prob[x] == prob[y] == prob[z] == 1.0 @@ -1273,7 +1273,7 @@ end @variables x(t) y(t) z(t) eqs = [D(x) ~ 0, y ~ y0 / x, D(z) ~ y] defaults = [y0 => 1, x => 1, z => y] - @named sys = ODESystem(eqs, t; defaults) + @named sys = System(eqs, t; defaults) ssys = structural_simplify(sys) prob = ODEProblem(ssys, [], (0.0, 1.0), []) @test prob[x] == prob[y] == prob[z] == 1.0 @@ -1282,7 +1282,7 @@ end @testset "Scalarized parameters in array functions" begin @variables u(t)[1:2] x(t)[1:2] o(t)[1:2] @parameters p[1:2, 1:2] [tunable = false] - @named sys = ODESystem( + @named sys = System( [D(u) ~ (sum(u) + sum(x) + sum(p) + sum(o)) * x, o ~ prod(u) * x], t, [u..., x..., o...], [p...]) sys1, = structural_simplify(sys, ([x...], [])) @@ -1343,7 +1343,7 @@ end @testset "Independent variable as system property" begin @variables x(t) - @named sys = ODESystem([x ~ t], t) + @named sys = System([x ~ t], t) @named sys = compose(sys, sys) # nest into a hierarchical system @test t === sys.t === sys.sys.t end @@ -1351,7 +1351,7 @@ end @testset "Substituting preserves parameter dependencies, defaults, guesses" begin @parameters p1 p2 @variables x(t) y(t) - @named sys = ODESystem([D(x) ~ y + p2], t; parameter_dependencies = [p2 ~ 2p1], + @named sys = System([D(x) ~ y + p2], t; parameter_dependencies = [p2 ~ 2p1], defaults = [p1 => 1.0, p2 => 2.0], guesses = [p1 => 2.0, p2 => 3.0]) @parameters p3 sys2 = substitute(sys, [p1 => p3]) @@ -1367,10 +1367,10 @@ end @testset "Substituting with nested systems" begin @parameters p1 p2 @variables x(t) y(t) - @named innersys = ODESystem([D(x) ~ y + p2], t; parameter_dependencies = [p2 ~ 2p1], + @named innersys = System([D(x) ~ y + p2], t; parameter_dependencies = [p2 ~ 2p1], defaults = [p1 => 1.0, p2 => 2.0], guesses = [p1 => 2.0, p2 => 3.0]) @parameters p3 p4 - @named outersys = ODESystem( + @named outersys = System( [D(innersys.y) ~ innersys.y + p4], t; parameter_dependencies = [p4 ~ 3p3], defaults = [p3 => 3.0, p4 => 9.0], guesses = [p4 => 10.0], systems = [innersys]) @test_nowarn structural_simplify(outersys) @@ -1397,7 +1397,7 @@ end o[1] ~ sum(p) * sum(u) o[2] ~ sum(p) * sum(x)] - @named sys = ODESystem(eqs, t, [u..., x..., o], [p...]) + @named sys = System(eqs, t, [u..., x..., o], [p...]) sys1, = structural_simplify(sys, ([x...], [o...]), split = false) @test_nowarn ModelingToolkit.build_explicit_observed_function(sys1, u; inputs = [x...]) @@ -1410,17 +1410,17 @@ end @testset "Passing `nothing` to `u0`" begin @variables x(t) = 1 - @mtkbuild sys = ODESystem(D(x) ~ t, t) + @mtkbuild sys = System(D(x) ~ t, t) prob = @test_nowarn ODEProblem(sys, nothing, (0.0, 1.0)) @test_nowarn solve(prob) end @testset "ODEs are not DDEs" begin @variables x(t) - @named sys = ODESystem(D(x) ~ x, t) + @named sys = System(D(x) ~ x, t) @test !ModelingToolkit.is_dde(sys) @test is_markovian(sys) - @named sys2 = ODESystem(Equation[], t; systems = [sys]) + @named sys2 = System(Equation[], t; systems = [sys]) @test !ModelingToolkit.is_dde(sys) @test is_markovian(sys) end @@ -1430,7 +1430,7 @@ end for eqs in [D(x) ~ x, collect(D(x) .~ x)] for dvs in [[x], collect(x)] - @named sys = ODESystem(eqs, t, dvs, []) + @named sys = System(eqs, t, dvs, []) sys = complete(sys) if eqs isa Vector && length(eqs) == 2 && length(dvs) == 2 @test_nowarn ODEProblem(sys, [], (0.0, 1.0)) @@ -1443,7 +1443,7 @@ end end for eqs in [[D(x) ~ x, D(y) ~ y], [collect(D(x) .~ x); D(y) ~ y]] for dvs in [[x, y], [x..., y]] - @named sys = ODESystem(eqs, t, dvs, []) + @named sys = System(eqs, t, dvs, []) sys = complete(sys) if eqs isa Vector && length(eqs) == 3 && length(dvs) == 3 @test_nowarn ODEProblem(sys, [], (0.0, 1.0)) @@ -1458,13 +1458,13 @@ end @testset "Parameter dependencies with constant RHS" begin @parameters p - @test_nowarn ODESystem(Equation[], t; parameter_dependencies = [p ~ 1.0], name = :a) + @test_nowarn System(Equation[], t; parameter_dependencies = [p ~ 1.0], name = :a) end @testset "Variable discovery in arrays of `Num` inside callable symbolic" begin @variables x(t) y(t) @parameters foo(::AbstractVector) - sys = @test_nowarn ODESystem(D(x) ~ foo([x, 2y]), t; name = :sys) + sys = @test_nowarn System(D(x) ~ foo([x, 2y]), t; name = :sys) @test length(unknowns(sys)) == 2 @test any(isequal(y), unknowns(sys)) end @@ -1472,7 +1472,7 @@ end @testset "Inplace observed" begin @variables x(t) @parameters p[1:2] q - @mtkbuild sys = ODESystem(D(x) ~ sum(p) * x + q * t, t) + @mtkbuild sys = System(D(x) ~ sum(p) * x + q * t, t) prob = ODEProblem(sys, [x => 1.0], (0.0, 1.0), [p => ones(2), q => 2]) obsfn = ModelingToolkit.build_explicit_observed_function( sys, [p..., q], return_inplace = true)[2] @@ -1512,7 +1512,7 @@ end @testset "`complete` with `split = false` removes the index cache" begin @variables x(t) @parameters p - @mtkbuild sys = ODESystem(D(x) ~ p * t, t) + @mtkbuild sys = System(D(x) ~ p * t, t) @test ModelingToolkit.get_index_cache(sys) !== nothing sys2 = complete(sys; split = false) @test ModelingToolkit.get_index_cache(sys2) === nothing @@ -1522,7 +1522,7 @@ end @testset "Observed variables dependent on discrete parameters" begin @variables x(t) obs(t) @parameters c(t) - @mtkbuild sys = ODESystem( + @mtkbuild sys = System( [D(x) ~ c * cos(x), obs ~ c], t, [x], [c]; discrete_events = [1.0 => [c ~ c + 1]]) prob = ODEProblem(sys, [x => 0.0], (0.0, 2pi), [c => 1.0]) sol = solve(prob, Tsit5()) @@ -1532,7 +1532,7 @@ end @testset "DAEProblem with array parameters" begin @variables x(t)=1.0 y(t) [guess = 1.0] @parameters p[1:2] = [1.0, 2.0] - @mtkbuild sys = ODESystem([D(x) ~ x, y^2 ~ x + sum(p)], t) + @mtkbuild sys = System([D(x) ~ x, y^2 ~ x + sum(p)], t) prob = DAEProblem(sys, [D(x) => x, D(y) => D(x) / 2y], [], (0.0, 1.0)) sol = solve(prob, DFBDF(), abstol = 1e-8, reltol = 1e-8) @test sol[x]≈sol[y^2 - sum(p)] atol=1e-5 @@ -1541,7 +1541,7 @@ end @testset "Symbolic tstops" begin @variables x(t) = 1.0 @parameters p=0.15 q=0.25 r[1:2]=[0.35, 0.45] - @mtkbuild sys = ODESystem( + @mtkbuild sys = System( [D(x) ~ p * x + q * t + sum(r)], t; tstops = [0.5p, [0.1, 0.2], [p + 2q], r]) prob = ODEProblem(sys, [], (0.0, 5.0)) sol = solve(prob) @@ -1553,7 +1553,7 @@ end @test all(x -> any(isapprox(x, atol = 1e-6), sol2.t), expected_tstops) @variables y(t) [guess = 1.0] - @mtkbuild sys = ODESystem([D(x) ~ p * x + q * t + sum(r), y^3 ~ 2x + 1], + @mtkbuild sys = System([D(x) ~ p * x + q * t + sum(r), y^3 ~ 2x + 1], t; tstops = [0.5p, [0.1, 0.2], [p + 2q], r]) prob = DAEProblem( sys, [D(y) => 2D(x) / 3y^2, D(x) => p * x + q * t + sum(r)], [], (0.0, 5.0)) @@ -1570,14 +1570,14 @@ end @parameters p d @variables X(t)::Int64 eq = D(X) ~ p - d * X - @test_throws ArgumentError @mtkbuild osys = ODESystem([eq], t) + @test_throws ArgumentError @mtkbuild osys = System([eq], t) @variables Y(t)[1:3]::String eq = D(Y) ~ [p, p, p] - @test_throws ArgumentError @mtkbuild osys = ODESystem([eq], t) + @test_throws ArgumentError @mtkbuild osys = System([eq], t) @variables X(t)::Complex eq = D(X) ~ p - d * X - @test_nowarn @named osys = ODESystem([eq], t) + @test_nowarn @named osys = System([eq], t) end # Test `isequal` @@ -1586,30 +1586,30 @@ end @parameters p d eq = D(X) ~ p - d * X - osys1 = complete(ODESystem([eq], t; name = :osys)) - osys2 = complete(ODESystem([eq], t; name = :osys)) + osys1 = complete(System([eq], t; name = :osys)) + osys2 = complete(System([eq], t; name = :osys)) @test osys1 == osys2 # true continuous_events = [[X ~ 1.0] => [X ~ X + 5.0]] discrete_events = [5.0 => [d ~ d / 2.0]] - osys1 = complete(ODESystem([eq], t; name = :osys, continuous_events)) - osys2 = complete(ODESystem([eq], t; name = :osys)) + osys1 = complete(System([eq], t; name = :osys, continuous_events)) + osys2 = complete(System([eq], t; name = :osys)) @test osys1 !== osys2 - osys1 = complete(ODESystem([eq], t; name = :osys, discrete_events)) - osys2 = complete(ODESystem([eq], t; name = :osys)) + osys1 = complete(System([eq], t; name = :osys, discrete_events)) + osys2 = complete(System([eq], t; name = :osys)) @test osys1 !== osys2 - osys1 = complete(ODESystem([eq], t; name = :osys, continuous_events)) - osys2 = complete(ODESystem([eq], t; name = :osys, discrete_events)) + osys1 = complete(System([eq], t; name = :osys, continuous_events)) + osys2 = complete(System([eq], t; name = :osys, discrete_events)) @test osys1 !== osys2 end @testset "dae_order_lowering basic test" begin @parameters a @variables x(t) y(t) z(t) - @named dae_sys = ODESystem([ + @named dae_sys = System([ D(x) ~ y, 0 ~ x + z, 0 ~ x - y + z @@ -1645,7 +1645,7 @@ end D(x) => 0.0, x => 10.0, y => 0.0, z => 10.0 ] default_p = [M => 1.0, b => 1.0, k => 1.0] - @named dae_sys = ODESystem(eqs, t, [x, y, z], ps; defaults = [default_u0; default_p]) + @named dae_sys = System(eqs, t, [x, y, z], ps; defaults = [default_u0; default_p]) simplified_dae_sys = structural_simplify(dae_sys) @@ -1670,32 +1670,32 @@ end cons = [x(0.3) ~ c * d, y(0.7) ~ 3] # Test variables + parameters infer correctly. - @mtkbuild sys = ODESystem(eqs, t; constraints = cons) + @mtkbuild sys = System(eqs, t; constraints = cons) @test issetequal(parameters(sys), [a, c, d, e]) @test issetequal(unknowns(sys), [x(t), y(t), z(t)]) @parameters t_c cons = [x(t_c) ~ 3] - @mtkbuild sys = ODESystem(eqs, t; constraints = cons) + @mtkbuild sys = System(eqs, t; constraints = cons) @test issetequal(parameters(sys), [a, e, t_c]) @parameters g(..) h i cons = [g(h, i) * x(3) ~ c] - @mtkbuild sys = ODESystem(eqs, t; constraints = cons) + @mtkbuild sys = System(eqs, t; constraints = cons) @test issetequal(parameters(sys), [g, h, i, a, e, c]) # Test that bad constraints throw errors. cons = [x(3, 4) ~ 3] # unknowns cannot have multiple args. - @test_throws ArgumentError @mtkbuild sys = ODESystem(eqs, t; constraints = cons) + @test_throws ArgumentError @mtkbuild sys = System(eqs, t; constraints = cons) cons = [x(y(t)) ~ 2] # unknown arg must be parameter, value, or t - @test_throws ArgumentError @mtkbuild sys = ODESystem(eqs, t; constraints = cons) + @test_throws ArgumentError @mtkbuild sys = System(eqs, t; constraints = cons) @variables u(t) v cons = [x(t) * u ~ 3] - @test_throws ArgumentError @mtkbuild sys = ODESystem(eqs, t; constraints = cons) + @test_throws ArgumentError @mtkbuild sys = System(eqs, t; constraints = cons) cons = [x(t) * v ~ 3] - @test_throws ArgumentError @mtkbuild sys = ODESystem(eqs, t; constraints = cons) # Need time argument. + @test_throws ArgumentError @mtkbuild sys = System(eqs, t; constraints = cons) # Need time argument. # Test array variables @variables x(..)[1:5] @@ -1706,13 +1706,13 @@ end 0 0 2 0 5] eqs = D(x(t)) ~ mat * x(t) cons = [x(3) ~ [2, 3, 3, 5, 4]] - @mtkbuild ode = ODESystem(D(x(t)) ~ mat * x(t), t; constraints = cons) + @mtkbuild ode = System(D(x(t)) ~ mat * x(t), t; constraints = cons) @test length(constraints(ModelingToolkit.get_constraintsystem(ode))) == 5 end @testset "`build_explicit_observed_function` with `expression = true` returns `Expr`" begin @variables x(t) - @mtkbuild sys = ODESystem(D(x) ~ 2x, t) + @mtkbuild sys = System(D(x) ~ 2x, t) obsfn_expr = ModelingToolkit.build_explicit_observed_function( sys, 2x + 1, expression = true) @test obsfn_expr isa Expr @@ -1729,17 +1729,17 @@ end @test scope isa ParentScope @test scope.parent isa ParentScope @test scope.parent.parent isa LocalScope - return ODESystem(D(x) ~ var1, t; name) + return System(D(x) ~ var1, t; name) end function SysB(; name, var1) @variables x(t) @named subsys = SysA(; var1) - return ODESystem(D(x) ~ x, t; systems = [subsys], name) + return System(D(x) ~ x, t; systems = [subsys], name) end function SysC(; name) @variables x(t) @named subsys = SysB(; var1 = x) - return ODESystem(D(x) ~ x, t; systems = [subsys], name) + return System(D(x) ~ x, t; systems = [subsys], name) end @mtkbuild sys = SysC() @test length(unknowns(sys)) == 3 diff --git a/test/optimal_control.jl b/test/optimal_control.jl index f32ba471d8..e1e09e1eb2 100644 --- a/test/optimal_control.jl +++ b/test/optimal_control.jl @@ -21,7 +21,7 @@ daesolvers = [Ascher2, Ascher4, Ascher6] parammap = [α => 7.5, β => 4, γ => 8.0, δ => 5.0] tspan = (0.0, 10.0) - @mtkbuild lotkavolterra = ODESystem(eqs, t) + @mtkbuild lotkavolterra = System(eqs, t) op = ODEProblem(lotkavolterra, u0map, tspan, parammap) osol = solve(op, Vern9()) @@ -53,7 +53,7 @@ end eqs = [D(θ) ~ θ_t D(θ_t) ~ -(g / L) * sin(θ)] - @mtkbuild pend = ODESystem(eqs, t) + @mtkbuild pend = System(eqs, t) u0map = [θ => π / 2, θ_t => π / 2] parammap = [:L => 1.0, :g => 9.81] @@ -81,7 +81,7 @@ end end ################################################################## -### ODESystem with constraint equations, DAEs with constraints ### +### System with constraint equations, DAEs with constraints ### ################################################################## # Test generation of boundary condition function using `generate_function_bc`. Compare solutions to manually written boundary conditions @@ -92,7 +92,7 @@ end D(y(t)) ~ -γ * y(t) + δ * x(t) * y(t)] tspan = (0.0, 1.0) - @mtkbuild lksys = ODESystem(eqs, t) + @mtkbuild lksys = System(eqs, t) function lotkavolterra!(du, u, p, t) du[1] = p[1] * u[1] - p[2] * u[1] * u[2] @@ -105,7 +105,7 @@ end # Test with a constraint. constr = [y(0.5) ~ 2.0] - @mtkbuild lksys = ODESystem(eqs, t; constraints = constr) + @mtkbuild lksys = System(eqs, t; constraints = constr) function bc!(resid, u, p, t) resid[1] = u(0.0)[1] - 1.0 @@ -166,7 +166,7 @@ function test_solvers( end end -# Simple ODESystem with BVP constraints. +# Simple System with BVP constraints. @testset "ODE with constraints" begin @parameters α=1.5 β=1.0 γ=3.0 δ=1.0 @variables x(..) y(..) @@ -178,7 +178,7 @@ end tspan = (0.0, 1.0) guess = [x(t) => 4.0, y(t) => 2.0] constr = [x(0.6) ~ 3.5, x(0.3) ~ 7.0] - @mtkbuild lksys = ODESystem(eqs, t; constraints = constr) + @mtkbuild lksys = System(eqs, t; constraints = constr) bvp = SciMLBase.BVProblem{true, SciMLBase.AutoSpecialize}( lksys, u0map, tspan; guesses = guess) @@ -186,13 +186,13 @@ end # Testing that more complicated constraints give correct solutions. constr = [y(0.2) + x(0.8) ~ 3.0, y(0.3) ~ 2.0] - @mtkbuild lksys = ODESystem(eqs, t; constraints = constr) + @mtkbuild lksys = System(eqs, t; constraints = constr) bvp = SciMLBase.BVProblem{false, SciMLBase.FullSpecialize}( lksys, u0map, tspan; guesses = guess) test_solvers(solvers, bvp, u0map, constr; dt = 0.05) constr = [α * β - x(0.6) ~ 0.0, y(0.2) ~ 3.0] - @mtkbuild lksys = ODESystem(eqs, t; constraints = constr) + @mtkbuild lksys = System(eqs, t; constraints = constr) bvp = SciMLBase.BVProblem{true, SciMLBase.AutoSpecialize}( lksys, u0map, tspan; guesses = guess) test_solvers(solvers, bvp, u0map, constr) @@ -206,7 +206,7 @@ end # eqs = [D(D(x)) ~ λ * x # D(D(y)) ~ λ * y - g # x^2 + y^2 ~ 1] -# @mtkbuild pend = ODESystem(eqs, t) +# @mtkbuild pend = System(eqs, t) # # tspan = (0.0, 1.5) # u0map = [x => 1, y => 0] @@ -244,7 +244,7 @@ end # D(D(y)) ~ λ * y - g # x(t)^2 + y^2 ~ 1] # constr = [x(0.5) ~ 1] -# @mtkbuild pend = ODESystem(eqs, t; constr) +# @mtkbuild pend = System(eqs, t; constr) # # tspan = (0.0, 1.5) # u0map = [x(t) => 0.6, y => 0.8] @@ -263,13 +263,13 @@ end # # constr = [x(0.5) ~ 1, # x(0.3)^3 + y(0.6)^2 ~ 0.5] -# @mtkbuild pend = ODESystem(eqs, t; constr) +# @mtkbuild pend = System(eqs, t; constr) # bvp = SciMLBase.BVProblem{true, SciMLBase.AutoSpecialize}(pend, u0map, tspan, parammap; guesses, check_length = false) # test_solvers(daesolvers, bvp, u0map, constr, get_alg_eqs(pend)) # # constr = [x(0.4) * g ~ y(0.2), # y(0.7) ~ 0.3] -# @mtkbuild pend = ODESystem(eqs, t; constr) +# @mtkbuild pend = System(eqs, t; constr) # bvp = SciMLBase.BVProblem{true, SciMLBase.AutoSpecialize}(pend, u0map, tspan, parammap; guesses, check_length = false) # test_solvers(daesolvers, bvp, u0map, constr, get_alg_eqs(pend)) # end @@ -287,8 +287,8 @@ end parammap = [α => 7.5, β => 4, γ => 8.0, δ => 5.0] costs = [x(0.6), x(0.3)^2] consolidate(u) = (u[1] + 3)^2 + u[2] - @mtkbuild lksys = ODESystem(eqs, t; costs, consolidate) - @test_throws ErrorException @mtkbuild lksys2 = ODESystem(eqs, t; costs) + @mtkbuild lksys = System(eqs, t; costs, consolidate) + @test_throws ErrorException @mtkbuild lksys2 = System(eqs, t; costs) @test_throws ErrorException ODEProblem(lksys, u0map, tspan, parammap) prob = ODEProblem(lksys, u0map, tspan, parammap; allow_cost = true) @@ -301,7 +301,7 @@ end @parameters t_c costs = [y(t_c) + x(0.0), x(0.4)^2] consolidate(u) = log(u[1]) - u[2] - @mtkbuild lksys = ODESystem(eqs, t; costs, consolidate) + @mtkbuild lksys = System(eqs, t; costs, consolidate) @test t_c ∈ Set(parameters(lksys)) push!(parammap, t_c => 0.56) prob = ODEProblem(lksys, u0map, tspan, parammap; allow_cost = true) diff --git a/test/parameter_dependencies.jl b/test/parameter_dependencies.jl index 31881e1ca8..6b85fb7e10 100644 --- a/test/parameter_dependencies.jl +++ b/test/parameter_dependencies.jl @@ -9,7 +9,7 @@ using SciMLStructures: canonicalize, Tunable, replace, replace! using SymbolicIndexingInterface using NonlinearSolve -@testset "ODESystem with callbacks" begin +@testset "System with callbacks" begin @parameters p1=1.0 p2 @variables x(t) cb1 = [x ~ 2.0] => [p1 ~ 2.0] # triggers at t=-2+√6 @@ -19,7 +19,7 @@ using NonlinearSolve cb2 = [x ~ 4.0] => (affect1!, [], [p1, p2], [p1]) # triggers at t=-2+√7 cb3 = [1.0] => [p1 ~ 5.0] - @mtkbuild sys = ODESystem( + @mtkbuild sys = System( [D(x) ~ p1 * t + p2], t; parameter_dependencies = [p2 => 2p1], @@ -53,7 +53,7 @@ end @parameters p1[1:2]=[1.0, 2.0] p2[1:2]=[0.0, 0.0] @variables x(t) = 0 - @named sys = ODESystem( + @named sys = System( [D(x) ~ sum(p1) * t + sum(p2)], t; parameter_dependencies = [p2 => 2p1] @@ -72,11 +72,11 @@ end @parameters p1=1.0 p2=1.0 @variables x(t) = 0 - @mtkbuild sys1 = ODESystem( + @mtkbuild sys1 = System( [D(x) ~ p1 * t + p2], t ) - @named sys2 = ODESystem( + @named sys2 = System( [], t; parameter_dependencies = [p2 => 2p1] @@ -93,7 +93,7 @@ end @parameters p1=1.0 p2=1.0 @variables x(t) = 0 - @named sys = ODESystem( + @named sys = System( [D(x) ~ p1 * t + p2], t; parameter_dependencies = [p2 => 2p1] @@ -107,7 +107,7 @@ end @parameters p1[1:2]=[1.0, 2.0] p2[1:2]=[0.0, 0.0] @variables x(t) = 0 - @named sys = ODESystem( + @named sys = System( [D(x) ~ sum(p1) * t + sum(p2)], t; parameter_dependencies = [p2 => 2p1] @@ -121,16 +121,16 @@ end @parameters p1=1.0 p2=2.0 @variables x(t) = 0 - @named sys1 = ODESystem( + @named sys1 = System( [D(x) ~ p1 * t + p2], t ) - @named sys2 = ODESystem( + @named sys2 = System( [D(x) ~ p1 * t - p2], t; parameter_dependencies = [p2 => 2p1] ) - sys = complete(ODESystem([], t, systems = [sys1, sys2], name = :sys)) + sys = complete(System([], t, systems = [sys1, sys2], name = :sys)) prob = ODEProblem(sys) v1 = sys.sys2.p2 @@ -158,12 +158,12 @@ end @parameters p2 @variables x(t) = 1.0 eqs = [D(x) ~ p2] - ODESystem(eqs, t, [x], [p2]; name) + System(eqs, t, [x], [p2]; name) end @parameters p1 = 1.0 parameter_dependencies = [sys2.p2 ~ p1 * 2.0] - sys1 = ODESystem( + sys1 = System( Equation[], t, [], [p1]; parameter_dependencies, name = :sys1, systems = [sys2]) # ensure that parameter_dependencies is type stable @@ -190,7 +190,7 @@ end @parameters p=2 (i::CallableFoo)(..) eqs = [D(y) ~ i(t) + p] - @named model = ODESystem(eqs, t, [y], [p, i]; + @named model = System(eqs, t, [y], [p, i]; parameter_dependencies = [i ~ CallableFoo(p)]) sys = structural_simplify(model) @@ -214,7 +214,7 @@ end D(x) ~ -x + u y ~ x z(k) ~ z(k - 2) + yd(k - 2)] - @test_throws ModelingToolkit.HybridSystemNotSupportedException @mtkbuild sys = ODESystem( + @test_throws ModelingToolkit.HybridSystemNotSupportedException @mtkbuild sys = System( eqs, t; parameter_dependencies = [kq => 2kp]) @test_skip begin @@ -224,7 +224,7 @@ end yd(k - 2) => 2.0]) @test_nowarn solve(prob, Tsit5()) - @mtkbuild sys = ODESystem(eqs, t; parameter_dependencies = [kq => 2kp], + @mtkbuild sys = System(eqs, t; parameter_dependencies = [kq => 2kp], discrete_events = [[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; @@ -256,7 +256,7 @@ end 0.1 * y, 0.1 * z] - @named sys = ODESystem(eqs, t) + @named sys = System(eqs, t) @named sdesys = SDESystem(sys, noiseeqs; parameter_dependencies = [ρ => 2σ]) sdesys = complete(sdesys) @test !(ρ in Set(parameters(sdesys))) @@ -267,7 +267,7 @@ end @test prob.ps[ρ] == 2prob.ps[σ] @test_nowarn solve(prob, SRIW1()) - @named sys = ODESystem(eqs, t) + @named sys = System(eqs, t) @named sdesys = SDESystem(sys, noiseeqs; parameter_dependencies = [ρ => 2σ], discrete_events = [[10.0] => [σ ~ 15.0]]) sdesys = complete(sdesys) @@ -346,7 +346,7 @@ end cb2 = [x ~ 4.0] => (affect1!, [], [p1, p2], [p1]) # triggers at t=-2+√7 cb3 = [1.0] => [p1 ~ 5.0] - @mtkbuild sys = ODESystem( + @mtkbuild sys = System( [D(x) ~ p1 * t + p2], t; parameter_dependencies = [p2 => 2p1] @@ -377,7 +377,7 @@ end @testset "Discovery of parameters from dependencies" begin @parameters p1 p2 @variables x(t) y(t) - @named sys = ODESystem([D(x) ~ y + p2], t; parameter_dependencies = [p2 ~ 2p1]) + @named sys = System([D(x) ~ y + p2], t; parameter_dependencies = [p2 ~ 2p1]) @test is_parameter(sys, p1) @named sys = NonlinearSystem([x * y^2 ~ y + p2]; parameter_dependencies = [p2 ~ 2p1]) @test is_parameter(sys, p1) diff --git a/test/precompile_test/ODEPrecompileTest.jl b/test/precompile_test/ODEPrecompileTest.jl index 81187c4075..911f45152e 100644 --- a/test/precompile_test/ODEPrecompileTest.jl +++ b/test/precompile_test/ODEPrecompileTest.jl @@ -13,7 +13,7 @@ function system(; kwargs...) D(y) ~ x * (ρ - z) - y, D(z) ~ x * y - β * z] - @named de = ODESystem(eqs, t) + @named de = System(eqs, t) de = complete(de) return ODEFunction(de, [x, y, z], [σ, ρ, β]; kwargs...) end diff --git a/test/problem_validation.jl b/test/problem_validation.jl index a0a7afaf3c..3db151bda5 100644 --- a/test/problem_validation.jl +++ b/test/problem_validation.jl @@ -6,7 +6,7 @@ using ModelingToolkit: t_nounits as t, D_nounits as D @variables X(t) @parameters p d eqs = [D(X) ~ p - d * X] - @mtkbuild osys = ODESystem(eqs, t) + @mtkbuild osys = System(eqs, t) p = "I accidentally renamed p" u0 = [X => 1.0] @@ -23,7 +23,7 @@ using ModelingToolkit: t_nounits as t, D_nounits as D @variables x(t) y(t) z(t) @parameters a b c d eqs = [D(x) ~ x * a, D(y) ~ y * c, D(z) ~ b + d] - @mtkbuild sys = ODESystem(eqs, t) + @mtkbuild sys = System(eqs, t) pmap = [a => 1, b => 2, c => 3, d => 4, "b" => 2] u0map = [x => 1, y => 2, z => 3] @test_throws InvalidKeyError ODEProblem(sys, u0map, (0.0, 1.0), pmap) diff --git a/test/reduction.jl b/test/reduction.jl index fa9029a652..4d786076be 100644 --- a/test/reduction.jl +++ b/test/reduction.jl @@ -28,7 +28,7 @@ eqs = [D(x) ~ σ * (y - x) 0 ~ a + z u ~ z + a] -lorenz1 = ODESystem(eqs, t, name = :lorenz1) +lorenz1 = System(eqs, t, name = :lorenz1) lorenz1_aliased = structural_simplify(lorenz1) io = IOBuffer(); @@ -53,13 +53,13 @@ eqs1 = [ u ~ x + y - z ] -lorenz = name -> ODESystem(eqs1, t, name = name) +lorenz = name -> System(eqs1, t, name = name) lorenz1 = lorenz(:lorenz1) state = TearingState(lorenz1) @test isempty(setdiff(state.fullvars, [D(x), F, y, x, D(y), u, z, D(z)])) lorenz2 = lorenz(:lorenz2) -@named connected = ODESystem( +@named connected = System( [s ~ a + lorenz1.x lorenz2.y ~ s lorenz1.u ~ lorenz2.F @@ -125,13 +125,13 @@ prob2 = SteadyStateProblem(reduced_system, u0, pp) let @variables x(t) u(t) y(t) @parameters a b c d - ol = ODESystem([D(x) ~ a * x + b * u; y ~ c * x + d * u], t, name = :ol) + ol = System([D(x) ~ a * x + b * u; y ~ c * x + d * u], t, name = :ol) @variables u_c(t) y_c(t) @parameters k_P - pc = ODESystem(Equation[u_c ~ k_P * y_c], t, name = :pc) + pc = System(Equation[u_c ~ k_P * y_c], t, name = :pc) connections = [pc.u_c ~ ol.u pc.y_c ~ ol.y] - @named connected = ODESystem(connections, t, systems = [ol, pc]) + @named connected = System(connections, t, systems = [ol, pc]) @test equations(connected) isa Vector{Equation} reduced_sys = structural_simplify(connected) ref_eqs = [D(ol.x) ~ ol.a * ol.x + ol.b * ol.u @@ -142,7 +142,7 @@ end # issue #889 let @variables x(t) - @named sys = ODESystem([0 ~ D(x) + x], t, [x], []) + @named sys = System([0 ~ D(x) + x], t, [x], []) #@test_throws ModelingToolkit.InvalidSystemException ODEProblem(sys, [1.0], (0, 10.0)) sys = structural_simplify(sys) #@test_nowarn ODEProblem(sys, [1.0], (0, 10.0)) @@ -188,7 +188,7 @@ eqs = [D(E) ~ k₋₁ * C - k₁ * E * S D(P) ~ k₂ * C E₀ ~ E + C] -@named sys = ODESystem(eqs, t, [E, C, S, P], [k₁, k₂, k₋₁, E₀]) +@named sys = System(eqs, t, [E, C, S, P], [k₁, k₂, k₋₁, E₀]) @test_throws ModelingToolkit.ExtraEquationsSystemException structural_simplify(sys) # Example 5 from Pantelides' original paper @@ -197,7 +197,7 @@ sts = collect(@variables x(t) u1(t) u2(t)) eqs = [0 ~ x + sin(u1 + u2) D(x) ~ x + y1 cos(x) ~ sin(y2)] -@named sys = ODESystem(eqs, t, sts, params) +@named sys = System(eqs, t, sts, params) @test_throws ModelingToolkit.InvalidSystemException structural_simplify(sys) # issue #963 @@ -214,7 +214,7 @@ eq = [v47 ~ v1 0 ~ i74 + i75 - i64 0 ~ i64 + i71] -@named sys0 = ODESystem(eq, t) +@named sys0 = System(eq, t) sys = structural_simplify(sys0) @test length(equations(sys)) == 1 eq = equations(tearing_substitution(sys))[1] @@ -232,7 +232,7 @@ eqs = [D(x) ~ σ * (y - x) 0 ~ a + z u ~ z + a] -lorenz1 = ODESystem(eqs, t, name = :lorenz1) +lorenz1 = System(eqs, t, name = :lorenz1) lorenz1_reduced, _ = structural_simplify(lorenz1, ([z], [])) @test z in Set(parameters(lorenz1_reduced)) @@ -241,7 +241,7 @@ vars = @variables x(t) y(t) z(t) eqs = [D(x) ~ x D(y) ~ y D(z) ~ t] -@named model = ODESystem(eqs, t) +@named model = System(eqs, t) sys = structural_simplify(model) Js = ModelingToolkit.jacobian_sparsity(sys) @test size(Js) == (3, 3) @@ -252,29 +252,29 @@ vars = @variables a(t) w(t) phi(t) eqs = [a ~ D(w) w ~ D(phi) w ~ sin(t)] -@named sys = ODESystem(eqs, t, vars, []) +@named sys = System(eqs, t, vars, []) ss = alias_elimination(sys) @test isempty(observed(ss)) @variables x(t) y(t) -@named sys = ODESystem([D(x) ~ 1 - x, +@named sys = System([D(x) ~ 1 - x, D(y) + D(x) ~ 0], t) new_sys = alias_elimination(sys) @test isempty(observed(new_sys)) -@named sys = ODESystem([D(x) ~ x, +@named sys = System([D(x) ~ x, D(y) + D(x) ~ 0], t) new_sys = alias_elimination(sys) @test isempty(observed(new_sys)) -@named sys = ODESystem([D(x) ~ 1 - x, +@named sys = System([D(x) ~ 1 - x, y + D(x) ~ 0], t) new_sys = alias_elimination(sys) @test isempty(observed(new_sys)) eqs = [x ~ 0 D(x) ~ x + y] -@named sys = ODESystem(eqs, t, [x, y], []) +@named sys = System(eqs, t, [x, y], []) ss = structural_simplify(sys) @test isempty(equations(ss)) @test sort(string.(observed(ss))) == ["x(t) ~ 0.0" @@ -282,7 +282,7 @@ ss = structural_simplify(sys) "y(t) ~ xˍt(t) - x(t)"] eqs = [D(D(x)) ~ -x] -@named sys = ODESystem(eqs, t, [x], []) +@named sys = System(eqs, t, [x], []) ss = alias_elimination(sys) @test length(equations(ss)) == length(unknowns(ss)) == 1 ss = structural_simplify(sys) diff --git a/test/scc_nonlinear_problem.jl b/test/scc_nonlinear_problem.jl index b2b326d090..67b531fddf 100644 --- a/test/scc_nonlinear_problem.jl +++ b/test/scc_nonlinear_problem.jl @@ -219,7 +219,7 @@ import ModelingToolkitStandardLibrary.Hydraulic.IsothermalCompressible as IC f ~ p * area m ~ rho * x * area] - return ODESystem(eqs, t, vars, pars; name, systems) + return System(eqs, t, vars, pars; name, systems) end systems = @named begin @@ -248,7 +248,7 @@ import ModelingToolkitStandardLibrary.Hydraulic.IsothermalCompressible as IC initialization_eqs = [mass.s ~ 0.0 mass.v ~ 0.0] - @mtkbuild sys = ODESystem(eqs, t, [], []; systems, initialization_eqs) + @mtkbuild sys = System(eqs, t, [], []; systems, initialization_eqs) prob = ODEProblem(sys, [], (0, 5)) sol = solve(prob) @test SciMLBase.successful_retcode(sol) diff --git a/test/sciml_problem_inputs.jl b/test/sciml_problem_inputs.jl index bfa560cda3..deae4b4772 100644 --- a/test/sciml_problem_inputs.jl +++ b/test/sciml_problem_inputs.jl @@ -38,7 +38,7 @@ begin ] # Create systems (without structural_simplify, since that might modify systems to affect intended tests). - osys = complete(ODESystem(diff_eqs, t; name = :osys)) + osys = complete(System(diff_eqs, t; name = :osys)) ssys = complete(SDESystem( diff_eqs, noise_eqs, t, [X, Y, Z], [kp, kd, k1, k2]; name = :ssys)) jsys = complete(JumpSystem(jumps, t, [X, Y, Z], [kp, kd, k1, k2]; name = :jsys)) diff --git a/test/sdesystem.jl b/test/sdesystem.jl index b031a2f5ab..96251ceb00 100644 --- a/test/sdesystem.jl +++ b/test/sdesystem.jl @@ -17,8 +17,8 @@ noiseeqs = [0.1 * x, 0.1 * y, 0.1 * z] -# ODESystem -> SDESystem shorthand constructor -@named sys = ODESystem(eqs, tt, [x, y, z], [σ, ρ, β]) +# System -> SDESystem shorthand constructor +@named sys = System(eqs, tt, [x, y, z], [σ, ρ, β]) @test SDESystem(sys, noiseeqs, name = :foo) isa SDESystem @named de = SDESystem(eqs, noiseeqs, tt, [x, y, z], [σ, ρ, β], tspan = (0.0, 10.0)) @@ -510,7 +510,7 @@ noiseeqs = [0.1 * x] @test observed(de) == [weight ~ x * 10] @test sol[weight] == 10 * sol[x] - @named ode = ODESystem(eqs, tt, [x], [α, β], observed = [weight ~ x * 10]) + @named ode = System(eqs, tt, [x], [α, β], observed = [weight ~ x * 10]) ode = complete(ode) odeprob = ODEProblem(ode, u0map, (0.0, 1.0), parammap) solode = solve(odeprob, Tsit5()) @@ -810,13 +810,13 @@ end @test prob[z] ≈ 2.0 end -@testset "SDESystem to ODESystem" begin +@testset "SDESystem to System" begin @variables x(t) y(t) z(t) @testset "Scalar noise" begin @named sys = SDESystem([D(x) ~ x, D(y) ~ y, z ~ x + y], [x, y, 3], t, [x, y, z], [], is_scalar_noise = true) - odesys = ODESystem(sys) - @test odesys isa ODESystem + odesys = System(sys) + @test odesys isa System vs = ModelingToolkit.vars(equations(odesys)) nbrownian = count( v -> ModelingToolkit.getvariabletype(v) == ModelingToolkit.BROWNIAN, vs) @@ -830,8 +830,8 @@ end @testset "Non-scalar vector noise" begin @named sys = SDESystem([D(x) ~ x, D(y) ~ y, z ~ x + y], [x, y, 0], t, [x, y, z], [], is_scalar_noise = false) - odesys = ODESystem(sys) - @test odesys isa ODESystem + odesys = System(sys) + @test odesys isa System vs = ModelingToolkit.vars(equations(odesys)) nbrownian = count( v -> ModelingToolkit.getvariabletype(v) == ModelingToolkit.BROWNIAN, vs) @@ -847,8 +847,8 @@ end 2y 2z 2x z+1 x+1 y+1] @named sys = SDESystem([D(x) ~ x, D(y) ~ y, D(z) ~ z], noiseeqs, t, [x, y, z], []) - odesys = ODESystem(sys) - @test odesys isa ODESystem + odesys = System(sys) + @test odesys isa System vs = ModelingToolkit.vars(equations(odesys)) nbrownian = count( v -> ModelingToolkit.getvariabletype(v) == ModelingToolkit.BROWNIAN, vs) @@ -892,7 +892,7 @@ end 0.1 * y, 0.1 * z] - @named sys = ODESystem(eqs, tt, [x, y, z], [σ, ρ, β]) + @named sys = System(eqs, tt, [x, y, z], [σ, ρ, β]) @named de = SDESystem(eqs, noiseeqs, tt, [x, y, z], [σ, ρ, β], tspan = (0.0, 10.0)) de = complete(de) diff --git a/test/serialization.jl b/test/serialization.jl index feb3c7e4e7..4f709393d4 100644 --- a/test/serialization.jl +++ b/test/serialization.jl @@ -3,7 +3,7 @@ using ModelingToolkit: t_nounits as t, D_nounits as D @variables x(t) -@named sys = ODESystem([D(x) ~ -0.5 * x], t, defaults = Dict(x => 1.0)) +@named sys = System([D(x) ~ -0.5 * x], t, defaults = Dict(x => 1.0)) sys = complete(sys) for prob in [ eval(ModelingToolkit.ODEProblem{false}(sys, nothing, nothing, @@ -35,7 +35,7 @@ all_obs = observables(ss) prob = ODEProblem(ss, [capacitor.v => 0.0], (0, 0.1)) sol = solve(prob, ImplicitEuler()) -## Check ODESystem with Observables ---------- +## Check System with Observables ---------- ss_exp = ModelingToolkit.toexpr(ss) ss_ = complete(eval(ss_exp)) prob_ = ODEProblem(ss_, [capacitor.v => 0.0], (0, 0.1)) diff --git a/test/split_parameters.jl b/test/split_parameters.jl index 18fdb49a48..39b242db08 100644 --- a/test/split_parameters.jl +++ b/test/split_parameters.jl @@ -68,7 +68,7 @@ function Sampled(; name, interp = Interpolator(Float64[], 0.0)) output.u ~ get_value(interpolator, t) ] - return ODESystem(eqs, t, vars, [interpolator]; name, systems) + return System(eqs, t, vars, [interpolator]; name, systems) end vars = @variables y(t) dy(t) ddy(t) @@ -80,7 +80,7 @@ eqs = [y ~ src.output.u D(dy) ~ ddy connect(src.output, int.input)] -@named sys = ODESystem(eqs, t, vars, []; systems = [int, src]) +@named sys = System(eqs, t, vars, []; systems = [int, src]) s = complete(sys) sys = structural_simplify(sys) prob = ODEProblem( @@ -107,7 +107,7 @@ eqs = [D(y) ~ dy * a D(dy) ~ ddy * b ddy ~ sin(t) * c] -@named model = ODESystem(eqs, t, vars, pars) +@named model = System(eqs, t, vars, pars) sys = structural_simplify(model; split = false) tspan = (0.0, t_end) @@ -134,7 +134,7 @@ using ModelingToolkit: connect "A wrapper function to make symbolic indexing easier" function wr(sys) - ODESystem(Equation[], ModelingToolkit.get_iv(sys), systems = [sys], name = :a_wrapper) + System(Equation[], ModelingToolkit.get_iv(sys), systems = [sys], name = :a_wrapper) end indexof(sym, syms) = findfirst(isequal(sym), syms) @@ -156,11 +156,11 @@ function SystemModel(u = nothing; name = :model) connect(inertia2.flange_a, spring.flange_b, damper.flange_b)] if u !== nothing push!(eqs, connect(torque.tau, u.output)) - return @named model = ODESystem(eqs, + return @named model = System(eqs, t; systems = [torque, inertia1, inertia2, spring, damper, u]) end - ODESystem(eqs, t; systems = [torque, inertia1, inertia2, spring, damper], + System(eqs, t; systems = [torque, inertia1, inertia2, spring, damper], name, guesses = [spring.flange_a.phi => 0.0]) end @@ -186,7 +186,7 @@ L = randn(1, 4) # Post-multiply by `C` to get the correct input to the controlle # @named input = RealInput(; nin = nin) # @named output = RealOutput(; nout = nout) # eqs = [output.u[i] ~ sum(K[i, j] * input.u[j] for j in 1:nin) for i in 1:nout] -# compose(ODESystem(eqs, t, [], []; name = name), [input, output]) +# compose(System(eqs, t, [], []; name = name), [input, output]) # end @named state_feedback = MatrixGain(K = -L) # Build negative feedback into the feedback matrix @@ -196,7 +196,7 @@ connections = [[state_feedback.input.u[i] ~ model_outputs[i] for i in 1:4] connect(d.output, :d, add.input1) connect(add.input2, state_feedback.output) connect(add.output, :u, model.torque.tau)] -@named closed_loop = ODESystem(connections, t, systems = [model, state_feedback, add, d]) +@named closed_loop = System(connections, t, systems = [model, state_feedback, add, d]) S = get_sensitivity(closed_loop, :u) @testset "Indexing MTKParameters with ParameterIndex" begin @@ -236,7 +236,7 @@ end (::Foo)(x) = 3x @variables x(t) @parameters fn(::Real) = _f1 - @mtkbuild sys = ODESystem(D(x) ~ fn(t), t) + @mtkbuild sys = System(D(x) ~ fn(t), t) @test is_parameter(sys, fn) @test ModelingToolkit.defaults(sys)[fn] == _f1 @@ -260,7 +260,7 @@ end interp = LinearInterpolation(ts .^ 2, ts; extrapolate = true) @variables x(t) @parameters (fn::typeof(interp))(..) - @mtkbuild sys = ODESystem(D(x) ~ fn(x), t) + @mtkbuild sys = System(D(x) ~ fn(x), t) @test is_parameter(sys, fn) getter = getp(sys, fn) prob = ODEProblem(sys, [x => 1.0], (0.0, 1.0), [fn => interp]) diff --git a/test/state_selection.jl b/test/state_selection.jl index a8d3e57773..b8bec5d7b7 100644 --- a/test/state_selection.jl +++ b/test/state_selection.jl @@ -7,7 +7,7 @@ eqs = [x1 + x2 + u1 ~ 0 x1 + x2 + x3 + u2 ~ 0 x1 + D(x3) + x4 + u3 ~ 0 2 * D(D(x1)) + D(D(x2)) + D(D(x3)) + D(x4) + u4 ~ 0] -@named sys = ODESystem(eqs, t) +@named sys = System(eqs, t) let dd = dummy_derivative(sys) has_dx1 = has_dx2 = false @@ -35,7 +35,7 @@ eqs = [D(x) ~ σ * (y - x) 0 ~ a + z u ~ z + a] -lorenz1 = ODESystem(eqs, t, name = :lorenz1) +lorenz1 = System(eqs, t, name = :lorenz1) let al1 = alias_elimination(lorenz1) let lss = partial_state_selection(al1) @test length(equations(lss)) == 2 @@ -47,13 +47,13 @@ let @connector function Fluid_port(; name, p = 101325.0, m = 0.0, T = 293.15) sts = @variables p(t) [guess = p] m(t) [guess = m, connect = Flow] T(t) [ guess = T, connect = Stream] - ODESystem(Equation[], t, sts, []; name = name) + System(Equation[], t, sts, []; name = name) end #this one is for latter @connector function Heat_port(; name, Q = 0.0, T = 293.15) sts = @variables T(t) [guess = T] Q(t) [guess = Q, connect = Flow] - ODESystem(Equation[], t, sts, []; name = name) + System(Equation[], t, sts, []; name = name) end # like ground but for fluid systems (fluid_port.m is expected to be zero in closed loop) @@ -62,7 +62,7 @@ let ps = @parameters p=p T_back=T_back eqs = [fluid_port.p ~ p fluid_port.T ~ T_back] - compose(ODESystem(eqs, t, [], ps; name = name), fluid_port) + compose(System(eqs, t, [], ps; name = name), fluid_port) end function Source(; name, delta_p = 100, T_feed = 293.15) @@ -73,7 +73,7 @@ let supply_port.p ~ return_port.p + delta_p supply_port.T ~ instream(supply_port.T) return_port.T ~ T_feed] - compose(ODESystem(eqs, t, [], ps; name = name), [supply_port, return_port]) + compose(System(eqs, t, [], ps; name = name), [supply_port, return_port]) end function Substation(; name, T_return = 343.15) @@ -84,7 +84,7 @@ let supply_port.p ~ return_port.p # zero pressure loss for now supply_port.T ~ instream(supply_port.T) return_port.T ~ T_return] - compose(ODESystem(eqs, t, [], ps; name = name), [supply_port, return_port]) + compose(System(eqs, t, [], ps; name = name), [supply_port, return_port]) end function Pipe(; name, L = 1000, d = 0.1, N = 100, rho = 1000, f = 1) @@ -98,7 +98,7 @@ let v * pi * d^2 / 4 * rho ~ fluid_port_a.m dp_z ~ abs(v) * v * 0.5 * rho * L / d * f # pressure loss D(v) * rho * L ~ (fluid_port_a.p - fluid_port_b.p - dp_z)] - compose(ODESystem(eqs, t, sts, ps; name = name), [fluid_port_a, fluid_port_b]) + compose(System(eqs, t, sts, ps; name = name), [fluid_port_a, fluid_port_b]) end function System(; name, L = 10.0) @named compensator = Compensator() @@ -113,7 +113,7 @@ let connect(supply_pipe.fluid_port_b, substation.supply_port) connect(substation.return_port, return_pipe.fluid_port_b) connect(return_pipe.fluid_port_a, source.return_port)] - compose(ODESystem(eqs, t, [], ps; name = name), subs) + compose(System(eqs, t, [], ps; name = name), subs) end @named system = System(L = 10) @@ -167,7 +167,7 @@ let D(rho_2) ~ (mo_1 - mo_3) / dx D(mo_2) ~ (Ek_1 - Ek_3 + p_1 - p_2) / dx - f / 2 / pipe_D * u_2 * u_2] - @named trans = ODESystem(eqs, t) + @named trans = System(eqs, t) sys = structural_simplify(trans) @@ -273,7 +273,7 @@ let # ---------------------------------------------------------------------------- # solution ------------------------------------------------------------------- - @named catapult = ODESystem(eqs, t, vars, params, defaults = defs) + @named catapult = System(eqs, t, vars, params, defaults = defs) sys = structural_simplify(catapult) prob = ODEProblem(sys, [], (0.0, 0.1), [l_2f => 0.55, damp => 1e7]; jac = true) @test solve(prob, Rodas4()).retcode == ReturnCode.Success diff --git a/test/static_arrays.jl b/test/static_arrays.jl index 61177e5ab2..61b9bb6b25 100644 --- a/test/static_arrays.jl +++ b/test/static_arrays.jl @@ -8,7 +8,7 @@ eqs = [D(D(x)) ~ σ * (y - x), D(y) ~ x * (ρ - z) - y, D(z) ~ x * y - β * z] -@named sys = ODESystem(eqs, t) +@named sys = System(eqs, t) sys = structural_simplify(sys) u0 = @SVector [D(x) => 2.0, diff --git a/test/steadystatesystems.jl b/test/steadystatesystems.jl index 4f1b5ed063..a99e84d9d2 100644 --- a/test/steadystatesystems.jl +++ b/test/steadystatesystems.jl @@ -6,7 +6,7 @@ using ModelingToolkit: t_nounits as t, D_nounits as D @parameters r @variables x(t) eqs = [D(x) ~ x^2 - r] -@named de = ODESystem(eqs, t) +@named de = System(eqs, t) de = complete(de) for factor in [1e-1, 1e0, 1e10], diff --git a/test/stream_connectors.jl b/test/stream_connectors.jl index 834ebce1a7..23c648d9e1 100644 --- a/test/stream_connectors.jl +++ b/test/stream_connectors.jl @@ -15,7 +15,7 @@ using ModelingToolkit: t_nounits as t, D_nounits as D P(t) = P end - ODESystem(Equation[], t, vars, pars; name = name) + System(Equation[], t, vars, pars; name = name) end @connector function TwoPhaseFluid(; name, R, B, V) @@ -32,7 +32,7 @@ end # equations --------------------------- eqs = Equation[m_flow ~ 0] - ODESystem(eqs, t, vars, pars; name) + System(eqs, t, vars, pars; name) end function MassFlowSource_h(; name, @@ -57,7 +57,7 @@ function MassFlowSource_h(; name, push!(eqns, port.m_flow ~ -m_flow_in) push!(eqns, port.h_outflow ~ h_in) - compose(ODESystem(eqns, t, vars, pars; name = name), subs) + compose(System(eqns, t, vars, pars; name = name), subs) end # Simplified components. @@ -74,7 +74,7 @@ function AdiabaticStraightPipe(; name, eqns = Equation[] push!(eqns, connect(port_a, port_b)) - sys = ODESystem(eqns, t, vars, pars; name = name) + sys = System(eqns, t, vars, pars; name = name) sys = compose(sys, subs) end @@ -97,7 +97,7 @@ function SmallBoundary_Ph(; name, push!(eqns, port1.P ~ P) push!(eqns, port1.h_outflow ~ h) - compose(ODESystem(eqns, t, vars, pars; name = name), subs) + compose(System(eqns, t, vars, pars; name = name), subs) end # N1M1 model and test code. @@ -114,7 +114,7 @@ function N1M1(; name, push!(eqns, connect(source.port1, port_a)) - sys = ODESystem(eqns, t, [], [], name = name) + sys = System(eqns, t, [], [], name = name) sys = compose(sys, subs) end @@ -126,13 +126,13 @@ end eqns = [connect(n1m1.port_a, pipe.port_a) connect(pipe.port_b, sink.port)] -@named sys = ODESystem(eqns, t) +@named sys = System(eqns, t) eqns = [domain_connect(fluid, n1m1.port_a) connect(n1m1.port_a, pipe.port_a) connect(pipe.port_b, sink.port)] -@named n1m1Test = ODESystem(eqns, t, [], []; systems = [fluid, n1m1, pipe, sink]) +@named n1m1Test = System(eqns, t, [], []; systems = [fluid, n1m1, pipe, sink]) @test_nowarn structural_simplify(n1m1Test) @unpack source, port_a = n1m1 @@ -192,7 +192,7 @@ function N1M2(; name, push!(eqns, connect(source.port1, port_a)) push!(eqns, connect(source.port1, port_b)) - sys = ODESystem(eqns, t, [], [], name = name) + sys = System(eqns, t, [], [], name = name) sys = compose(sys, subs) end @@ -203,7 +203,7 @@ end eqns = [connect(n1m2.port_a, sink1.port) connect(n1m2.port_b, sink2.port)] -@named sys = ODESystem(eqns, t) +@named sys = System(eqns, t) @named n1m2Test = compose(sys, n1m2, sink1, sink2) @test_nowarn structural_simplify(n1m2Test) @@ -218,7 +218,7 @@ eqns = [connect(n1m2.port_a, pipe1.port_a) connect(n1m2.port_b, pipe2.port_a) connect(pipe2.port_b, sink2.port)] -@named sys = ODESystem(eqns, t) +@named sys = System(eqns, t) @named n1m2AltTest = compose(sys, n1m2, pipe1, pipe2, sink1, sink2) @test_nowarn structural_simplify(n1m2AltTest) @@ -236,7 +236,7 @@ function N2M2(; name, push!(eqns, connect(port_a, pipe.port_a)) push!(eqns, connect(pipe.port_b, port_b)) - sys = ODESystem(eqns, t, [], [], name = name) + sys = System(eqns, t, [], [], name = name) sys = compose(sys, subs) end @@ -247,14 +247,14 @@ end eqns = [connect(source.port, n2m2.port_a) connect(n2m2.port_b, sink.port1)] -@named sys = ODESystem(eqns, t) +@named sys = System(eqns, t) @named n2m2Test = compose(sys, n2m2, source, sink) @test_nowarn structural_simplify(n2m2Test) # stream var @named sp1 = TwoPhaseFluidPort() @named sp2 = TwoPhaseFluidPort() -@named sys = ODESystem([connect(sp1, sp2)], t) +@named sys = System([connect(sp1, sp2)], t) sys_exp = expand_connections(compose(sys, [sp1, sp2])) @test ssort(equations(sys_exp)) == ssort([0 ~ -sp1.m_flow - sp2.m_flow 0 ~ sp1.m_flow @@ -266,14 +266,14 @@ sys_exp = expand_connections(compose(sys, [sp1, sp2])) # array var @connector function VecPin(; name) sts = @variables v(t)[1:2]=[1.0, 0.0] i(t)[1:2]=1.0 [connect = Flow] - ODESystem(Equation[], t, [sts...;], []; name = name) + System(Equation[], t, [sts...;], []; name = name) end @named vp1 = VecPin() @named vp2 = VecPin() @named vp3 = VecPin() -@named simple = ODESystem([connect(vp1, vp2, vp3)], t) +@named simple = System([connect(vp1, vp2, vp3)], t) sys = expand_connections(compose(simple, [vp1, vp2, vp3])) @test ssort(equations(sys)) == ssort([0 .~ collect(vp1.i) 0 .~ collect(vp2.i) @@ -287,7 +287,7 @@ sys = expand_connections(compose(simple, [vp1, vp2, vp3])) @connector function VectorHeatPort(; name, N = 100, T0 = 0.0, Q0 = 0.0) @variables (T(t))[1:N]=T0 (Q(t))[1:N]=Q0 [connect = Flow] - ODESystem(Equation[], t, [T; Q], []; name = name) + System(Equation[], t, [T; Q], []; name = name) end @test_nowarn @named a = VectorHeatPort() @@ -319,7 +319,7 @@ csys = complete(n1m1Test) # equations --------------------------- eqs = Equation[] - ODESystem(eqs, t, vars, pars; name, defaults = [dm => 0]) + System(eqs, t, vars, pars; name, defaults = [dm => 0]) end @connector function Fluid(; name, R, B, V) @@ -338,7 +338,7 @@ end dm ~ 0 ] - ODESystem(eqs, t, vars, pars; name) + System(eqs, t, vars, pars; name) end function StepSource(; P, name) @@ -358,7 +358,7 @@ function StepSource(; P, name) H.p ~ p_int * (t > 0.01) ] - ODESystem(eqs, t, vars, pars; name, systems) + System(eqs, t, vars, pars; name, systems) end function StaticVolume(; P, V, name) @@ -387,7 +387,7 @@ function StaticVolume(; P, V, name) H.p ~ p H.dm ~ drho * V] - ODESystem(eqs, t, vars, pars; name, systems, + System(eqs, t, vars, pars; name, systems, defaults = [vrho => rho_0 * (1 + p_int / H.bulk)]) end @@ -410,7 +410,7 @@ function PipeBase(; P, R, name) 0 ~ HA.dm + HB.dm domain_connect(HA, HB)] - ODESystem(eqs, t, vars, pars; name, systems) + System(eqs, t, vars, pars; name, systems) end function Pipe(; P, R, name) @@ -432,7 +432,7 @@ function Pipe(; P, R, name) eqs = [connect(v1.H, p12.HA, HA) connect(v2.H, p12.HB, HB)] - ODESystem(eqs, t, vars, pars; name, systems) + System(eqs, t, vars, pars; name, systems) end function TwoFluidSystem(; name) @@ -460,7 +460,7 @@ function TwoFluidSystem(; name) connect(source_b.H, pipe_b.HA) connect(pipe_b.HB, volume_b.H)] - ODESystem(eqs, t, vars, pars; name, systems) + System(eqs, t, vars, pars; name, systems) end @named two_fluid_system = TwoFluidSystem() @@ -498,7 +498,7 @@ function OneFluidSystem(; name) connect(source_b.H, pipe_b.HA) connect(pipe_b.HB, volume_b.H)] - ODESystem(eqs, t, vars, pars; name, systems) + System(eqs, t, vars, pars; name, systems) end @named one_fluid_system = OneFluidSystem() diff --git a/test/structural_transformation/index_reduction.jl b/test/structural_transformation/index_reduction.jl index f9d6037022..26f73db4e7 100644 --- a/test/structural_transformation/index_reduction.jl +++ b/test/structural_transformation/index_reduction.jl @@ -12,7 +12,7 @@ using ModelingToolkit: t_nounits as t, D_nounits as D eqs2 = [D(D(x)) ~ T * x, D(D(y)) ~ T * y - g, 0 ~ x^2 + y^2 - L^2] -pendulum2 = ODESystem(eqs2, t, [x, y, T], [L, g], name = :pendulum) +pendulum2 = System(eqs2, t, [x, y, T], [L, g], name = :pendulum) lowered_sys = ModelingToolkit.ode_order_lowering(pendulum2) lowered_eqs = [D(xˍt) ~ T * x, @@ -20,7 +20,7 @@ lowered_eqs = [D(xˍt) ~ T * x, D(x) ~ xˍt, D(y) ~ yˍt, 0 ~ x^2 + y^2 - L^2] -@test ODESystem(lowered_eqs, t, [xˍt, yˍt, x, y, T], [L, g], name = :pendulum) == +@test System(lowered_eqs, t, [xˍt, yˍt, x, y, T], [L, g], name = :pendulum) == lowered_sys @test isequal(equations(lowered_sys), lowered_eqs) @@ -30,7 +30,7 @@ eqs = [D(x) ~ w, D(w) ~ T * x, D(z) ~ T * y - g, 0 ~ x^2 + y^2 - L^2] -pendulum = ODESystem(eqs, t, [x, y, w, z, T], [L, g], name = :pendulum) +pendulum = System(eqs, t, [x, y, w, z, T], [L, g], name = :pendulum) state = TearingState(pendulum) @unpack graph, var_to_diff = state.structure @@ -57,7 +57,7 @@ idx1_pendulum = [D(x) ~ w, # 2x*D(D(x)) + 2*D(x)*D(x) + 2y*D(D(y)) + 2*D(y)*D(y) and # substitute the rhs 0 ~ 2x * (T * x) + 2 * xˍt * xˍt + 2y * (T * y - g) + 2 * yˍt * yˍt] -@named idx1_pendulum = ODESystem(idx1_pendulum, t, [x, y, w, z, xˍt, yˍt, T], [L, g]) +@named idx1_pendulum = System(idx1_pendulum, t, [x, y, w, z, xˍt, yˍt, T], [L, g]) first_order_idx1_pendulum = complete(ode_order_lowering(idx1_pendulum)) using OrdinaryDiffEq @@ -90,7 +90,7 @@ sol = solve(prob_auto, Rodas5()); eqs2 = [D(D(x)) ~ T * x, D(D(y)) ~ T * y - g, 0 ~ x^2 + y^2 - L^2] -pendulum2 = ODESystem(eqs2, t, [x, y, T], [L, g], name = :pendulum) +pendulum2 = System(eqs2, t, [x, y, T], [L, g], name = :pendulum) # Turn into a first order differential equation system first_order_sys = ModelingToolkit.ode_order_lowering(pendulum2) @@ -126,7 +126,7 @@ eqs = [D(x) ~ w, D(w) ~ T * x, D(z) ~ T * y - g, 0 ~ x^2 + y^2 - L^2] -pendulum = ODESystem(eqs, t, [x, y, w, z, T], [L, g], name = :pendulum) +pendulum = System(eqs, t, [x, y, w, z, T], [L, g], name = :pendulum) let pss_pendulum = partial_state_selection(pendulum) # This currently selects `T` rather than `x` at top level. Needs tearing priorities to fix. @@ -159,7 +159,7 @@ let eqs = [D(D(x)) ~ λ * x D(D(y)) ~ λ * y - g x^2 + y^2 ~ 1] - @named pend = ODESystem(eqs, t) + @named pend = System(eqs, t) sys = complete(structural_simplify(pend; dummy_derivative = false)) prob = ODEProblem( sys, [x => 1, y => 0, D(x) => 0.0], (0.0, 10.0), [g => 1], guesses = [λ => 0.0]) diff --git a/test/structural_transformation/tearing.jl b/test/structural_transformation/tearing.jl index e9cd92ec94..24083b3524 100644 --- a/test/structural_transformation/tearing.jl +++ b/test/structural_transformation/tearing.jl @@ -147,7 +147,7 @@ using ModelingToolkit, OrdinaryDiffEq, BenchmarkTools eqs = [D(x) ~ z * h 0 ~ x - y 0 ~ sin(z) + y - p * t] -@named daesys = ODESystem(eqs, t) +@named daesys = System(eqs, t) newdaesys = structural_simplify(daesys) @test equations(newdaesys) == [D(x) ~ z; 0 ~ y + sin(z) - p * t] @test equations(tearing_substitution(newdaesys)) == [D(x) ~ z; 0 ~ x + sin(z) - p * t] @@ -164,7 +164,7 @@ prob.f(du, u, pr, tt) @test du≈[u[2], u[1] + sin(u[2]) - pr * tt] atol=1e-5 # test the initial guess is respected -@named sys = ODESystem(eqs, t, defaults = Dict(z => NaN)) +@named sys = System(eqs, t, defaults = Dict(z => NaN)) infprob = ODEProblem(structural_simplify(sys), [x => 1.0], (0, 1.0), [p => 0.2]) infprob.f(du, infprob.u0, pr, tt) @test any(isnan, du) @@ -177,7 +177,7 @@ function Translational_Mass(; name, m = 1.0) eqs = [D(s) ~ v D(v) ~ a m * a ~ 0.0] - ODESystem(eqs, t, sts, ps; name = name) + System(eqs, t, sts, ps; name = name) end m = 1.0 @@ -185,7 +185,7 @@ m = 1.0 ms_eqs = [] -@named _ms_model = ODESystem(ms_eqs, t) +@named _ms_model = System(ms_eqs, t) @named ms_model = compose(_ms_model, [mass]) diff --git a/test/structural_transformation/utils.jl b/test/structural_transformation/utils.jl index b5335ad6b1..b9abbcfc77 100644 --- a/test/structural_transformation/utils.jl +++ b/test/structural_transformation/utils.jl @@ -18,7 +18,7 @@ eqs = [D(x) ~ w, D(w) ~ T * x, D(z) ~ T * y - g, 0 ~ x^2 + y^2 - L^2] -pendulum = ODESystem(eqs, t, [x, y, w, z, T], [L, g], name = :pendulum) +pendulum = System(eqs, t, [x, y, w, z, T], [L, g], name = :pendulum) state = TearingState(pendulum) StructuralTransformations.find_solvables!(state) sss = state.structure @@ -48,7 +48,7 @@ end @variables x(t) y(t)[1:2] z(t)[1:2] @parameters foo(::AbstractVector)[1:2] _tmp_fn(x) = 2x - @mtkbuild sys = ODESystem( + @mtkbuild sys = System( [D(x) ~ z[1] + z[2] + foo(z)[1], y[1] ~ 2t, y[2] ~ 3t, z ~ foo(y)], t) @test length(equations(sys)) == 1 @test length(observed(sys)) == 7 @@ -74,7 +74,7 @@ end val[] += 1 return [x, 2x] end - @mtkbuild sys = ODESystem([D(x) ~ y[1] + y[2], y ~ foo(x)], t) + @mtkbuild sys = System([D(x) ~ y[1] + y[2], y ~ foo(x)], t) @test length(equations(sys)) == 1 @test length(observed(sys)) == 3 prob = ODEProblem(sys, [x => 1.0], (0.0, 1.0), [foo => _tmp_fn2]) @@ -93,7 +93,7 @@ end @testset "CSE hack in equations(sys)" begin val[] = 0 @variables z(t)[1:2] - @mtkbuild sys = ODESystem( + @mtkbuild sys = System( [D(y) ~ foo(x), D(x) ~ sum(y), zeros(2) ~ foo(prod(z))], t) @test length(equations(sys)) == 5 @test length(observed(sys)) == 2 @@ -119,7 +119,7 @@ end @variables x(t) y(t)[1:2] z(t)[1:2] @parameters foo(::AbstractVector)[1:2] _tmp_fn(x) = 2x - @named sys = ODESystem( + @named sys = System( [D(x) ~ z[1] + z[2] + foo(z)[1], y[1] ~ 2t, y[2] ~ 3t, z ~ foo(y)], t) sys1 = structural_simplify(sys; cse_hack = false) @@ -140,7 +140,7 @@ end @variables x(t) y(t)[1:2] z(t)[1:2] w(t) @parameters foo(::AbstractVector)[1:2] _tmp_fn(x) = 2x - @named sys = ODESystem( + @named sys = System( [D(x) ~ z[1] + z[2] + foo(z)[1] + w, y[1] ~ 2t, y[2] ~ 3t, z ~ foo(y)], t) sys1 = structural_simplify(sys; cse_hack = false, fully_determined = false) @@ -160,7 +160,7 @@ end @testset "additional passes" begin @variables x(t) y(t) - @named sys = ODESystem([D(x) ~ x, y ~ x + t], t) + @named sys = System([D(x) ~ x, y ~ x + t], t) value = Ref(0) pass(sys; kwargs...) = (value[] += 1; return sys) structural_simplify(sys; additional_passes = [pass]) @@ -198,13 +198,13 @@ end end @testset "Requires simplified system" begin @variables x(t) y(t) - @named sys = ODESystem([D(x) ~ x, y ~ 2x], t) + @named sys = System([D(x) ~ x, y ~ 2x], t) sys = complete(sys) @test_throws ArgumentError map_variables_to_equations(sys) end @testset "`ODESystem`" begin @variables x(t) y(t) z(t) - @mtkbuild sys = ODESystem([D(x) ~ 2x + y, y ~ x + z, z^3 + x^3 ~ 12], t) + @mtkbuild sys = System([D(x) ~ 2x + y, y ~ x + z, z^3 + x^3 ~ 12], t) mapping = map_variables_to_equations(sys) @test mapping[x] == (D(x) ~ 2x + y) @test mapping[y] == (y ~ x + z) @@ -217,7 +217,7 @@ end eqs = [D(D(x)) ~ λ * x D(D(y)) ~ λ * y - g x^2 + y^2 ~ 1] - @mtkbuild sys = ODESystem(eqs, t) + @mtkbuild sys = System(eqs, t) mapping = map_variables_to_equations(sys) yt = default_toterm(unwrap(D(y))) diff --git a/test/substitute_component.jl b/test/substitute_component.jl index 9fb254136b..ad20dbcbc1 100644 --- a/test/substitute_component.jl +++ b/test/substitute_component.jl @@ -232,9 +232,9 @@ end @testset "Different indepvar" begin @independent_variables tt - @named empty = ODESystem(Equation[], t) - @named outer = ODESystem(Equation[], t; systems = [empty]) - @named empty = ODESystem(Equation[], tt) + @named empty = System(Equation[], t) + @named outer = System(Equation[], t; systems = [empty]) + @named empty = System(Equation[], tt) @test_throws ["independent variable"] substitute_component( outer, outer.empty => empty) end diff --git a/test/symbolic_events.jl b/test/symbolic_events.jl index 9099d32d14..dc8f059bf9 100644 --- a/test/symbolic_events.jl +++ b/test/symbolic_events.jl @@ -343,14 +343,14 @@ end ## -@named sys = ODESystem(eqs, t, continuous_events = [x ~ 1]) +@named sys = System(eqs, t, continuous_events = [x ~ 1]) @test getfield(sys, :continuous_events)[] == SymbolicContinuousCallback(Equation[x ~ 1], NULL_AFFECT) @test isequal(equations(getfield(sys, :continuous_events))[], x ~ 1) fsys = flatten(sys) @test isequal(equations(getfield(fsys, :continuous_events))[], x ~ 1) -@named sys2 = ODESystem([D(x) ~ 1], t, continuous_events = [x ~ 2], systems = [sys]) +@named sys2 = System([D(x) ~ 1], t, continuous_events = [x ~ 2], systems = [sys]) @test getfield(sys2, :continuous_events)[] == SymbolicContinuousCallback(Equation[x ~ 2], NULL_AFFECT) @test all(ModelingToolkit.continuous_events(sys2) .== [ @@ -426,7 +426,7 @@ sol = solve(prob, Tsit5(); abstol = 1e-14, reltol = 1e-14) @test minimum(t -> abs(t - 1), sol.t) < 1e-10 # test that the solver stepped at the first root @test minimum(t -> abs(t - 2), sol.t) < 1e-10 # test that the solver stepped at the second root -@named sys = ODESystem(eqs, t, continuous_events = [x ~ 1, x ~ 2]) # two root eqs using the same unknown +@named sys = System(eqs, t, continuous_events = [x ~ 1, x ~ 2]) # two root eqs using the same unknown sys = complete(sys) prob = ODEProblem(sys, Pair[], (0.0, 3.0)) @test get_callback(prob) isa ModelingToolkit.DiffEqCallbacks.VectorContinuousCallback @@ -440,8 +440,8 @@ sol = solve(prob, Tsit5(); abstol = 1e-14, reltol = 1e-14) root_eqs = [x ~ 0] affect = [v ~ -v] -@named ball = ODESystem([D(x) ~ v - D(v) ~ -9.8], t, continuous_events = root_eqs => affect) +@named ball = System([D(x) ~ v + D(v) ~ -9.8], t, continuous_events = root_eqs => affect) @test getfield(ball, :continuous_events)[] == SymbolicContinuousCallback(Equation[x ~ 0], Equation[v ~ -v]) @@ -461,7 +461,7 @@ sol = solve(prob, Tsit5()) continuous_events = [[x ~ 0] => [vx ~ -vx] [y ~ -1.5, y ~ 1.5] => [vy ~ -vy]] -@named ball = ODESystem( +@named ball = System( [D(x) ~ vx D(y) ~ vy D(vx) ~ -9.8 @@ -506,10 +506,10 @@ continuous_events = [ [x ~ 0] => [vx ~ -vx, vy ~ -vy] ] -@named ball = ODESystem([D(x) ~ vx - D(y) ~ vy - D(vx) ~ -1 - D(vy) ~ 0], t; continuous_events) +@named ball = System([D(x) ~ vx + D(y) ~ vy + D(vx) ~ -1 + D(vy) ~ 0], t; continuous_events) ball_nosplit = structural_simplify(ball) ball = structural_simplify(ball) @@ -536,7 +536,7 @@ eq = [vs ~ sin(2pi * t) D(v) ~ vs - v D(vmeasured) ~ 0.0] ev = [sin(20pi * t) ~ 0.0] => [vmeasured ~ v] -@named sys = ODESystem(eq, t, continuous_events = ev) +@named sys = System(eq, t, continuous_events = ev) sys = structural_simplify(sys) prob = ODEProblem(sys, zeros(2), (0.0, 5.1)) sol = solve(prob, Tsit5()) @@ -553,22 +553,22 @@ function Mass(; name, m = 1.0, p = 0, v = 0) ps = @parameters m = m sts = @variables pos(t)=p vel(t)=v eqs = Dₜ(pos) ~ vel - ODESystem(eqs, t, [pos, vel], ps; name) + System(eqs, t, [pos, vel], ps; name) end function Spring(; name, k = 1e4) ps = @parameters k = k @variables x(t) = 0 # Spring deflection - ODESystem(Equation[], t, [x], ps; name) + System(Equation[], t, [x], ps; name) end function Damper(; name, c = 10) ps = @parameters c = c @variables vel(t) = 0 - ODESystem(Equation[], t, [vel], ps; name) + System(Equation[], t, [vel], ps; name) end function SpringDamper(; name, k = false, c = false) spring = Spring(; name = :spring, k) damper = Damper(; name = :damper, c) - compose(ODESystem(Equation[], t; name), + compose(System(Equation[], t; name), spring, damper) end connect_sd(sd, m1, m2) = [sd.spring.x ~ m1.pos - m2.pos, sd.damper.vel ~ m1.vel - m2.vel] @@ -580,7 +580,7 @@ function Model(u, d = 0) eqs = [connect_sd(sd, mass1, mass2) Dₜ(mass1.vel) ~ (sd_force(sd) + u) / mass1.m Dₜ(mass2.vel) ~ (-sd_force(sd) + d) / mass2.m] - @named _model = ODESystem(eqs, t; observed = [y ~ mass2.pos]) + @named _model = System(eqs, t; observed = [y ~ mass2.pos]) @named model = compose(_model, mass1, mass2, sd) end model = Model(sin(30t)) @@ -610,7 +610,7 @@ let ∂ₜ = D eqs = [∂ₜ(A) ~ -k * A] - @named osys = ODESystem(eqs, t, [A], [k, t1, t2], discrete_events = [cb1, cb2]) + @named osys = System(eqs, t, [A], [k, t1, t2], discrete_events = [cb1, cb2]) u0 = [A => 1.0] p = [k => 0.0, t1 => 1.0, t2 => 2.0] tspan = (0.0, 4.0) @@ -619,7 +619,7 @@ let cond1a = (t == t1) affect1a = [A ~ A + 1, B ~ A] cb1a = cond1a => affect1a - @named osys1 = ODESystem(eqs, t, [A, B], [k, t1, t2], discrete_events = [cb1a, cb2]) + @named osys1 = System(eqs, t, [A, B], [k, t1, t2], discrete_events = [cb1a, cb2]) u0′ = [A => 1.0, B => 0.0] sol = testsol( osys1, u0′, p, tspan; tstops = [1.0, 2.0], check_length = false, paramtotest = k) @@ -628,11 +628,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 osys‵ = ODESystem(eqs, t, [A], [k], discrete_events = [cb1‵, cb2‵]) + @named osys‵ = System(eqs, t, [A], [k], discrete_events = [cb1‵, cb2‵]) testsol(osys‵, u0, p, tspan; paramtotest = k) # mixing discrete affects - @named osys3 = ODESystem(eqs, t, [A], [k, t1, t2], discrete_events = [cb1, cb2‵]) + @named osys3 = System(eqs, t, [A], [k, t1, t2], discrete_events = [cb1, cb2‵]) testsol(osys3, u0, p, tspan; tstops = [1.0], paramtotest = k) # mixing with a func affect @@ -641,22 +641,22 @@ let nothing end cb2‵‵ = [2.0] => (affect!, [], [k], [k], nothing) - @named osys4 = ODESystem(eqs, t, [A], [k, t1], discrete_events = [cb1, cb2‵‵]) + @named osys4 = System(eqs, t, [A], [k, t1], discrete_events = [cb1, cb2‵‵]) oprob4 = ODEProblem(complete(osys4), u0, tspan, p) testsol(osys4, u0, p, tspan; tstops = [1.0], paramtotest = k) # mixing with symbolic condition in the func affect cb2‵‵‵ = (t == t2) => (affect!, [], [k], [k], nothing) - @named osys5 = ODESystem(eqs, t, [A], [k, t1, t2], discrete_events = [cb1, cb2‵‵‵]) + @named osys5 = System(eqs, t, [A], [k, t1, t2], discrete_events = [cb1, cb2‵‵‵]) testsol(osys5, u0, p, tspan; tstops = [1.0, 2.0]) - @named osys6 = ODESystem(eqs, t, [A], [k, t1, t2], discrete_events = [cb2‵‵‵, cb1]) + @named osys6 = System(eqs, t, [A], [k, t1, t2], discrete_events = [cb2‵‵‵, cb1]) testsol(osys6, u0, p, tspan; tstops = [1.0, 2.0], paramtotest = k) # mix a continuous event too cond3 = A ~ 0.1 affect3 = [k ~ 0.0] cb3 = cond3 => affect3 - @named osys7 = ODESystem(eqs, t, [A], [k, t1, t2], discrete_events = [cb1, cb2‵‵‵], + @named osys7 = System(eqs, t, [A], [k, t1, t2], discrete_events = [cb1, cb2‵‵‵], continuous_events = [cb3]) sol = testsol(osys7, u0, p, (0.0, 10.0); tstops = [1.0, 2.0]) @test isapprox(sol(10.0)[1], 0.1; atol = 1e-10, rtol = 1e-10) @@ -816,19 +816,19 @@ let ps = @parameters k=k Θ=0.5 eqs = [D(x) ~ v, D(v) ~ -k * x + F] ev = [x ~ Θ] => [x ~ 1.0, v ~ 0.0] - ODESystem(eqs, t, sts, ps, continuous_events = [ev]; name) + System(eqs, t, sts, ps, continuous_events = [ev]; name) end @named oscce = oscillator_ce() eqs = [oscce.F ~ 0] - @named eqs_sys = ODESystem(eqs, t) + @named eqs_sys = System(eqs, t) @named oneosc_ce = compose(eqs_sys, oscce) oneosc_ce_simpl = structural_simplify(oneosc_ce) prob = ODEProblem(oneosc_ce_simpl, [], (0.0, 2.0), []) sol = solve(prob, Tsit5(), saveat = 0.1) - @test typeof(oneosc_ce_simpl) == ODESystem + @test typeof(oneosc_ce_simpl) == System @test sol[oscce.x, 6] < 1.0 # test whether x(t) decreases over time @test sol[oscce.x, 18] > 0.5 # test whether event happened end @@ -844,7 +844,7 @@ end [c1 ~ 0], (record_crossings, [c1 => :v], [], [], cr1)) evt2 = ModelingToolkit.SymbolicContinuousCallback( [c2 ~ 0], (record_crossings, [c2 => :v], [], [], cr2)) - @named trigsys = ODESystem(eqs, t; continuous_events = [evt1, evt2]) + @named trigsys = System(eqs, t; continuous_events = [evt1, evt2]) trigsys_ss = structural_simplify(trigsys) prob = ODEProblem(trigsys_ss, [], (0.0, 2π)) sol = solve(prob, Tsit5()) @@ -866,7 +866,7 @@ end evt2 = ModelingToolkit.SymbolicContinuousCallback( [c2 ~ 0], (record_crossings, [c2 => :v], [], [], cr2p); affect_neg = (record_crossings, [c2 => :v], [], [], cr2n)) - @named trigsys = ODESystem(eqs, t; continuous_events = [evt1, evt2]) + @named trigsys = System(eqs, t; continuous_events = [evt1, evt2]) trigsys_ss = structural_simplify(trigsys) prob = ODEProblem(trigsys_ss, [], (0.0, 2π)) sol = solve(prob, Tsit5(); dtmax = 0.01) @@ -890,7 +890,7 @@ end [c1 ~ 0], (record_crossings, [c1 => :v], [], [], cr1p); affect_neg = nothing) evt2 = ModelingToolkit.SymbolicContinuousCallback( [c2 ~ 0], (record_crossings, [c2 => :v], [], [], cr2p); affect_neg = nothing) - @named trigsys = ODESystem(eqs, t; continuous_events = [evt1, evt2]) + @named trigsys = System(eqs, t; continuous_events = [evt1, evt2]) trigsys_ss = structural_simplify(trigsys) prob = ODEProblem(trigsys_ss, [], (0.0, 2π)) sol = solve(prob, Tsit5(); dtmax = 0.01) @@ -909,7 +909,7 @@ end evt2 = ModelingToolkit.SymbolicContinuousCallback( [c2 ~ 0], (record_crossings, [c2 => :v], [], [], cr2p); affect_neg = (record_crossings, [c2 => :v], [], [], cr2n)) - @named trigsys = ODESystem(eqs, t; continuous_events = [evt1, evt2]) + @named trigsys = System(eqs, t; continuous_events = [evt1, evt2]) trigsys_ss = structural_simplify(trigsys) prob = ODEProblem(trigsys_ss, [], (0.0, 2π)) sol = solve(prob, Tsit5(); dtmax = 0.01) @@ -933,7 +933,7 @@ end evt2 = ModelingToolkit.SymbolicContinuousCallback( [c2 ~ 0], (record_crossings, [c2 => :v], [], [], cr2); rootfind = SciMLBase.RightRootFind) - @named trigsys = ODESystem(eqs, t; continuous_events = [evt1, evt2]) + @named trigsys = System(eqs, t; continuous_events = [evt1, evt2]) trigsys_ss = structural_simplify(trigsys) prob = ODEProblem(trigsys_ss, [], (0.0, 2π)) sol = solve(prob, Tsit5(); dtmax = 0.01) @@ -953,7 +953,7 @@ end evt2 = ModelingToolkit.SymbolicContinuousCallback( [c2 ~ 0], (record_crossings, [c2 => :v], [], [], cr2); rootfind = SciMLBase.RightRootFind) - @named trigsys = ODESystem(eqs, t; continuous_events = [evt1, evt2]) + @named trigsys = System(eqs, t; continuous_events = [evt1, evt2]) trigsys_ss = structural_simplify(trigsys) prob = ODEProblem(trigsys_ss, [], (0.0, 2π)) sol = solve(prob, Tsit5()) @@ -971,7 +971,7 @@ end evt2 = ModelingToolkit.SymbolicContinuousCallback( [c2 ~ 0], (record_crossings, [c2 => :v], [], [], cr2); rootfind = SciMLBase.RightRootFind) - @named trigsys = ODESystem(eqs, t; continuous_events = [evt2, evt1]) + @named trigsys = System(eqs, t; continuous_events = [evt2, evt1]) trigsys_ss = structural_simplify(trigsys) prob = ODEProblem(trigsys_ss, [], (0.0, 2π)) sol = solve(prob, Tsit5()) @@ -1073,7 +1073,7 @@ end cb2 = [x ~ 0.5] => (save_affect!, [], [b], [b], nothing) cb3 = 1.0 => [c ~ t] - @mtkbuild sys = ODESystem(D(x) ~ cos(t), t, [x], [a, b, c]; + @mtkbuild sys = System(D(x) ~ cos(t), t, [x], [a, b, c]; continuous_events = [cb1, cb2], discrete_events = [cb3]) prob = ODEProblem(sys, [x => 1.0], (0.0, 2pi), [a => 1.0, b => 2.0, c => 0.0]) @test sort(canonicalize(Discrete(), prob.p)[1]) == [0.0, 1.0, 2.0] @@ -1100,7 +1100,7 @@ end ModelingToolkit.ImperativeAffect(modified = (; furnace_on)) do x, o, i, c @set! x.furnace_on = true end) - @named sys = ODESystem( + @named sys = System( eqs, t, [temp], params; continuous_events = [furnace_off, furnace_enable]) ss = structural_simplify(sys) prob = ODEProblem(ss, [temp => 0.0, furnace_on => true], (0.0, 100.0)) @@ -1120,7 +1120,7 @@ end ModelingToolkit.ImperativeAffect(modified = (; furnace_on)) do x, o, c, i @set! x.furnace_on = true end) - @named sys = ODESystem( + @named sys = System( eqs, t, [temp], params; continuous_events = [furnace_off, furnace_enable]) ss = structural_simplify(sys) prob = ODEProblem(ss, [temp => 0.0, furnace_on => true], (0.0, 100.0)) @@ -1142,7 +1142,7 @@ end modified = (; furnace_on), observed = (; furnace_on)) do x, o, c, i @set! x.furnace_on = false end) - @named sys = ODESystem(eqs, t, [temp], params; continuous_events = [furnace_off]) + @named sys = System(eqs, t, [temp], params; continuous_events = [furnace_off]) ss = structural_simplify(sys) @test_logs (:warn, "The symbols Any[:furnace_on] are declared as both observed and modified; this is a code smell because it becomes easy to confuse them and assign/not assign a value.") prob=ODEProblem( @@ -1158,7 +1158,7 @@ end modified = (; furnace_on, tempsq), observed = (; furnace_on)) do x, o, c, i @set! x.furnace_on = false end) - @named sys = ODESystem( + @named sys = System( eqs, t, [temp, tempsq], params; continuous_events = [furnace_off]) ss = structural_simplify(sys) @test_throws "refers to missing variable(s)" prob=ODEProblem( @@ -1171,7 +1171,7 @@ end observed = (; furnace_on, not_actually_here)) do x, o, c, i @set! x.furnace_on = false end) - @named sys = ODESystem( + @named sys = System( eqs, t, [temp, tempsq], params; continuous_events = [furnace_off]) ss = structural_simplify(sys) @test_throws "refers to missing variable(s)" prob=ODEProblem( @@ -1183,7 +1183,7 @@ end observed = (; furnace_on)) do x, o, c, i return (; fictional2 = false) end) - @named sys = ODESystem( + @named sys = System( eqs, t, [temp, tempsq], params; continuous_events = [furnace_off]) ss = structural_simplify(sys) prob = ODEProblem( @@ -1243,7 +1243,7 @@ end @set! x.cnt += decoder(x.hA, x.hB, o.qA, x.qB) x end; rootfind = SciMLBase.RightRootFind) - @named sys = ODESystem( + @named sys = System( eqs, t, [theta, omega], params; continuous_events = [qAevt, qBevt]) ss = structural_simplify(sys) prob = ODEProblem(ss, [theta => 1e-5], (0.0, pi)) @@ -1258,7 +1258,7 @@ end f = (i, u, p, c) -> seen = true, sts = [], pars = [], discretes = []) cb1 = ModelingToolkit.SymbolicContinuousCallback( [x ~ 0], Equation[], initialize = [x ~ 1.5], finalize = f) - @mtkbuild sys = ODESystem(D(x) ~ -1, t, [x], []; continuous_events = [cb1]) + @mtkbuild sys = System(D(x) ~ -1, t, [x], []; continuous_events = [cb1]) prob = ODEProblem(sys, [x => 1.0], (0.0, 2), []) sol = solve(prob, Tsit5(); dtmax = 0.01) @test sol[x][1] ≈ 1.0 @@ -1279,7 +1279,7 @@ end f = (i, u, p, c) -> finaled = true, sts = [], pars = [], discretes = []) cb2 = ModelingToolkit.SymbolicContinuousCallback( [x ~ 0.1], Equation[], initialize = a, finalize = b) - @mtkbuild sys = ODESystem(D(x) ~ -1, t, [x], []; continuous_events = [cb1, cb2]) + @mtkbuild sys = System(D(x) ~ -1, t, [x], []; continuous_events = [cb1, cb2]) prob = ODEProblem(sys, [x => 1.0], (0.0, 2), []) sol = solve(prob, Tsit5()) @test sol[x][1] ≈ 1.0 @@ -1293,7 +1293,7 @@ end finaled = false cb3 = ModelingToolkit.SymbolicDiscreteCallback( 1.0, [x ~ 2], initialize = a, finalize = b) - @mtkbuild sys = ODESystem(D(x) ~ -1, t, [x], []; discrete_events = [cb3]) + @mtkbuild sys = System(D(x) ~ -1, t, [x], []; discrete_events = [cb3]) prob = ODEProblem(sys, [x => 1.0], (0.0, 2), []) sol = solve(prob, Tsit5()) @test inited == true @@ -1306,7 +1306,7 @@ end inited = false finaled = false cb3 = ModelingToolkit.SymbolicDiscreteCallback(1.0, f, initialize = a, finalize = b) - @mtkbuild sys = ODESystem(D(x) ~ -1, t, [x], []; discrete_events = [cb3]) + @mtkbuild sys = System(D(x) ~ -1, t, [x], []; discrete_events = [cb3]) prob = ODEProblem(sys, [x => 1.0], (0.0, 2), []) sol = solve(prob, Tsit5()) @test seen == true @@ -1317,7 +1317,7 @@ end inited = false finaled = false cb3 = ModelingToolkit.SymbolicDiscreteCallback([1.0], f, initialize = a, finalize = b) - @mtkbuild sys = ODESystem(D(x) ~ -1, t, [x], []; discrete_events = [cb3]) + @mtkbuild sys = System(D(x) ~ -1, t, [x], []; discrete_events = [cb3]) prob = ODEProblem(sys, [x => 1.0], (0.0, 2), []) sol = solve(prob, Tsit5()) @test seen == true @@ -1330,7 +1330,7 @@ end finaled = false cb3 = ModelingToolkit.SymbolicDiscreteCallback( t == 1.0, f, initialize = a, finalize = b) - @mtkbuild sys = ODESystem(D(x) ~ -1, t, [x], []; discrete_events = [cb3]) + @mtkbuild sys = System(D(x) ~ -1, t, [x], []; discrete_events = [cb3]) prob = ODEProblem(sys, [x => 1.0], (0.0, 2), []) sol = solve(prob, Tsit5(); tstops = 1.0) @test seen == true @@ -1342,17 +1342,17 @@ end @variables x(t) [irreducible = true] y(t) [irreducible = true] eqs = [x ~ y, D(x) ~ -1] cb = [x ~ 0.0] => [x ~ 0, y ~ 1] - @mtkbuild pend = ODESystem(eqs, t; continuous_events = [cb]) + @mtkbuild pend = System(eqs, t; continuous_events = [cb]) prob = ODEProblem(pend, [x => 1], (0.0, 3.0), guesses = [y => x]) @test_throws "DAE initialization failed" solve(prob, Rodas5()) cb = [x ~ 0.0] => [y ~ 1] - @mtkbuild pend = ODESystem(eqs, t; continuous_events = [cb]) + @mtkbuild pend = System(eqs, t; continuous_events = [cb]) prob = ODEProblem(pend, [x => 1], (0.0, 3.0), guesses = [y => x]) @test_broken !SciMLBase.successful_retcode(solve(prob, Rodas5())) cb = [x ~ 0.0] => [x ~ 1, y ~ 1] - @mtkbuild pend = ODESystem(eqs, t; continuous_events = [cb]) + @mtkbuild pend = System(eqs, t; continuous_events = [cb]) prob = ODEProblem(pend, [x => 1], (0.0, 3.0), guesses = [y => x]) @test all(≈(0.0; atol = 1e-9), solve(prob, Rodas5())[[x, y]][end]) end @@ -1395,7 +1395,7 @@ end @set! m.x = 0.0 return m end - return ODESystem(eqs, t, vars, params; name = name, + return System(eqs, t, vars, params; name = name, continuous_events = [[x ~ max_time] => reset]) end @@ -1409,19 +1409,19 @@ end eqs = reduce(vcat, Symbolics.scalarize.([ D(x) ~ 1.0 ])) - return ODESystem(eqs, t, vars, params; name = name) # note no event + return System(eqs, t, vars, params; name = name) # note no event end @named wd1 = weird1(0.021) @named wd2 = weird2(0.021) - sys1 = structural_simplify(ODESystem([], t; name = :parent, + sys1 = structural_simplify(System([], t; name = :parent, discrete_events = [0.01 => ModelingToolkit.ImperativeAffect( modified = (; θs = reduce(vcat, [[wd1.θ]])), ctx = [1]) do m, o, c, i @set! m.θs[1] = c[] += 1 end], systems = [wd1])) - sys2 = structural_simplify(ODESystem([], t; name = :parent, + sys2 = structural_simplify(System([], t; name = :parent, discrete_events = [0.01 => ModelingToolkit.ImperativeAffect( modified = (; θs = reduce(vcat, [[wd2.θ]])), ctx = [1]) do m, o, c, i @set! m.θs[1] = c[] += 1 diff --git a/test/symbolic_indexing_interface.jl b/test/symbolic_indexing_interface.jl index 8b3da5fd72..85deba82c9 100644 --- a/test/symbolic_indexing_interface.jl +++ b/test/symbolic_indexing_interface.jl @@ -2,11 +2,11 @@ using ModelingToolkit, SymbolicIndexingInterface, SciMLBase using ModelingToolkit: t_nounits as t, D_nounits as D, ParameterIndex using SciMLStructures: Tunable -@testset "ODESystem" begin +@testset "System" begin @parameters a b @variables x(t)=1.0 y(t)=2.0 xy(t) eqs = [D(x) ~ a * y + t, D(y) ~ b * t] - @named odesys = ODESystem(eqs, t, [x, y], [a, b]; observed = [xy ~ x + y]) + @named odesys = System(eqs, t, [x, y], [a, b]; observed = [xy ~ x + y]) odesys = complete(odesys) @test SymbolicIndexingInterface.supports_tuple_observed(odesys) @test all(is_variable.((odesys,), [x, y, 1, 2, :x, :y])) @@ -44,7 +44,7 @@ using SciMLStructures: Tunable @test getter(prob) isa Tuple @test_nowarn @inferred getter(prob) - @named odesys = ODESystem( + @named odesys = System( eqs, t, [x, y], [a, b]; defaults = [xy => 3.0], observed = [xy ~ x + y]) odesys = complete(odesys) @test default_values(odesys)[xy] == 3.0 @@ -77,7 +77,7 @@ end # D(x) ~ -x + u # y ~ x] -# @mtkbuild cl = ODESystem(eqs, t) +# @mtkbuild cl = System(eqs, t) # partition1_params = [Hold(ud1), Sample(t, dt)(y), ud1, yd1] # partition2_params = [Hold(ud2), Sample(t, dt2)(y), ud2, yd2] # @test all( @@ -186,7 +186,7 @@ using SymbolicIndexingInterface @parameters p1[1:2]=[1.0, 2.0] p2[1:2]=[0.0, 0.0] @variables x(t) = 0 -@named sys = ODESystem( +@named sys = System( [D(x) ~ sum(p1) * t + sum(p2)], t; ) @@ -197,7 +197,7 @@ get_dep = @test_nowarn getu(prob, 2p1) @testset "Observed functions with variables as `Symbol`s" begin @variables x(t) y(t) z(t)[1:2] @parameters p1 p2[1:2, 1:2] - @mtkbuild sys = ODESystem([D(x) ~ x * t + p1, y ~ 2x, D(z) ~ p2 * z], t) + @mtkbuild sys = System([D(x) ~ x * t + p1, y ~ 2x, D(z) ~ p2 * z], t) prob = ODEProblem( sys, [x => 1.0, z => ones(2)], (0.0, 1.0), [p1 => 2.0, p2 => ones(2, 2)]) @test getu(prob, x)(prob) == getu(prob, :x)(prob) @@ -210,7 +210,7 @@ end @testset "Parameter dependencies as symbols" begin @variables x(t) = 1.0 @parameters a=1 b - @named model = ODESystem(D(x) ~ x + a - b, t, parameter_dependencies = [b ~ a + 1]) + @named model = System(D(x) ~ x + a - b, t, parameter_dependencies = [b ~ a + 1]) sys = complete(model) prob = ODEProblem(sys, [], (0.0, 1.0)) @test prob.ps[b] == prob.ps[:b] @@ -219,7 +219,7 @@ end @testset "`get_all_timeseries_indexes` with non-split systems" begin @variables x(t) y(t) z(t) @parameters a - @named sys = ODESystem([D(x) ~ a * x, y ~ 2x, z ~ 0.0], t) + @named sys = System([D(x) ~ a * x, y ~ 2x, z ~ 0.0], t) sys = structural_simplify(sys, split = false) for sym in [x, y, z, x + y, x + a, y / x] @test only(get_all_timeseries_indexes(sys, sym)) == ContinuousTimeseries() @@ -231,7 +231,7 @@ end @variables x(t)[1:2] @parameters p(t)[1:2, 1:2] ev = [x[1] ~ 2.0] => [p ~ -ones(2, 2)] - @mtkbuild sys = ODESystem(D(x) ~ p * x, t; continuous_events = [ev]) + @mtkbuild sys = System(D(x) ~ p * x, t; continuous_events = [ev]) p = ModelingToolkit.unwrap(p) @test timeseries_parameter_index(sys, p) === ParameterTimeseriesIndex(1, (1, 1)) @test timeseries_parameter_index(sys, p[1, 1]) === diff --git a/test/symbolic_parameters.jl b/test/symbolic_parameters.jl index a29090912c..8949d09688 100644 --- a/test/symbolic_parameters.jl +++ b/test/symbolic_parameters.jl @@ -59,7 +59,7 @@ vars = @variables(begin end) der = Differential(t) eqs = [der(x) ~ x] -@named sys = ODESystem(eqs, t, vars, [x0]) +@named sys = System(eqs, t, vars, [x0]) sys = complete(sys) pars = [ x0 => 10.0 diff --git a/test/test_variable_metadata.jl b/test/test_variable_metadata.jl index aaf6addb59..7ce45bb211 100644 --- a/test/test_variable_metadata.jl +++ b/test/test_variable_metadata.jl @@ -123,7 +123,7 @@ Dₜ = Differential(t) @parameters k2 [tunable = false] eqs = [Dₜ(x) ~ (-k2 * x + k * u) / T y ~ x] -sys = ODESystem(eqs, t, name = :tunable_first_order) +sys = System(eqs, t, name = :tunable_first_order) unk_meta = ModelingToolkit.dump_unknowns(sys) @test length(unk_meta) == 3 @test all(iszero, meta.default for meta in unk_meta) @@ -169,14 +169,14 @@ sp = Set(p) @independent_variables t @variables u(t) [description = "A short description of u"] @parameters p [description = "A description of p"] -@named sys = ODESystem([u ~ p], t) +@named sys = System([u ~ p], t) @test_nowarn show(stdout, "text/plain", sys) # Defaults, guesses overridden by system, parameter dependencies @variables x(t)=1.0 y(t) [guess = 1.0] @parameters p=2.0 q -@named sys = ODESystem(Equation[], t, [x, y], [p]; defaults = Dict(x => 2.0, p => 3.0), +@named sys = System(Equation[], t, [x, y], [p]; defaults = Dict(x => 2.0, p => 3.0), guesses = Dict(y => 2.0), parameter_dependencies = [q => 2p]) unks_meta = ModelingToolkit.dump_unknowns(sys) unks_meta = Dict([ModelingToolkit.getname(meta.var) => meta for meta in unks_meta]) diff --git a/test/units.jl b/test/units.jl index ff0cd42ac3..267b526c4b 100644 --- a/test/units.jl +++ b/test/units.jl @@ -44,42 +44,42 @@ D = Differential(t) eqs = [D(E) ~ P - E / τ 0 ~ P] @test UMT.validate(eqs) -@named sys = ODESystem(eqs, t) +@named sys = System(eqs, t) @test !UMT.validate(D(D(E)) ~ P) @test !UMT.validate(0 ~ P + E * τ) # Disabling unit validation/checks selectively -@test_throws MT.ArgumentError ODESystem(eqs, t, [E, P, t], [τ], name = :sys) -ODESystem(eqs, t, [E, P, t], [τ], name = :sys, checks = MT.CheckUnits) +@test_throws MT.ArgumentError System(eqs, t, [E, P, t], [τ], name = :sys) +System(eqs, t, [E, P, t], [τ], name = :sys, checks = MT.CheckUnits) eqs = [D(E) ~ P - E / τ 0 ~ P + E * τ] -@test_throws MT.ValidationError ODESystem(eqs, t, name = :sys, checks = MT.CheckAll) -@test_throws MT.ValidationError ODESystem(eqs, t, name = :sys, checks = true) -ODESystem(eqs, t, name = :sys, checks = MT.CheckNone) -ODESystem(eqs, t, name = :sys, checks = false) -@test_throws MT.ValidationError ODESystem(eqs, t, name = :sys, +@test_throws MT.ValidationError System(eqs, t, name = :sys, checks = MT.CheckAll) +@test_throws MT.ValidationError System(eqs, t, name = :sys, checks = true) +System(eqs, t, name = :sys, checks = MT.CheckNone) +System(eqs, t, name = :sys, checks = false) +@test_throws MT.ValidationError System(eqs, t, name = :sys, checks = MT.CheckComponents | MT.CheckUnits) -@named sys = ODESystem(eqs, t, checks = MT.CheckComponents) -@test_throws MT.ValidationError ODESystem(eqs, t, [E, P, t], [τ], name = :sys, +@named sys = System(eqs, t, checks = MT.CheckComponents) +@test_throws MT.ValidationError System(eqs, t, [E, P, t], [τ], name = :sys, checks = MT.CheckUnits) # connection validation @connector function Pin(; name) sts = @variables(v(t)=1.0, [unit = u"V"], i(t)=1.0, [unit = u"A", connect = Flow]) - ODESystem(Equation[], t, sts, []; name = name) + System(Equation[], t, sts, []; name = name) end @connector function OtherPin(; name) sts = @variables(v(t)=1.0, [unit = u"mV"], i(t)=1.0, [unit = u"mA", connect = Flow]) - ODESystem(Equation[], t, sts, []; name = name) + System(Equation[], t, sts, []; name = name) end @connector function LongPin(; name) sts = @variables(v(t)=1.0, [unit = u"V"], i(t)=1.0, [unit = u"A", connect = Flow], x(t)=1.0, [unit = NoUnits]) - ODESystem(Equation[], t, sts, []; name = name) + System(Equation[], t, sts, []; name = name) end @named p1 = Pin() @named p2 = Pin() @@ -91,8 +91,8 @@ bad_length_eqs = [connect(op, lp)] @test UMT.validate(good_eqs) @test !UMT.validate(bad_eqs) @test !UMT.validate(bad_length_eqs) -@named sys = ODESystem(good_eqs, t, [], []) -@test_throws MT.ValidationError ODESystem(bad_eqs, t, [], []; name = :sys) +@named sys = System(good_eqs, t, [], []) +@test_throws MT.ValidationError System(bad_eqs, t, [], []; name = :sys) # Array variables @independent_variables t [unit = u"s"] @@ -100,7 +100,7 @@ bad_length_eqs = [connect(op, lp)] @variables x(t)[1:3] [unit = u"m"] D = Differential(t) eqs = D.(x) .~ v -ODESystem(eqs, t, name = :sys) +System(eqs, t, name = :sys) # Nonlinear system @parameters a [unit = u"kg"^-1] @@ -139,12 +139,12 @@ noiseeqs = [0.1u"MW" 0.1u"MW" D = Differential(t) eqs = [D(L) ~ v, V ~ L^3] -@named sys = ODESystem(eqs, t) +@named sys = System(eqs, t) sys_simple = structural_simplify(sys) eqs = [D(V) ~ r, V ~ L^3] -@named sys = ODESystem(eqs, t) +@named sys = System(eqs, t) sys_simple = structural_simplify(sys) @variables V [unit = u"m"^3] L [unit = u"m"] diff --git a/test/variable_scope.jl b/test/variable_scope.jl index 59647bf441..a7cfe0a1af 100644 --- a/test/variable_scope.jl +++ b/test/variable_scope.jl @@ -57,10 +57,10 @@ p = [a ParentScope(ParentScope(c)) GlobalScope(d)] -level0 = ODESystem(Equation[], t, [], p; name = :level0) -level1 = ODESystem(Equation[], t, [], []; name = :level1) ∘ level0 -level2 = ODESystem(Equation[], t, [], []; name = :level2) ∘ level1 -level3 = ODESystem(Equation[], t, [], []; name = :level3) ∘ level2 +level0 = System(Equation[], t, [], p; name = :level0) +level1 = System(Equation[], t, [], []; name = :level1) ∘ level0 +level2 = System(Equation[], t, [], []; name = :level2) ∘ level1 +level3 = System(Equation[], t, [], []; name = :level3) ∘ level2 ps = ModelingToolkit.getname.(parameters(level3)) @@ -73,8 +73,8 @@ ps = ModelingToolkit.getname.(parameters(level3)) # Tests from PR#2354 @parameters xx[1:2] arr_p = [ParentScope(xx[1]), xx[2]] -arr0 = ODESystem(Equation[], t, [], arr_p; name = :arr0) -arr1 = ODESystem(Equation[], t, [], []; name = :arr1) ∘ arr0 +arr0 = System(Equation[], t, [], arr_p; name = :arr0) +arr1 = System(Equation[], t, [], []; name = :arr1) ∘ arr0 arr_ps = ModelingToolkit.getname.(parameters(arr1)) @test isequal(arr_ps[1], Symbol("xx")) @test isequal(arr_ps[2], Symbol("arr0₊xx")) @@ -82,13 +82,13 @@ arr_ps = ModelingToolkit.getname.(parameters(arr1)) function Foo(; name, p = 1) @parameters p = p @variables x(t) - return ODESystem(D(x) ~ p, t; name) + return System(D(x) ~ p, t; name) end function Bar(; name, p = 2) @parameters p = p @variables x(t) @named foo = Foo(; p) - return ODESystem(D(x) ~ p + t, t; systems = [foo], name) + return System(D(x) ~ p + t, t; systems = [foo], name) end @named bar = Bar() bar = complete(bar) @@ -108,15 +108,15 @@ defs = ModelingToolkit.defaults(bar) p3 = ParentScope(ParentScope(p3)) p4 = GlobalScope(p4) - @named sys1 = ODESystem([D(x1) ~ p1, D(x2) ~ p2, D(x3) ~ p3, D(x4) ~ p4], t) + @named sys1 = System([D(x1) ~ p1, D(x2) ~ p2, D(x3) ~ p3, D(x4) ~ p4], t) @test isequal(x1, only(unknowns(sys1))) @test isequal(p1, only(parameters(sys1))) - @named sys2 = ODESystem(Equation[], t; systems = [sys1]) + @named sys2 = System(Equation[], t; systems = [sys1]) @test length(unknowns(sys2)) == 2 @test any(isequal(x2), unknowns(sys2)) @test length(parameters(sys2)) == 2 @test any(isequal(p2), parameters(sys2)) - @named sys3 = ODESystem(Equation[], t) + @named sys3 = System(Equation[], t) sys3 = sys3 ∘ sys2 @test length(unknowns(sys3)) == 3 @test any(isequal(x3), unknowns(sys3)) diff --git a/test/variable_utils.jl b/test/variable_utils.jl index 3204d28836..2c32700507 100644 --- a/test/variable_utils.jl +++ b/test/variable_utils.jl @@ -56,7 +56,7 @@ end ρ β end - sys = ODESystem( + sys = System( [D(D(x)) ~ σ * (y - x) D(y) ~ x * (ρ - z) - y D(z) ~ x * y - β * z], iv; name) @@ -68,12 +68,12 @@ end @parameters begin p[1:2, 1:2] end - sys = ODESystem([D(D(x)) ~ p * x], iv; name) + sys = System([D(D(x)) ~ p * x], iv; name) end function Outer(; name) @named 😄 = Lorenz() @named arr = ArrSys() - sys = ODESystem(Equation[], iv; name, systems = [😄, arr]) + sys = System(Equation[], iv; name, systems = [😄, arr]) end @mtkbuild sys = Outer() From b277af261240982786023dd0f7e8536818c477ab Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Tue, 22 Apr 2025 18:41:18 +0530 Subject: [PATCH 091/185] feat: add utility constructor for `SDESystem` --- src/ModelingToolkit.jl | 2 +- src/systems/system.jl | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/ModelingToolkit.jl b/src/ModelingToolkit.jl index 9f349f587c..e644441a29 100644 --- a/src/ModelingToolkit.jl +++ b/src/ModelingToolkit.jl @@ -267,7 +267,7 @@ export AbstractTimeDependentSystem, AbstractMultivariateSystem export ODEFunction, ODEFunctionExpr, ODEProblemExpr, convert_system, - System, OptimizationSystem, JumpSystem + System, OptimizationSystem, JumpSystem, SDESystem export DAEFunctionExpr, DAEProblemExpr export SDEFunction, SDEFunctionExpr, SDEProblemExpr export SystemStructure diff --git a/src/systems/system.jl b/src/systems/system.jl index 838088ce31..4fcf0716b3 100644 --- a/src/systems/system.jl +++ b/src/systems/system.jl @@ -572,6 +572,18 @@ function JumpSystem(jumps, iv, dvs, ps; kwargs...) return System(Equation[], iv, dvs, ps; jumps, kwargs...) end +function SDESystem(eqs::Vector{Equation}, noise, iv; kwargs...) + return System(eqs, iv; noise_eqs = noise, kwargs...) +end + +function SDESystem(eqs::Vector{Equation}, noise, iv, dvs, ps; kwargs...) + return System(eqs, iv, dvs, ps; noise_eqs = noise, kwargs...) +end + +function SDESystem(sys::System, noise; kwargs...) + SDESystem(equations(sys), noise, get_iv(sys); kwargs...) +end + struct SystemNotCompleteError <: Exception obj::Any end From c04a0aa7ed29cd5392772581dde72e31da492b41 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Tue, 22 Apr 2025 18:58:10 +0530 Subject: [PATCH 092/185] test: replace `NonlinearSystem` with `System` --- test/code_generation.jl | 2 +- test/components.jl | 4 +- test/dep_graphs.jl | 2 +- test/dq_units.jl | 6 +-- test/extensions/bifurcationkit.jl | 4 +- test/extensions/homotopy_continuation.jl | 42 +++++++++--------- test/initial_values.jl | 4 +- test/initializationsystem.jl | 10 ++--- test/modelingtoolkitize.jl | 2 +- test/namespacing.jl | 4 +- test/nonlinearsystem.jl | 52 +++++++++++------------ test/parameter_dependencies.jl | 4 +- test/reduction.jl | 4 +- test/scc_nonlinear_problem.jl | 20 ++++----- test/sciml_problem_inputs.jl | 2 +- test/structural_transformation/tearing.jl | 4 +- test/structural_transformation/utils.jl | 2 +- test/symbolic_indexing_interface.jl | 2 +- test/symbolic_parameters.jl | 4 +- test/units.jl | 6 +-- test/variable_scope.jl | 14 +++--- 21 files changed, 97 insertions(+), 97 deletions(-) diff --git a/test/code_generation.jl b/test/code_generation.jl index 03abf9e588..e51007563e 100644 --- a/test/code_generation.jl +++ b/test/code_generation.jl @@ -31,7 +31,7 @@ using ModelingToolkit: t_nounits as t, D_nounits as D @test fn5(u0, p, 1.0) == 1.0 @variables x y[1:3] - sys = complete(NonlinearSystem(Equation[], [x; y], [p1, p2, p3, p4]; name = :sys)) + sys = complete(System(Equation[], [x; y], [p1, p2, p3, p4]; name = :sys)) p = MTKParameters(sys, []) fn1 = generate_custom_function(sys, x + y[1] + p1 + p2[1] + p3; expression = Val(false)) diff --git a/test/components.jl b/test/components.jl index c7e97dea81..2030be8559 100644 --- a/test/components.jl +++ b/test/components.jl @@ -381,8 +381,8 @@ end @test ModelingToolkit.get_metadata(sys) == "test" end @testset "NonlinearSystem" begin - @named inner = NonlinearSystem([0 ~ x^2 + 4x + 4], [x], []) - @named outer = NonlinearSystem( + @named inner = System([0 ~ x^2 + 4x + 4], [x], []) + @named outer = System( [0 ~ x^3 - y^3], [x, y], []; systems = [inner], metadata = "test") @test ModelingToolkit.get_metadata(outer) == "test" sys = complete(outer) diff --git a/test/dep_graphs.jl b/test/dep_graphs.jl index 76cc216635..3c7b88dd05 100644 --- a/test/dep_graphs.jl +++ b/test/dep_graphs.jl @@ -184,7 +184,7 @@ s_eqdeps = [[1], [2], [3]] eqs = [0 ~ σ * (y - x), 0 ~ ρ - y, 0 ~ y - β * z] -@named ns = NonlinearSystem(eqs, [x, y, z], [σ, ρ, β]) +@named ns = System(eqs, [x, y, z], [σ, ρ, β]) deps = equation_dependencies(ns) eq_sdeps = [[x, y], [y], [y, z]] @test all(i -> isequal(Set(deps[i]), Set(value.(eq_sdeps[i]))), 1:length(deps)) diff --git a/test/dq_units.jl b/test/dq_units.jl index 267e993971..c6cada3363 100644 --- a/test/dq_units.jl +++ b/test/dq_units.jl @@ -81,7 +81,7 @@ System(eqs, t, name = :sys) eqs = [ 0 ~ a * x ] -@named nls = NonlinearSystem(eqs, [x], [a]) +@named nls = System(eqs, [x], [a]) # SDE test w/ noise vector @parameters τ [unit = u"s"] Q [unit = u"W"] @@ -124,12 +124,12 @@ sys_simple = structural_simplify(sys) @parameters v [unit = u"m/s"] r [unit = u"m"^3 / u"s"] eqs = [V ~ r * t, V ~ L^3] -@named sys = NonlinearSystem(eqs, [V, L], [t, r]) +@named sys = System(eqs, [V, L], [t, r]) sys_simple = structural_simplify(sys) eqs = [L ~ v * t, V ~ L^3] -@named sys = NonlinearSystem(eqs, [V, L], [t, r]) +@named sys = System(eqs, [V, L], [t, r]) sys_simple = structural_simplify(sys) #Jump System diff --git a/test/extensions/bifurcationkit.jl b/test/extensions/bifurcationkit.jl index 227cd175e0..7b95ffd82e 100644 --- a/test/extensions/bifurcationkit.jl +++ b/test/extensions/bifurcationkit.jl @@ -8,7 +8,7 @@ let @parameters μ α eqs = [0 ~ μ * x - x^3 + α * y, 0 ~ -y] - @named nsys = NonlinearSystem(eqs, [x, y], [μ, α]) + @named nsys = System(eqs, [x, y], [μ, α]) nsys = complete(nsys) # Creates BifurcationProblem bif_par = μ @@ -103,7 +103,7 @@ let eqs = [0 ~ μ - x^3 + 2x^2, 0 ~ p * μ - y, 0 ~ y - z] - @named nsys = NonlinearSystem(eqs, [x, y, z], [μ, p]) + @named nsys = System(eqs, [x, y, z], [μ, p]) nsys = structural_simplify(nsys) # Creates BifurcationProblem. diff --git a/test/extensions/homotopy_continuation.jl b/test/extensions/homotopy_continuation.jl index 65f7d765a4..607c27346c 100644 --- a/test/extensions/homotopy_continuation.jl +++ b/test/extensions/homotopy_continuation.jl @@ -33,7 +33,7 @@ end eqs = [0 ~ x^2 + y^2 + 2x * y 0 ~ x^2 + 4x + 4 0 ~ y * z + 4x^2] - @mtkbuild sys = NonlinearSystem(eqs) + @mtkbuild sys = System(eqs) u0 = [x => 1.0, y => 1.0, z => 1.0] prob = HomotopyContinuationProblem(sys, u0) @test prob isa NonlinearProblem @@ -60,7 +60,7 @@ end 0 ~ x^2 + 4x + q 0 ~ y * z + 4x^2 + wrapper(r)] - @mtkbuild sys = NonlinearSystem(eqs) + @mtkbuild sys = System(eqs) prob = HomotopyContinuationProblem(sys, [x => 1.0, y => 1.0, z => 1.0], [p => 2.0, q => 4, r => Wrapper([1.0 1.0; 0.0 0.0])]) @test prob.ps[p] == 2.0 @@ -78,7 +78,7 @@ end @parameters p[1:3] _x = collect(x) eqs = collect(0 .~ vec(sum(_x * _x'; dims = 2)) + collect(p)) - @mtkbuild sys = NonlinearSystem(eqs) + @mtkbuild sys = System(eqs) prob = HomotopyContinuationProblem(sys, [x => ones(3)], [p => 1:3]) @test prob[x] == ones(3) @test prob[p + x] == [2, 3, 4] @@ -93,7 +93,7 @@ end @testset "Parametric exponents" begin @variables x = 1.0 @parameters n::Integer = 4 - @mtkbuild sys = NonlinearSystem([x^n + x^2 - 1 ~ 0]) + @mtkbuild sys = System([x^n + x^2 - 1 ~ 0]) prob = HomotopyContinuationProblem(sys, []) sol = solve(prob, singlerootalg) test_single_root(sol) @@ -103,34 +103,34 @@ end @testset "Polynomial check and warnings" begin @variables x = 1.0 - @mtkbuild sys = NonlinearSystem([x^1.5 + x^2 - 1 ~ 0]) + @mtkbuild sys = System([x^1.5 + x^2 - 1 ~ 0]) @test_throws ["Cannot convert", "Unable", "symbolically solve", "Exponent", "not an integer", "not a polynomial"] HomotopyContinuationProblem( sys, []) - @mtkbuild sys = NonlinearSystem([x^x - x ~ 0]) + @mtkbuild sys = System([x^x - x ~ 0]) @test_throws ["Cannot convert", "Unable", "symbolically solve", "Exponent", "unknowns", "not a polynomial"] HomotopyContinuationProblem( sys, []) - @mtkbuild sys = NonlinearSystem([((x^2) / sin(x))^2 + x ~ 0]) + @mtkbuild sys = System([((x^2) / sin(x))^2 + x ~ 0]) @test_throws ["Cannot convert", "both polynomial", "non-polynomial", "recognized", "sin", "not a polynomial"] HomotopyContinuationProblem( sys, []) @variables y = 2.0 - @mtkbuild sys = NonlinearSystem([x^2 + y^2 + 2 ~ 0, y ~ sin(x)]) + @mtkbuild sys = System([x^2 + y^2 + 2 ~ 0, y ~ sin(x)]) @test_throws ["Cannot convert", "recognized", "sin", "not a polynomial"] HomotopyContinuationProblem( sys, []) - @mtkbuild sys = NonlinearSystem([x^2 + y^2 - 2 ~ 0, sin(x + y) ~ 0]) + @mtkbuild sys = System([x^2 + y^2 - 2 ~ 0, sin(x + y) ~ 0]) @test_throws ["Cannot convert", "function of multiple unknowns"] HomotopyContinuationProblem( sys, []) - @mtkbuild sys = NonlinearSystem([sin(x)^2 + 1 ~ 0, cos(y) - cos(x) - 1 ~ 0]) + @mtkbuild sys = System([sin(x)^2 + 1 ~ 0, cos(y) - cos(x) - 1 ~ 0]) @test_throws ["Cannot convert", "multiple non-polynomial terms", "same unknown"] HomotopyContinuationProblem( sys, []) - @mtkbuild sys = NonlinearSystem([sin(x^2)^2 + sin(x^2) - 1 ~ 0]) + @mtkbuild sys = System([sin(x^2)^2 + sin(x^2) - 1 ~ 0]) @test_throws ["import Nemo"] HomotopyContinuationProblem(sys, []) end @@ -138,7 +138,7 @@ import Nemo @testset "With Nemo" begin @variables x = 2.0 - @mtkbuild sys = NonlinearSystem([sin(x^2)^2 + sin(x^2) - 1 ~ 0]) + @mtkbuild sys = System([sin(x^2)^2 + sin(x^2) - 1 ~ 0]) prob = HomotopyContinuationProblem(sys, []) @test prob[1] ≈ 2.0 # singlerootalg doesn't converge @@ -151,8 +151,8 @@ end @variables x=0.25 y=0.125 a = sin(x^2 - 4x + 1) b = cos(3log(y) + 4) - @mtkbuild sys = NonlinearSystem([(a^2 - 5a * b + 6b^2) / (a - 0.25) ~ 0 - (a^2 - 0.75a + 0.125) ~ 0]) + @mtkbuild sys = System([(a^2 - 5a * b + 6b^2) / (a - 0.25) ~ 0 + (a^2 - 0.75a + 0.125) ~ 0]) prob = HomotopyContinuationProblem(sys, []) @test prob[x] ≈ 0.25 @test prob[y] ≈ 0.125 @@ -165,7 +165,7 @@ end @testset "Rational functions" begin @variables x=2.0 y=2.0 @parameters n = 5 - @mtkbuild sys = NonlinearSystem([ + @mtkbuild sys = System([ 0 ~ (x^2 - n * x + 6) * (x - 1) / (x - 2) / (x - 3) ]) prob = HomotopyContinuationProblem(sys, []) @@ -178,7 +178,7 @@ end end end - @named sys = NonlinearSystem( + @named sys = System( [ 0 ~ (x - 2) / (x - 4) + ((x - 3) / (y - 7)) / ((x^2 - 4x + y) / (x - 2.5)), 0 ~ ((y - 3) / (y - 4)) * (n / (y - 5)) + ((x - 1.5) / (x - 5.5))^2 @@ -209,7 +209,7 @@ end @testset "Rational function in observed" begin @variables x=1 y=1 - @mtkbuild sys = NonlinearSystem([x^2 + y^2 - 2x - 2 ~ 0, y ~ (x - 1) / (x - 2)]) + @mtkbuild sys = System([x^2 + y^2 - 2x - 2 ~ 0, y ~ (x - 1) / (x - 2)]) prob = HomotopyContinuationProblem(sys, []) @test any(prob.f.denominator([2.0], parameter_values(prob)) .≈ 0.0) @test SciMLBase.successful_retcode(solve(prob, singlerootalg)) @@ -217,7 +217,7 @@ end @testset "Rational function forced to common denominators" begin @variables x = 1 - @mtkbuild sys = NonlinearSystem([0 ~ 1 / (1 + x) - x]) + @mtkbuild sys = System([0 ~ 1 / (1 + x) - x]) prob = HomotopyContinuationProblem(sys, []) @test any(prob.f.denominator([-1.0], parameter_values(prob)) .≈ 0.0) sol = solve(prob, singlerootalg) @@ -228,7 +228,7 @@ end @testset "Non-polynomial observed not used in equations" begin @variables x=1 y - @mtkbuild sys = NonlinearSystem([x^2 - 2 ~ 0, y ~ sin(x)]) + @mtkbuild sys = System([x^2 - 2 ~ 0, y ~ sin(x)]) prob = HomotopyContinuationProblem(sys, []) sol = @test_nowarn solve(prob, singlerootalg) @test sol[x] ≈ √2.0 @@ -237,8 +237,8 @@ end @testset "`fraction_cancel_fn`" begin @variables x = 1 - @named sys = NonlinearSystem([0 ~ ((x^2 - 5x + 6) / (x - 2) - 1) * (x^2 - 7x + 12) / - (x - 4)^3]) + @named sys = System([0 ~ ((x^2 - 5x + 6) / (x - 2) - 1) * (x^2 - 7x + 12) / + (x - 4)^3]) sys = complete(sys) @testset "`simplify_fractions`" begin diff --git a/test/initial_values.jl b/test/initial_values.jl index d6c194cc33..8192c00e5b 100644 --- a/test/initial_values.jl +++ b/test/initial_values.jl @@ -103,7 +103,7 @@ prob = ODEProblem(sys, [], (0.0, 1.0), [A1 => 0.3]) System(Equation[], t, [x, y], [p]; defaults = [y => nothing], name = :osys), SDESystem(Equation[], [], t, [x, y], [p]; defaults = [y => nothing], name = :ssys), JumpSystem(Equation[], t, [x, y], [p]; defaults = [y => nothing], name = :jsys), - NonlinearSystem(Equation[], [x, y], [p]; defaults = [y => nothing], name = :nsys), + System(Equation[], [x, y], [p]; defaults = [y => nothing], name = :nsys), OptimizationSystem( Equation[], [x, y], [p]; defaults = [y => nothing], name = :optsys), ConstraintsSystem( @@ -244,7 +244,7 @@ end 0 ~ p[1] - X[1], 0 ~ p[2] - X[2] ] - @named nlsys = NonlinearSystem(eqs) + @named nlsys = System(eqs) nlsys = complete(nlsys) # Creates the `NonlinearProblem`. diff --git a/test/initializationsystem.jl b/test/initializationsystem.jl index f484108983..e4e499cf07 100644 --- a/test/initializationsystem.jl +++ b/test/initializationsystem.jl @@ -769,7 +769,7 @@ end eqs = [0 ~ σ * (y - x), 0 ~ x * (ρ - z) - y, 0 ~ x * y - β * z] - @mtkbuild ns = NonlinearSystem(eqs, [x, y, z], [σ, ρ, β]) + @mtkbuild ns = System(eqs, [x, y, z], [σ, ρ, β]) prob = NonlinearProblem(ns, []) @test prob.f.initialization_data.update_initializeprob! === nothing @@ -810,7 +810,7 @@ end # https://github.com/SciML/NonlinearSolve.jl/issues/586 eqs = [0 ~ -c * z + (q - z) * (x^2) 0 ~ p * (-x + (q - z) * x)] - @named sys = NonlinearSystem(eqs; initialization_eqs = [p^2 + q^2 + 2p * q ~ 0]) + @named sys = System(eqs; initialization_eqs = [p^2 + q^2 + 2p * q ~ 0]) sys = complete(sys) # @mtkbuild sys = NonlinearSystem( # [p * x^2 + q * y^3 ~ 0, x - q ~ 0]; defaults = [q => missing], @@ -1420,7 +1420,7 @@ end end @testset "$Problem" for Problem in [NonlinearProblem, NonlinearLeastSquaresProblem] @parameters p1 p2 - @mtkbuild sys = NonlinearSystem([x^2 + y^2 ~ p1, (x - 1)^2 + (y - 1)^2 ~ p2]; + @mtkbuild sys = System([x^2 + y^2 ~ p1, (x - 1)^2 + (y - 1)^2 ~ p2]; parameter_dependencies = [p2 ~ 2p1], guesses = [p1 => 0.0], defaults = [p1 => missing]) prob = Problem(sys, [x => 1.0, y => 1.0], [p2 => 6.0]) @@ -1439,7 +1439,7 @@ end initialization_eqs = [ X2 ~ Γ[1] - X1 ] - @mtkbuild nlsys = NonlinearSystem(eqs, [X1, X2], [k1, k2, Γ]; initialization_eqs) + @mtkbuild nlsys = System(eqs, [X1, X2], [k1, k2, Γ]; initialization_eqs) @testset "throws if initialization_eqs contain unknowns" begin u0 = [X1 => 1.0, X2 => 2.0] @@ -1452,7 +1452,7 @@ end initialization_eqs = [ Initial(X2) ~ Γ[1] - Initial(X1) ] - @mtkbuild nlsys = NonlinearSystem(eqs, [X1, X2], [k1, k2, Γ]; initialization_eqs) + @mtkbuild nlsys = System(eqs, [X1, X2], [k1, k2, Γ]; initialization_eqs) @testset "solves initialization" begin u0 = [X1 => 1.0, X2 => 2.0] diff --git a/test/modelingtoolkitize.jl b/test/modelingtoolkitize.jl index d146fa95c9..04e6263c7c 100644 --- a/test/modelingtoolkitize.jl +++ b/test/modelingtoolkitize.jl @@ -391,7 +391,7 @@ sys = modelingtoolkitize(prob) @testset "Nonlinear" begin @variables x=1.0 y=2.0 @parameters p=3.0 q=4.0 - @mtkbuild nlsys = NonlinearSystem([0 ~ p * y^2 + x, 0 ~ x + exp(x) * q]) + @mtkbuild nlsys = System([0 ~ p * y^2 + x, 0 ~ x + exp(x) * q]) prob1 = NonlinearProblem(nlsys, []) newsys = complete(modelingtoolkitize(prob1)) @test is_variable(newsys, newsys.x) diff --git a/test/namespacing.jl b/test/namespacing.jl index 3871398144..de33f9e927 100644 --- a/test/namespacing.jl +++ b/test/namespacing.jl @@ -110,7 +110,7 @@ end @testset "NonlinearSystem" begin @variables x @parameters p - sys = NonlinearSystem([x ~ p * x^2 + 1]; name = :inner) + sys = System([x ~ p * x^2 + 1]; name = :inner) @test !iscomplete(sys) @test does_namespacing(sys) @@ -129,7 +129,7 @@ end @test isequal(p, nsys.p) @test !isequal(p, sys.p) - @test_throws ["namespacing", "inner"] NonlinearSystem( + @test_throws ["namespacing", "inner"] System( Equation[]; systems = [nsys], name = :a) end diff --git a/test/nonlinearsystem.jl b/test/nonlinearsystem.jl index 4aac92ee64..b8c2a7bb0d 100644 --- a/test/nonlinearsystem.jl +++ b/test/nonlinearsystem.jl @@ -26,7 +26,7 @@ end eqs = [0 ~ σ * (y - x) * h, 0 ~ x * (ρ - z) - y, 0 ~ x * y - β * z] -@named ns = NonlinearSystem(eqs, [x, y, z], [σ, ρ, β], defaults = Dict(x => 2)) +@named ns = System(eqs, [x, y, z], [σ, ρ, β], defaults = Dict(x => 2)) @test eval(toexpr(ns)) == ns test_nlsys_inference("standard", ns, (x, y, z), (σ, ρ, β)) @test begin @@ -44,7 +44,7 @@ end eqs = [0 ~ σ * (y - x), y ~ x * (ρ - z), β * z ~ x * y] -@named ns = NonlinearSystem(eqs, [x, y, z], [σ, ρ, β]) +@named ns = System(eqs, [x, y, z], [σ, ρ, β]) jac = calculate_jacobian(ns) @testset "nlsys jacobian" begin @test canonequal(jac[1, 1], σ * -1) @@ -67,7 +67,7 @@ a = y - x eqs = [0 ~ σ * a * h, 0 ~ x * (ρ - z) - y, 0 ~ x * y - β * z] -@named ns = NonlinearSystem(eqs, [x, y, z], [σ, ρ, β]) +@named ns = System(eqs, [x, y, z], [σ, ρ, β]) ns = complete(ns) nlsys_func = generate_function(ns, [x, y, z], [σ, ρ, β]) nf = NonlinearFunction(ns) @@ -102,11 +102,11 @@ eqs1 = [ 0 ~ x + y - z - u ] -lorenz = name -> NonlinearSystem(eqs1, [x, y, z, u, F], [σ, ρ, β], name = name) +lorenz = name -> System(eqs1, [x, y, z, u, F], [σ, ρ, β], name = name) lorenz1 = lorenz(:lorenz1) @test_throws ArgumentError NonlinearProblem(complete(lorenz1), zeros(5), zeros(3)) lorenz2 = lorenz(:lorenz2) -@named connected = NonlinearSystem( +@named connected = System( [s ~ a + lorenz1.x lorenz2.y ~ s * h lorenz1.F ~ lorenz2.u @@ -135,7 +135,7 @@ sol = solve(prob, FBDF(), reltol = 1e-7, abstol = 1e-7) eqs = [0 ~ σ * (y - x), 0 ~ x * (ρ - z) - y, 0 ~ x * y - β * z * h] -@named ns = NonlinearSystem(eqs, [x, y, z], [σ, ρ, β]) +@named ns = System(eqs, [x, y, z], [σ, ρ, β]) np = NonlinearProblem( complete(ns), [0, 0, 0], [σ => 1, ρ => 2, β => 3], jac = true, sparse = true) @test calculate_jacobian(ns, sparse = true) isa SparseMatrixCSC @@ -146,13 +146,13 @@ np = NonlinearProblem( @parameters a @variables x f - NonlinearSystem([0 ~ -a * x + f], [x, f], [a]; name) + System([0 ~ -a * x + f], [x, f], [a]; name) end function issue819() sys1 = makesys(:sys1) sys2 = makesys(:sys1) - @test_throws ArgumentError NonlinearSystem([sys2.f ~ sys1.x, sys1.f ~ 0], [], [], + @test_throws ArgumentError System([sys2.f ~ sys1.x, sys1.f ~ 0], [], [], systems = [sys1, sys2], name = :foo) end issue819() @@ -169,8 +169,8 @@ end 0 ~ b * y ] - @named sys1 = NonlinearSystem(eqs1, [x], [a]) - @named sys2 = NonlinearSystem(eqs2, [y], [b]) + @named sys1 = System(eqs1, [x], [a]) + @named sys2 = System(eqs2, [y], [b]) @named sys3 = extend(sys1, sys2) @test isequal(union(Set(parameters(sys1)), Set(parameters(sys2))), @@ -183,7 +183,7 @@ end @independent_variables t @parameters τ @variables x(t) RHS(t) -@named fol = NonlinearSystem([0 ~ (1 - x * h) / τ], [x], [τ]; +@named fol = System([0 ~ (1 - x * h) / τ], [x], [τ]; observed = [RHS ~ (1 - x) / τ]) @test isequal(RHS, @nonamespace fol.RHS) RHS2 = RHS @@ -208,7 +208,7 @@ eq = [v1 ~ sin(2pi * t * h) u[3] ~ 1, u[4] ~ h] - sys = NonlinearSystem(eqs, collect(u[1:4]), Num[], defaults = Dict([]), name = :test) + sys = System(eqs, collect(u[1:4]), Num[], defaults = Dict([]), name = :test) sys = complete(sys) prob = NonlinearProblem(sys, ones(length(unknowns(sys)))) @@ -222,7 +222,7 @@ end eqs = [0 ~ a * x] testdict = Dict([:test => 1]) -@named sys = NonlinearSystem(eqs, [x], [a], metadata = testdict) +@named sys = System(eqs, [x], [a], metadata = testdict) @test get_metadata(sys) == testdict @testset "Remake" begin @@ -233,7 +233,7 @@ testdict = Dict([:test => 1]) eqs = [0 ~ a * (y - x) * h, 0 ~ x * (b - z) - y, 0 ~ x * y - c * z] - @named sys = NonlinearSystem(eqs, [x, y, z], [a, b, c], defaults = Dict(x => 2.0)) + @named sys = System(eqs, [x, y, z], [a, b, c], defaults = Dict(x => 2.0)) sys = complete(sys) prob = NonlinearProblem(sys, ones(length(unknowns(sys)))) @@ -254,7 +254,7 @@ end eqs = [0 ~ x + sin(y), 0 ~ z - cos(x), 0 ~ x * y] - @named ns = NonlinearSystem(eqs, [x, y, z], []) + @named ns = System(eqs, [x, y, z], []) ns = complete(ns) vs = [unknowns(ns); parameters(ns)] ss_mtk = structural_simplify(ns) @@ -268,7 +268,7 @@ end @variables X(t) alg_eqs = [0 ~ p - d * X] -sys = @test_nowarn NonlinearSystem(alg_eqs; name = :name) +sys = @test_nowarn System(alg_eqs; name = :name) @test isequal(only(unknowns(sys)), X) @test all(isequal.(parameters(sys), [p, d])) @@ -276,14 +276,14 @@ sys = @test_nowarn NonlinearSystem(alg_eqs; name = :name) @variables u1 u2 @parameters u3 u4 eqs = [u3 ~ u1 + u2, u4 ~ 2 * (u1 + u2), u3 + u4 ~ 3 * (u1 + u2)] -@named ns = NonlinearSystem(eqs, [u1, u2], [u3, u4]) +@named ns = System(eqs, [u1, u2], [u3, u4]) sys = structural_simplify(ns; fully_determined = false) @test length(unknowns(sys)) == 1 # Conservative @variables X(t) alg_eqs = [1 ~ 2X] -@named ns = NonlinearSystem(alg_eqs) +@named ns = System(alg_eqs) sys = structural_simplify(ns) @test length(equations(sys)) == 0 sys = structural_simplify(ns; conservative = true) @@ -298,7 +298,7 @@ sys = structural_simplify(ns; conservative = true) 0 ~ x * y - β * z] guesses = [x => 1.0, z => 0.0] ps = [σ => 10.0, ρ => 26.0, β => 8 / 3] - @mtkbuild ns = NonlinearSystem(eqs) + @mtkbuild ns = System(eqs) @test isequal(calculate_jacobian(ns), [(-1 - z + ρ)*σ -x*σ 2x*(-z + ρ) -β-(x^2)]) @@ -315,7 +315,7 @@ sys = structural_simplify(ns; conservative = true) # system that contains a chain of observed variables when simplified @variables x y z eqs = [0 ~ x^2 + 2z + y, z ~ y, y ~ x] # analytical solution x = y = z = 0 or -3 - @mtkbuild ns = NonlinearSystem(eqs) # solve for y with observed chain z -> x -> y + @mtkbuild ns = System(eqs) # solve for y with observed chain z -> x -> y @test isequal(expand.(calculate_jacobian(ns)), [3 // 2 + y;;]) @test isequal(calculate_hessian(ns), [[1;;]]) prob = NonlinearProblem(ns, unknowns(ns) .=> -4.0) # give guess < -3 to reach -3 @@ -325,7 +325,7 @@ end @testset "Passing `nothing` to `u0`" begin @variables x = 1 - @mtkbuild sys = NonlinearSystem([0 ~ x^2 - x^3 + 3]) + @mtkbuild sys = System([0 ~ x^2 - x^3 + 3]) prob = @test_nowarn NonlinearProblem(sys, nothing) @test_nowarn solve(prob) end @@ -337,7 +337,7 @@ end 2 -2 4 -1 1/2 -1] b = [1, -2, 0] - @named sys = NonlinearSystem(A * x ~ b, [x], []) + @named sys = System(A * x ~ b, [x], []) sys = structural_simplify(sys) prob = NonlinearProblem(sys, unknowns(sys) .=> 0.0) sol = solve(prob) @@ -347,7 +347,7 @@ end @testset "resid_prototype when system has no unknowns and an equation" begin @variables x @parameters p - @named sys = NonlinearSystem([x ~ 1, x^2 - p ~ 0]) + @named sys = System([x ~ 1, x^2 - p ~ 0]) for sys in [ structural_simplify(sys, fully_determined = false), structural_simplify(sys, fully_determined = false, split = false) @@ -365,7 +365,7 @@ end @testset "IntervalNonlinearProblem" begin @variables x @parameters p - @named nlsys = NonlinearSystem([0 ~ x * x - p]) + @named nlsys = System([0 ~ x * x - p]) for sys in [complete(nlsys), complete(nlsys; split = false)] prob = IntervalNonlinearProblem(sys, (0.0, 2.0), [p => 1.0]) @@ -375,7 +375,7 @@ end end @variables y - @mtkbuild sys = NonlinearSystem([0 ~ x * x - p * x + p, 0 ~ x * y + p]) + @mtkbuild sys = System([0 ~ x * x - p * x + p, 0 ~ x * y + p]) @test_throws ["single equation", "unknown"] IntervalNonlinearProblem(sys, (0.0, 1.0)) @test_throws ["single equation", "unknown"] IntervalNonlinearFunction(sys, (0.0, 1.0)) @test_throws ["single equation", "unknown"] IntervalNonlinearProblemExpr( @@ -388,7 +388,7 @@ end @variables x y @parameters p[1:2] (f::Function)(..) - @mtkbuild sys = NonlinearSystem([x^2 - p[1]^2 ~ 0, y^2 ~ f(p)]) + @mtkbuild sys = System([x^2 - p[1]^2 ~ 0, y^2 ~ f(p)]) @test !any(isequal(p[1]), parameters(sys)) @test is_parameter(sys, p) end diff --git a/test/parameter_dependencies.jl b/test/parameter_dependencies.jl index 6b85fb7e10..be7c81ff9d 100644 --- a/test/parameter_dependencies.jl +++ b/test/parameter_dependencies.jl @@ -324,7 +324,7 @@ end @parameters p1=1.0 p2=1.0 @variables x(t) eqs = [0 ~ p1 * x * exp(x) + p2] - @mtkbuild sys = NonlinearSystem(eqs; parameter_dependencies = [p2 => 2p1]) + @mtkbuild sys = System(eqs; parameter_dependencies = [p2 => 2p1]) @test isequal(only(parameters(sys)), p1) @test Set(full_parameters(sys)) == Set([p1, p2, Initial(p2), Initial(x)]) prob = NonlinearProblem(sys, [x => 1.0]) @@ -379,7 +379,7 @@ end @variables x(t) y(t) @named sys = System([D(x) ~ y + p2], t; parameter_dependencies = [p2 ~ 2p1]) @test is_parameter(sys, p1) - @named sys = NonlinearSystem([x * y^2 ~ y + p2]; parameter_dependencies = [p2 ~ 2p1]) + @named sys = System([x * y^2 ~ y + p2]; parameter_dependencies = [p2 ~ 2p1]) @test is_parameter(sys, p1) k = ShiftIndex(t) @named sys = DiscreteSystem( diff --git a/test/reduction.jl b/test/reduction.jl index 4d786076be..db0bddc361 100644 --- a/test/reduction.jl +++ b/test/reduction.jl @@ -154,7 +154,7 @@ end eqs = [u1 ~ u2 u3 ~ u1 + u2 + p u3 ~ hypot(u1, u2) * p] -@named sys = NonlinearSystem(eqs, [u1, u2, u3], [p]) +@named sys = System(eqs, [u1, u2, u3], [p]) reducedsys = structural_simplify(sys) @test length(observed(reducedsys)) == 2 @@ -174,7 +174,7 @@ N = 5 @variables xs[1:N] A = reshape(1:(N^2), N, N) eqs = xs ~ A * xs -@named sys′ = NonlinearSystem(eqs, [xs], []) +@named sys′ = System(eqs, [xs], []) sys = structural_simplify(sys′) @test length(equations(sys)) == 3 && length(observed(sys)) == 2 diff --git a/test/scc_nonlinear_problem.jl b/test/scc_nonlinear_problem.jl index 67b531fddf..a435e83a7a 100644 --- a/test/scc_nonlinear_problem.jl +++ b/test/scc_nonlinear_problem.jl @@ -20,7 +20,7 @@ using ModelingToolkit: t_nounits as t, D_nounits as D eqs = Any[0 for _ in 1:8] f!(eqs, u, nothing) eqs = 0 .~ eqs - @named model = NonlinearSystem(eqs) + @named model = System(eqs) @test_throws ["simplified", "required"] SCCNonlinearProblem(model, []) _model = structural_simplify(model; split = false) @test_throws ["not compatible"] SCCNonlinearProblem(_model, []) @@ -78,7 +78,7 @@ end @variables u[1:5] [irreducible = true] @parameters p1[1:6] p2 eqs = 0 .~ collect(nlf(u, (u0, (p1, p2)))) - @mtkbuild sys = NonlinearSystem(eqs, [u], [p1, p2]) + @mtkbuild sys = System(eqs, [u], [p1, p2]) sccprob = SCCNonlinearProblem(sys, [u => u0], [p1 => p[1], p2 => p[2][]]) sccsol = solve(sccprob, SimpleNewtonRaphson(); abstol = 1e-9) @test SciMLBase.successful_retcode(sccsol) @@ -134,7 +134,7 @@ end eqs = 0 .~ eqs subrules = Dict(Symbolics.unwrap(D(y[i])) => ((y[i] - u0[i]) / dt) for i in 1:8) eqs = substitute.(eqs, (subrules,)) - @mtkbuild sys = NonlinearSystem(eqs) + @mtkbuild sys = System(eqs) prob = NonlinearProblem(sys, [y => u0], [t => t0]) sol = solve(prob, NewtonRaphson(); abstol = 1e-12) @@ -152,10 +152,10 @@ end x + y end @register_symbolic func(x, y) - @mtkbuild sys = NonlinearSystem([0 ~ x[1]^3 + x[2]^3 - 5 - 0 ~ sin(x[1] - x[2]) - 0.5 - 0 ~ func(x[1], x[2]) * exp(x[3]) - x[4]^3 - 5 - 0 ~ func(x[1], x[2]) * exp(x[4]) - x[3]^3 - 4]) + @mtkbuild sys = System([0 ~ x[1]^3 + x[2]^3 - 5 + 0 ~ sin(x[1] - x[2]) - 0.5 + 0 ~ func(x[1], x[2]) * exp(x[3]) - x[4]^3 - 5 + 0 ~ func(x[1], x[2]) * exp(x[4]) - x[3]^3 - 4]) sccprob = SCCNonlinearProblem(sys, []) sccsol = solve(sccprob, NewtonRaphson()) @test SciMLBase.successful_retcode(sccsol) @@ -257,7 +257,7 @@ end @testset "Array variables split across SCCs" begin @variables x[1:3] @parameters (f::Function)(..) - @mtkbuild sys = NonlinearSystem([ + @mtkbuild sys = System([ 0 ~ x[1]^2 - 9, x[2] ~ 2x[1], 0 ~ x[3]^2 - x[1]^2 + f(x)]) prob = SCCNonlinearProblem(sys, [x => ones(3)], [f => sum]) sol = solve(prob, NewtonRaphson()) @@ -267,7 +267,7 @@ end @testset "SCCNonlinearProblem retains parameter order" begin @variables x y z @parameters σ β ρ - @mtkbuild fullsys = NonlinearSystem( + @mtkbuild fullsys = System( [0 ~ x^3 * β + y^3 * ρ - σ, 0 ~ x^2 + 2x * y + y^2, 0 ~ z^2 - 4z + 4], [x, y, z], [σ, β, ρ]) @@ -287,7 +287,7 @@ end @variables x y @parameters p[1:2] (f::Function)(..) - @mtkbuild sys = NonlinearSystem([x^2 - p[1]^2 ~ 0, y^2 ~ f(p)]) + @mtkbuild sys = System([x^2 - p[1]^2 ~ 0, y^2 ~ f(p)]) prob = SCCNonlinearProblem(sys, [x => 1.0, y => 1.0], [p => ones(2), f => sum]) @test_nowarn solve(prob, NewtonRaphson()) end diff --git a/test/sciml_problem_inputs.jl b/test/sciml_problem_inputs.jl index deae4b4772..a1b9d1e416 100644 --- a/test/sciml_problem_inputs.jl +++ b/test/sciml_problem_inputs.jl @@ -42,7 +42,7 @@ begin ssys = complete(SDESystem( diff_eqs, noise_eqs, t, [X, Y, Z], [kp, kd, k1, k2]; name = :ssys)) jsys = complete(JumpSystem(jumps, t, [X, Y, Z], [kp, kd, k1, k2]; name = :jsys)) - nsys = complete(NonlinearSystem(alg_eqs; name = :nsys)) + nsys = complete(System(alg_eqs; name = :nsys)) u0_alts = [ # Vectors not providing default values. diff --git a/test/structural_transformation/tearing.jl b/test/structural_transformation/tearing.jl index 24083b3524..5bf88e1dd5 100644 --- a/test/structural_transformation/tearing.jl +++ b/test/structural_transformation/tearing.jl @@ -18,7 +18,7 @@ eqs = [ 0 ~ u4 - hypot(u2, u3), 0 ~ u5 - hypot(u4, u1) ] -@named sys = NonlinearSystem(eqs, [u1, u2, u3, u4, u5], []) +@named sys = System(eqs, [u1, u2, u3, u4, u5], []) state = TearingState(sys) StructuralTransformations.find_solvables!(state) @@ -133,7 +133,7 @@ eqs = [ 0 ~ z + y, 0 ~ x + z ] -@named nlsys = NonlinearSystem(eqs, [x, y, z], []) +@named nlsys = System(eqs, [x, y, z], []) newsys = tearing(nlsys) @test length(equations(newsys)) <= 1 diff --git a/test/structural_transformation/utils.jl b/test/structural_transformation/utils.jl index b9abbcfc77..9d560a2614 100644 --- a/test/structural_transformation/utils.jl +++ b/test/structural_transformation/utils.jl @@ -275,7 +275,7 @@ end end @testset "`NonlinearSystem`" begin @variables x y z - @mtkbuild sys = NonlinearSystem([x^2 ~ 2y^2 + 1, sin(z) ~ y, z^3 + 4z + 1 ~ 0]) + @mtkbuild sys = System([x^2 ~ 2y^2 + 1, sin(z) ~ y, z^3 + 4z + 1 ~ 0]) mapping = map_variables_to_equations(sys) @test mapping[x] == (0 ~ 2y^2 + 1 - x^2) @test mapping[y] == (y ~ sin(z)) diff --git a/test/symbolic_indexing_interface.jl b/test/symbolic_indexing_interface.jl index 85deba82c9..2c1eac6562 100644 --- a/test/symbolic_indexing_interface.jl +++ b/test/symbolic_indexing_interface.jl @@ -108,7 +108,7 @@ end eqs = [0 ~ σ * (y - x), 0 ~ x * (ρ - z) - y, 0 ~ x * y - β * z] - @named ns = NonlinearSystem(eqs, [x, y, z], [σ, ρ, β]) + @named ns = System(eqs, [x, y, z], [σ, ρ, β]) ns = complete(ns) @test SymbolicIndexingInterface.supports_tuple_observed(ns) @test !is_time_dependent(ns) diff --git a/test/symbolic_parameters.jl b/test/symbolic_parameters.jl index 8949d09688..ed32b474e4 100644 --- a/test/symbolic_parameters.jl +++ b/test/symbolic_parameters.jl @@ -20,7 +20,7 @@ u0 = [ y => σ, # default u0 from default p z => u - 0.1 ] -ns = NonlinearSystem(eqs, [x, y, z], [σ, ρ, β], name = :ns, defaults = [par; u0]) +ns = System(eqs, [x, y, z], [σ, ρ, β], name = :ns, defaults = [par; u0]) ns.y = u * 1.1 resolved = ModelingToolkit.varmap_to_vars(Dict(), parameters(ns), defaults = ModelingToolkit.defaults(ns)) @@ -32,7 +32,7 @@ prob = NonlinearProblem(complete(ns), [u => 1.0], Pair[]) @variables a @parameters b -top = NonlinearSystem([0 ~ -a + ns.x + b], [a], [b], systems = [ns], name = :top) +top = System([0 ~ -a + ns.x + b], [a], [b], systems = [ns], name = :top) top.b = ns.σ * 0.5 top.ns.x = u * 0.5 diff --git a/test/units.jl b/test/units.jl index 267b526c4b..2bd67f2745 100644 --- a/test/units.jl +++ b/test/units.jl @@ -108,7 +108,7 @@ System(eqs, t, name = :sys) eqs = [ 0 ~ a * x ] -@named nls = NonlinearSystem(eqs, [x], [a]) +@named nls = System(eqs, [x], [a]) # SDE test w/ noise vector @independent_variables t [unit = u"ms"] @@ -151,12 +151,12 @@ sys_simple = structural_simplify(sys) @parameters v [unit = u"m/s"] r [unit = u"m"^3 / u"s"] t [unit = u"s"] eqs = [V ~ r * t, V ~ L^3] -@named sys = NonlinearSystem(eqs, [V, L], [t, r]) +@named sys = System(eqs, [V, L], [t, r]) sys_simple = structural_simplify(sys) eqs = [L ~ v * t, V ~ L^3] -@named sys = NonlinearSystem(eqs, [V, L], [t, r]) +@named sys = System(eqs, [V, L], [t, r]) sys_simple = structural_simplify(sys) #Jump System diff --git a/test/variable_scope.jl b/test/variable_scope.jl index a7cfe0a1af..6d7d20d948 100644 --- a/test/variable_scope.jl +++ b/test/variable_scope.jl @@ -22,11 +22,11 @@ eqs = [0 ~ a 0 ~ b 0 ~ c 0 ~ d] -@named sub4 = NonlinearSystem(eqs, [a, b, c, d], []) -@named sub3 = NonlinearSystem(eqs, [a, b, c, d], []) -@named sub2 = NonlinearSystem([], [], [], systems = [sub3, sub4]) -@named sub1 = NonlinearSystem([], [], [], systems = [sub2]) -@named sys = NonlinearSystem([], [], [], systems = [sub1]) +@named sub4 = System(eqs, [a, b, c, d], []) +@named sub3 = System(eqs, [a, b, c, d], []) +@named sub2 = System([], [], [], systems = [sub3, sub4]) +@named sub1 = System([], [], [], systems = [sub2]) +@named sys = System([], [], [], systems = [sub1]) names = ModelingToolkit.getname.(unknowns(sys)) @test :d in names @@ -35,8 +35,8 @@ names = ModelingToolkit.getname.(unknowns(sys)) @test Symbol("sub1₊sub2₊sub3₊a") in names @test Symbol("sub1₊sub2₊sub4₊a") in names -@named foo = NonlinearSystem(eqs, [a, b, c, d], []) -@named bar = NonlinearSystem(eqs, [a, b, c, d], []) +@named foo = System(eqs, [a, b, c, d], []) +@named bar = System(eqs, [a, b, c, d], []) @test ModelingToolkit.getname(ModelingToolkit.namespace_expr( ModelingToolkit.namespace_expr(b, foo), From 4aeb8fb55e9be071ce0d9da54004918e4c9de9e4 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Tue, 22 Apr 2025 18:45:51 +0530 Subject: [PATCH 093/185] test: replace `ImplicitDiscreteSystem` with `System` --- test/implicit_discrete_system.jl | 10 +++++----- test/namespacing.jl | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/test/implicit_discrete_system.jl b/test/implicit_discrete_system.jl index 932b6c6981..45c89f969e 100644 --- a/test/implicit_discrete_system.jl +++ b/test/implicit_discrete_system.jl @@ -7,7 +7,7 @@ rng = StableRNG(22525) @testset "Correct ImplicitDiscreteFunction" begin @variables x(t) = 1 - @mtkbuild sys = ImplicitDiscreteSystem([x(k) ~ x(k) * x(k - 1) - 3], t) + @mtkbuild sys = System([x(k) ~ x(k) * x(k - 1) - 3], t) tspan = (0, 10) # u[2] - u_next[1] @@ -27,7 +27,7 @@ rng = StableRNG(22525) prob = ImplicitDiscreteProblem(sys, [], tspan) @test prob.u0 == [1.0, 1.0] @variables x(t) - @mtkbuild sys = ImplicitDiscreteSystem([x(k) ~ x(k) * x(k - 1) - 3], t) + @mtkbuild sys = System([x(k) ~ x(k) * x(k - 1) - 3], t) @test_throws ErrorException prob=ImplicitDiscreteProblem(sys, [], tspan) end @@ -35,7 +35,7 @@ end @variables x(t) y(t) eqs = [x(k) ~ x(k - 1) + x(k - 2), x^2 ~ 1 - y^2] - @mtkbuild sys = ImplicitDiscreteSystem(eqs, t) + @mtkbuild sys = System(eqs, t) f = ImplicitDiscreteFunction(sys) function correct_f(u_next, u, p, t) @@ -62,7 +62,7 @@ end eqs = [x(k) ~ x(k - 1) + x(k - 2), y(k) ~ x(k) + x(k - 2) * z(k - 1), x + y + z ~ 2] - @mtkbuild sys = ImplicitDiscreteSystem(eqs, t) + @mtkbuild sys = System(eqs, t) @test length(unknowns(sys)) == length(equations(sys)) == 3 @test occursin("var\"y(t)\"", string(ImplicitDiscreteFunctionExpr(sys))) @@ -70,6 +70,6 @@ end eqs = [z(k) ~ x(k) + sin(x(k)), y(k) ~ x(k - 1) + x(k - 2), z(k) * x(k) ~ 3] - @mtkbuild sys = ImplicitDiscreteSystem(eqs, t) + @mtkbuild sys = System(eqs, t) @test occursin("var\"Shift(t, 1)(z(t))\"", string(ImplicitDiscreteFunctionExpr(sys))) end diff --git a/test/namespacing.jl b/test/namespacing.jl index de33f9e927..7ae4702304 100644 --- a/test/namespacing.jl +++ b/test/namespacing.jl @@ -84,7 +84,7 @@ end @variables x(t) @parameters p k = ShiftIndex(t) - sys = ImplicitDiscreteSystem([x(k) ~ p + x(k - 1) * x(k)], t; name = :inner) + sys = System([x(k) ~ p + x(k - 1) * x(k)], t; name = :inner) @test !iscomplete(sys) @test does_namespacing(sys) @@ -103,7 +103,7 @@ end @test isequal(p, nsys.p) @test !isequal(p, sys.p) - @test_throws ["namespacing", "inner"] ImplicitDiscreteSystem( + @test_throws ["namespacing", "inner"] System( Equation[], t; systems = [nsys], name = :a) end From 5f647215d6f37b8ec0d49925b4bd4efc274b0970 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Tue, 22 Apr 2025 18:47:40 +0530 Subject: [PATCH 094/185] test: replace `DiscreteSystem` with `System` --- test/components.jl | 4 ++-- test/discrete_system.jl | 32 ++++++++++++++++---------------- test/namespacing.jl | 4 ++-- test/parameter_dependencies.jl | 2 +- 4 files changed, 21 insertions(+), 21 deletions(-) diff --git a/test/components.jl b/test/components.jl index 2030be8559..1efdd8a22f 100644 --- a/test/components.jl +++ b/test/components.jl @@ -390,8 +390,8 @@ end end k = ShiftIndex(t) @testset "DiscreteSystem" begin - @named inner = DiscreteSystem([x(k) ~ x(k - 1) + x(k - 2)], t, [x], []) - @named outer = DiscreteSystem([y(k) ~ y(k - 1) + y(k - 2)], t, [x, y], + @named inner = System([x(k) ~ x(k - 1) + x(k - 2)], t, [x], []) + @named outer = System([y(k) ~ y(k - 1) + y(k - 2)], t, [x, y], []; systems = [inner], metadata = "test") @test ModelingToolkit.get_metadata(outer) == "test" sys = complete(outer) diff --git a/test/discrete_system.jl b/test/discrete_system.jl index b0e2481e56..641e1fd45b 100644 --- a/test/discrete_system.jl +++ b/test/discrete_system.jl @@ -10,7 +10,7 @@ using ModelingToolkit: get_metadata, MTKParameters # Make sure positive shifts error @variables x(t) k = ShiftIndex(t) -@test_throws ErrorException @mtkbuild sys = DiscreteSystem([x(k + 1) ~ x + x(k - 1)], t) +@test_throws ErrorException @mtkbuild sys = System([x(k + 1) ~ x + x(k - 1)], t) @inline function rate_to_proportion(r, t) 1 - exp(-r * t) @@ -30,7 +30,7 @@ eqs = [S ~ S(k - 1) - infection * h, R ~ R(k - 1) + recovery] # System -@named sys = DiscreteSystem(eqs, t, [S, I, R], [c, nsteps, δt, β, γ]) +@named sys = System(eqs, t, [S, I, R], [c, nsteps, δt, β, γ]) syss = structural_simplify(sys) @test syss == syss @@ -72,7 +72,7 @@ eqs2 = [S ~ S(k - 1) - infection2, R ~ R(k - 1) + recovery2, R2 ~ R] -@mtkbuild sys = DiscreteSystem( +@mtkbuild sys = System( eqs2, t, [S, I, R, R2], [c, nsteps, δt, β, γ]; controls = [β, γ], tspan) @test ModelingToolkit.defaults(sys) != Dict() @@ -127,7 +127,7 @@ sol_map2 = solve(prob_map, FunctionMap()); # ] # # System -# @named sys = DiscreteSystem(eqs, t, [x(t), x(t - 1.5), x(t - 3), y(t), y(t - 2), z], []) +# @named sys = System(eqs, t, [x(t), x(t - 1.5), x(t - 3), y(t), y(t - 2), z], []) # eqs2, max_delay = ModelingToolkit.linearize_eqs(sys; return_max_delay = true) @@ -143,7 +143,7 @@ sol_map2 = solve(prob_map, FunctionMap()); # observed variable handling @variables x(t) RHS(t) @parameters τ -@named fol = DiscreteSystem( +@named fol = System( [x ~ (1 - x(k - 1)) / τ], t, [x, RHS], [τ]; observed = [RHS ~ (1 - x) / τ * h]) @test isequal(RHS, @nonamespace fol.RHS) RHS2 = RHS @@ -198,7 +198,7 @@ RHS2 = RHS # Δ(us[i]) ~ dummy_identity(buffer[i], us[i]) # end -# @mtkbuild sys = DiscreteSystem(eqs, t, us, ps; defaults = defs, preface = preface) +# @mtkbuild sys = System(eqs, t, us, ps; defaults = defs, preface = preface) # prob = DiscreteProblem(sys, [], (0.0, 1.0)) # sol = solve(prob, FunctionMap(); dt = dt) # @test c[1] + 1 == length(sol) @@ -206,14 +206,14 @@ RHS2 = RHS @variables x(t) y(t) testdict = Dict([:test => 1]) -@named sys = DiscreteSystem([x(k + 1) ~ 1.0], t, [x], []; metadata = testdict) +@named sys = System([x(k + 1) ~ 1.0], t, [x], []; metadata = testdict) @test get_metadata(sys) == testdict @variables x(t) y(t) u(t) eqs = [u ~ 1 x ~ x(k - 1) + u y ~ x + u] -@mtkbuild de = DiscreteSystem(eqs, t) +@mtkbuild de = System(eqs, t) prob = DiscreteProblem(de, [x(k - 1) => 0.0], (0, 10)) sol = solve(prob, FunctionMap()) @@ -231,7 +231,7 @@ function SampledData(; name, buffer) @variables output(t) time(t) eqs = [time ~ time(k - 1) + 1 output ~ getdata(buffer, time)] - return DiscreteSystem(eqs, t; name) + return System(eqs, t; name) end function System(; name, buffer) @named y_sys = SampledData(; buffer = buffer) @@ -245,7 +245,7 @@ function System(; name, buffer) # y[t] = 0.5 * y[t - 1] + 0.5 * y[t + 1] + y_shk[t] y(k - 1) ~ α * y(k - 2) + (β * y(k) + y_shk(k - 1))] - DiscreteSystem(eqs, t, vars, pars; systems = [y_sys], name = name) + System(eqs, t, vars, pars; systems = [y_sys], name = name) end @test_nowarn @mtkbuild sys = System(; buffer = ones(10)) @@ -253,13 +253,13 @@ end # Ensure discrete systems with algebraic equations throw @variables x(t) y(t) k = ShiftIndex(t) -@named sys = DiscreteSystem([x ~ x^2 + y^2, y ~ x(k - 1) + y(k - 1)], t) @test_throws ["algebraic equations", "ImplicitDiscreteSystem"] structural_simplify(sys) +@named sys = System([x ~ x^2 + y^2, y ~ x(k - 1) + y(k - 1)], t) @testset "Passing `nothing` to `u0`" begin @variables x(t) = 1 k = ShiftIndex() - @mtkbuild sys = DiscreteSystem([x(k) ~ x(k - 1) + 1], t) + @mtkbuild sys = System([x(k) ~ x(k - 1) + 1], t) prob = @test_nowarn DiscreteProblem(sys, nothing, (0.0, 1.0)) @test_nowarn solve(prob, FunctionMap()) end @@ -267,7 +267,7 @@ end @testset "Initialization" begin # test that default values apply to the entire history @variables x(t) = 1.0 - @mtkbuild de = DiscreteSystem([x ~ x(k - 1) + x(k - 2)], t) + @mtkbuild de = System([x ~ x(k - 1) + x(k - 2)], t) prob = DiscreteProblem(de, [], (0, 10)) @test prob[x] == 2.0 @test prob[x(k - 1)] == 1.0 @@ -290,14 +290,14 @@ end # Test missing initial throws error @variables x(t) - @mtkbuild de = DiscreteSystem([x ~ x(k - 1) + x(k - 2) * x(k - 3)], t) + @mtkbuild de = System([x ~ x(k - 1) + x(k - 2) * x(k - 3)], t) @test_throws ErrorException prob=DiscreteProblem(de, [x(k - 3) => 2.0], (0, 10)) @test_throws ErrorException prob=DiscreteProblem( de, [x(k - 3) => 2.0, x(k - 1) => 3.0], (0, 10)) # Test non-assigned initials are given default value @variables x(t) = 2.0 - @mtkbuild de = DiscreteSystem([x ~ x(k - 1) + x(k - 2) * x(k - 3)], t) + @mtkbuild de = System([x ~ x(k - 1) + x(k - 2) * x(k - 3)], t) prob = DiscreteProblem(de, [x(k - 3) => 12.0], (0, 10)) @test prob[x] == 26.0 @test prob[x(k - 1)] == 2.0 @@ -307,7 +307,7 @@ end @variables xₜ₋₂(t) zₜ₋₁(t) z(t) eqs = [x ~ x(k - 1) + z(k - 2), z ~ x(k - 2) * x(k - 3) - z(k - 1)^2] - @mtkbuild de = DiscreteSystem(eqs, t) + @mtkbuild de = System(eqs, t) u0 = [x(k - 1) => 3, xₜ₋₂(k - 1) => 4, x(k - 2) => 1, diff --git a/test/namespacing.jl b/test/namespacing.jl index 7ae4702304..50cbd7e3a3 100644 --- a/test/namespacing.jl +++ b/test/namespacing.jl @@ -57,7 +57,7 @@ end @variables x(t) @parameters p k = ShiftIndex(t) - sys = DiscreteSystem([x(k) ~ p * x(k - 1)], t; name = :inner) + sys = System([x(k) ~ p * x(k - 1)], t; name = :inner) @test !iscomplete(sys) @test does_namespacing(sys) @@ -76,7 +76,7 @@ end @test isequal(p, nsys.p) @test !isequal(p, sys.p) - @test_throws ["namespacing", "inner"] DiscreteSystem( + @test_throws ["namespacing", "inner"] System( Equation[], t; systems = [nsys], name = :a) end diff --git a/test/parameter_dependencies.jl b/test/parameter_dependencies.jl index be7c81ff9d..4e3743b84f 100644 --- a/test/parameter_dependencies.jl +++ b/test/parameter_dependencies.jl @@ -382,7 +382,7 @@ end @named sys = System([x * y^2 ~ y + p2]; parameter_dependencies = [p2 ~ 2p1]) @test is_parameter(sys, p1) k = ShiftIndex(t) - @named sys = DiscreteSystem( + @named sys = System( [x(k - 1) ~ x(k) + y(k) + p2], t; parameter_dependencies = [p2 ~ 2p1]) @test is_parameter(sys, p1) end From 367bd2219240e78fdba6b15a9ea096a2fe42f0a1 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Fri, 25 Apr 2025 15:47:39 +0530 Subject: [PATCH 095/185] refactor: do not accept `dvs` and `ps` in `XFunction` constructors --- src/problems/daeproblem.jl | 2 +- src/problems/ddeproblem.jl | 2 +- src/problems/discreteproblem.jl | 2 +- src/problems/implicitdiscreteproblem.jl | 2 +- src/problems/intervalnonlinearproblem.jl | 2 +- src/problems/nonlinearproblem.jl | 2 +- src/problems/odeproblem.jl | 2 +- src/problems/optimizationproblem.jl | 4 ++-- src/problems/sddeproblem.jl | 2 +- src/problems/sdeproblem.jl | 2 +- src/systems/problem_utils.jl | 2 +- 11 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/problems/daeproblem.jl b/src/problems/daeproblem.jl index d0ea2cf61b..743fab51b4 100644 --- a/src/problems/daeproblem.jl +++ b/src/problems/daeproblem.jl @@ -1,5 +1,5 @@ @fallback_iip_specialize function SciMLBase.DAEFunction{iip, spec}( - sys::System, _d = nothing, u0 = nothing, p = nothing; tgrad = false, jac = false, + sys::System; u0 = nothing, p = nothing, tgrad = false, jac = false, t = nothing, eval_expression = false, eval_module = @__MODULE__, sparse = false, steady_state = false, checkbounds = false, sparsity = false, analytic = nothing, simplify = false, cse = true, initialization_data = nothing, diff --git a/src/problems/ddeproblem.jl b/src/problems/ddeproblem.jl index 5aea37e4d5..9693dd4f1f 100644 --- a/src/problems/ddeproblem.jl +++ b/src/problems/ddeproblem.jl @@ -1,5 +1,5 @@ @fallback_iip_specialize function SciMLBase.DDEFunction{iip, spec}( - sys::System, _d = nothing, u0 = nothing, p = nothing; + sys::System; u0 = nothing, p = nothing, eval_expression = false, eval_module = @__MODULE__, checkbounds = false, initialization_data = nothing, cse = true, check_compatibility = true, sparse = false, simplify = false, analytic = nothing, kwargs...) where { diff --git a/src/problems/discreteproblem.jl b/src/problems/discreteproblem.jl index 8f8f3ca3d1..26c463b837 100644 --- a/src/problems/discreteproblem.jl +++ b/src/problems/discreteproblem.jl @@ -1,5 +1,5 @@ @fallback_iip_specialize function SciMLBase.DiscreteFunction{iip, spec}( - sys::System, _d = nothing, u0 = nothing, p = nothing; + sys::System; u0 = nothing, p = nothing, t = nothing, eval_expression = false, eval_module = @__MODULE__, checkbounds = false, analytic = nothing, simplify = false, cse = true, initialization_data = nothing, check_compatibility = true, kwargs...) where { diff --git a/src/problems/implicitdiscreteproblem.jl b/src/problems/implicitdiscreteproblem.jl index 06316a28cc..e63738a257 100644 --- a/src/problems/implicitdiscreteproblem.jl +++ b/src/problems/implicitdiscreteproblem.jl @@ -1,5 +1,5 @@ @fallback_iip_specialize function SciMLBase.ImplicitDiscreteFunction{iip, spec}( - sys::System, _d = nothing, u0 = nothing, p = nothing; + sys::System; u0 = nothing, p = nothing, t = nothing, eval_expression = false, eval_module = @__MODULE__, checkbounds = false, analytic = nothing, simplify = false, cse = true, initialization_data = nothing, check_compatibility = true, kwargs...) where { diff --git a/src/problems/intervalnonlinearproblem.jl b/src/problems/intervalnonlinearproblem.jl index 18bcf80697..02d18ef7d3 100644 --- a/src/problems/intervalnonlinearproblem.jl +++ b/src/problems/intervalnonlinearproblem.jl @@ -1,5 +1,5 @@ function SciMLBase.IntervalNonlinearFunction( - sys::System, _d = nothing, u0 = nothing, p = nothing; + sys::System; u0 = nothing, p = nothing, eval_expression = false, eval_module = @__MODULE__, checkbounds = false, analytic = nothing, cse = true, initialization_data = nothing, diff --git a/src/problems/nonlinearproblem.jl b/src/problems/nonlinearproblem.jl index 646322816c..768210b624 100644 --- a/src/problems/nonlinearproblem.jl +++ b/src/problems/nonlinearproblem.jl @@ -1,5 +1,5 @@ @fallback_iip_specialize function SciMLBase.NonlinearFunction{iip, spec}( - sys::System, _d = nothing, u0 = nothing, p = nothing; jac = false, + sys::System; u0 = nothing, p = nothing, jac = false, eval_expression = false, eval_module = @__MODULE__, sparse = false, checkbounds = false, sparsity = false, analytic = nothing, simplify = false, cse = true, initialization_data = nothing, diff --git a/src/problems/odeproblem.jl b/src/problems/odeproblem.jl index a62863216c..da79eb86fc 100644 --- a/src/problems/odeproblem.jl +++ b/src/problems/odeproblem.jl @@ -1,5 +1,5 @@ @fallback_iip_specialize function SciMLBase.ODEFunction{iip, spec}( - sys::System, _d = nothing, u0 = nothing, p = nothing; tgrad = false, jac = false, + sys::System; u0 = nothing, p = nothing, tgrad = false, jac = false, t = nothing, eval_expression = false, eval_module = @__MODULE__, sparse = false, steady_state = false, checkbounds = false, sparsity = false, analytic = nothing, simplify = false, cse = true, initialization_data = nothing, diff --git a/src/problems/optimizationproblem.jl b/src/problems/optimizationproblem.jl index 80c1dab1f1..55a256a712 100644 --- a/src/problems/optimizationproblem.jl +++ b/src/problems/optimizationproblem.jl @@ -2,8 +2,8 @@ function SciMLBase.OptimizationFunction(sys::System, args...; kwargs...) return OptimizationFunction{true}(sys, args...; kwargs...) end -function SciMLBase.OptimizationFunction{iip}(sys::System, - _d = nothing, u0 = nothing, p = nothing; grad = false, hess = false, +function SciMLBase.OptimizationFunction{iip}(sys::System; + u0 = nothing, p = nothing, grad = false, hess = false, sparse = false, cons_j = false, cons_h = false, cons_sparse = false, linenumbers = true, eval_expression = false, eval_module = @__MODULE__, simplify = false, check_compatibility = true, checkbounds = false, cse = true, diff --git a/src/problems/sddeproblem.jl b/src/problems/sddeproblem.jl index 8c001294dd..a95c1e341f 100644 --- a/src/problems/sddeproblem.jl +++ b/src/problems/sddeproblem.jl @@ -1,5 +1,5 @@ @fallback_iip_specialize function SciMLBase.SDDEFunction{iip, spec}( - sys::System, _d = nothing, u0 = nothing, p = nothing; + sys::System; u0 = nothing, p = nothing, eval_expression = false, eval_module = @__MODULE__, checkbounds = false, initialization_data = nothing, cse = true, check_compatibility = true, sparse = false, simplify = false, analytic = nothing, kwargs...) where { diff --git a/src/problems/sdeproblem.jl b/src/problems/sdeproblem.jl index 45ed21c9da..84d1f0b0ee 100644 --- a/src/problems/sdeproblem.jl +++ b/src/problems/sdeproblem.jl @@ -1,5 +1,5 @@ @fallback_iip_specialize function SciMLBase.SDEFunction{iip, spec}( - sys::System, _d = nothing, u0 = nothing, p = nothing; tgrad = false, jac = false, + sys::System; u0 = nothing, p = nothing, tgrad = false, jac = false, t = nothing, eval_expression = false, eval_module = @__MODULE__, sparse = false, steady_state = false, checkbounds = false, sparsity = false, analytic = nothing, simplify = false, cse = true, initialization_data = nothing, diff --git a/src/systems/problem_utils.jl b/src/systems/problem_utils.jl index a87c0a358e..ba84decd3e 100644 --- a/src/systems/problem_utils.jl +++ b/src/systems/problem_utils.jl @@ -1184,7 +1184,7 @@ function process_SciMLProblem( kwargs = merge(kwargs,) end - f = constructor(sys, dvs, ps, u0; p = p, + f = constructor(sys; u0 = u0, p = p, eval_expression = eval_expression, eval_module = eval_module, kwargs...) From 24d930e69901fa2bc7735d3a96ee43c28cad87ac Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Fri, 25 Apr 2025 15:48:28 +0530 Subject: [PATCH 096/185] fix: ensure equations are `Vector{Equation}` in `generate_initializesystem` --- src/systems/nonlinear/initializesystem.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/systems/nonlinear/initializesystem.jl b/src/systems/nonlinear/initializesystem.jl index 0e727cc2c0..054c969e8f 100644 --- a/src/systems/nonlinear/initializesystem.jl +++ b/src/systems/nonlinear/initializesystem.jl @@ -247,7 +247,7 @@ function generate_initializesystem_timeindependent(sys::AbstractSystem; # so add scalarized versions as well scalarize_varmap!(paramsubs) - eqs_ics = Symbolics.substitute.(eqs_ics, (paramsubs,)) + eqs_ics = Vector{Equation}(Symbolics.substitute.(eqs_ics, (paramsubs,))) for k in keys(defs) defs[k] = substitute(defs[k], paramsubs) end From 02f880956cfb6567928422f95981180aaf30aee5 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Fri, 25 Apr 2025 15:48:51 +0530 Subject: [PATCH 097/185] test: fix usage of array equations in test --- test/dq_units.jl | 2 +- test/units.jl | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/dq_units.jl b/test/dq_units.jl index c6cada3363..4d8c245e06 100644 --- a/test/dq_units.jl +++ b/test/dq_units.jl @@ -72,7 +72,7 @@ good_eqs = [connect(op, op2)] # Array variables @variables x(t)[1:3] [unit = u"m"] @parameters v[1:3]=[1, 2, 3] [unit = u"m/s"] -eqs = D.(x) .~ v +eqs = [D(x) ~ v] System(eqs, t, name = :sys) # Nonlinear system diff --git a/test/units.jl b/test/units.jl index 2bd67f2745..b7d141f347 100644 --- a/test/units.jl +++ b/test/units.jl @@ -99,7 +99,7 @@ bad_length_eqs = [connect(op, lp)] @parameters v[1:3]=[1, 2, 3] [unit = u"m/s"] @variables x(t)[1:3] [unit = u"m"] D = Differential(t) -eqs = D.(x) .~ v +eqs = [D(x) ~ v] System(eqs, t, name = :sys) # Nonlinear system From 54fa64ceb2abb52c1eb6099cf6526136573a558d Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Fri, 25 Apr 2025 15:49:13 +0530 Subject: [PATCH 098/185] test: ensure equations passed to system are `Vector{Equation}` --- test/variable_scope.jl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/variable_scope.jl b/test/variable_scope.jl index 6d7d20d948..2ecd62ec1f 100644 --- a/test/variable_scope.jl +++ b/test/variable_scope.jl @@ -24,9 +24,9 @@ eqs = [0 ~ a 0 ~ d] @named sub4 = System(eqs, [a, b, c, d], []) @named sub3 = System(eqs, [a, b, c, d], []) -@named sub2 = System([], [], [], systems = [sub3, sub4]) -@named sub1 = System([], [], [], systems = [sub2]) -@named sys = System([], [], [], systems = [sub1]) +@named sub2 = System(Equation[], [], [], systems = [sub3, sub4]) +@named sub1 = System(Equation[], [], [], systems = [sub2]) +@named sys = System(Equation[], [], [], systems = [sub1]) names = ModelingToolkit.getname.(unknowns(sys)) @test :d in names From 3f120e8452703fdcae9619dced6f7866ba2fd32d Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Fri, 25 Apr 2025 15:49:31 +0530 Subject: [PATCH 099/185] refactor: remove `process_equations` --- src/utils.jl | 56 ---------------------------------------------------- 1 file changed, 56 deletions(-) diff --git a/src/utils.jl b/src/utils.jl index 428a060a6d..bc45752707 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -1199,62 +1199,6 @@ function guesses_from_metadata!(guesses, vars) end end -""" - $(TYPEDSIGNATURES) - -Find all the unknowns and parameters from the equations of a System. Return re-ordered -equations, differential variables, all variables, and parameters. -""" -function process_equations(eqs, iv) - if eltype(eqs) <: AbstractVector - eqs = reduce(vcat, eqs) - end - eqs = collect(eqs) - - diffvars = OrderedSet() - allunknowns = OrderedSet() - ps = OrderedSet() - - # NOTE: this assumes that the order of algebraic equations doesn't matter - # reorder equations such that it is in the form of `diffeq, algeeq` - diffeq = Equation[] - algeeq = Equation[] - # initial loop for finding `iv` - if iv === nothing - for eq in eqs - if !(eq.lhs isa Number) # assume eq.lhs is either Differential or Number - iv = iv_from_nested_derivative(eq.lhs) - break - end - end - end - iv = value(iv) - iv === nothing && throw(ArgumentError("Please pass in independent variables.")) - - compressed_eqs = Equation[] # equations that need to be expanded later, like `connect(a, b)` - for eq in eqs - eq.lhs isa Union{Symbolic, Number} || (push!(compressed_eqs, eq); continue) - collect_vars!(allunknowns, ps, eq, iv) - if isdiffeq(eq) - diffvar, _ = var_from_nested_derivative(eq.lhs) - if check_scope_depth(getmetadata(diffvar, SymScope, LocalScope()), 0) - isequal(iv, iv_from_nested_derivative(eq.lhs)) || - throw(ArgumentError("A system of differential equations can only have one independent variable.")) - diffvar in diffvars && - throw(ArgumentError("The differential variable $diffvar is not unique in the system of equations.")) - !has_diffvar_type(diffvar) && - throw(ArgumentError("Differential variable $diffvar has type $(symtype(diffvar)). Differential variables should be of a continuous, non-concrete number type: Real, Complex, AbstractFloat, or Number.")) - push!(diffvars, diffvar) - end - push!(diffeq, eq) - else - push!(algeeq, eq) - end - end - - diffvars, allunknowns, ps, Equation[diffeq; algeeq; compressed_eqs] -end - function has_diffvar_type(diffvar) st = symtype(diffvar) st === Real || eltype(st) === Real || st === Complex || eltype(st) === Complex || From 1ac662b1ffe189805bbeb7d993ce573b19ca8734 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Fri, 25 Apr 2025 15:49:56 +0530 Subject: [PATCH 100/185] docs: document `collect_var!` --- src/utils.jl | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/utils.jl b/src/utils.jl index bc45752707..c17d352713 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -601,6 +601,13 @@ function collect_vars!(unknowns, parameters, p::Pair, iv; depth = 0, op = Differ return nothing end +""" + $(TYPEDSIGNATURES) + +Identify whether `var` belongs to the current system using `depth` and scoping information. +Add `var` to `unknowns` or `parameters` appropriately, and search through any expressions +in known metadata of `var` using `collect_vars!`. +""" function collect_var!(unknowns, parameters, var, iv; depth = 0) isequal(var, iv) && return nothing check_scope_depth(getmetadata(var, SymScope, LocalScope()), depth) || return nothing From a063b0086b490028e586cf5bb2c9f4b32c9634a9 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Fri, 25 Apr 2025 15:50:34 +0530 Subject: [PATCH 101/185] refactor: change default operator in `collect_vars!` to `Symbolics.Operator` --- src/utils.jl | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/utils.jl b/src/utils.jl index c17d352713..b97614375c 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -563,7 +563,7 @@ function collect_scoped_vars!(unknowns, parameters, sys, iv; depth = 1, op = Dif end end -function collect_vars!(unknowns, parameters, expr, iv; depth = 0, op = Differential) +function collect_vars!(unknowns, parameters, expr, iv; depth = 0, op = Symbolics.Operator) if issym(expr) collect_var!(unknowns, parameters, expr, iv; depth) else @@ -589,13 +589,14 @@ eqtype_supports_collect_vars(eq::Inequality) = true eqtype_supports_collect_vars(eq::Pair) = true function collect_vars!(unknowns, parameters, eq::Union{Equation, Inequality}, iv; - depth = 0, op = Differential) + depth = 0, op = Symbolics.Operator) collect_vars!(unknowns, parameters, eq.lhs, iv; depth, op) collect_vars!(unknowns, parameters, eq.rhs, iv; depth, op) return nothing end -function collect_vars!(unknowns, parameters, p::Pair, iv; depth = 0, op = Differential) +function collect_vars!( + unknowns, parameters, p::Pair, iv; depth = 0, op = Symbolics.Operator) collect_vars!(unknowns, parameters, p[1], iv; depth, op) collect_vars!(unknowns, parameters, p[2], iv; depth, op) return nothing From 58648af98a487e7afd4a7899cc39572184760840 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Fri, 25 Apr 2025 15:50:58 +0530 Subject: [PATCH 102/185] feat: add `validate_operator` --- src/discretedomain.jl | 21 +++++++++++++++++ src/utils.jl | 55 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 76 insertions(+) diff --git a/src/discretedomain.jl b/src/discretedomain.jl index 7260237053..0befab61a2 100644 --- a/src/discretedomain.jl +++ b/src/discretedomain.jl @@ -7,6 +7,8 @@ SymbolicUtils.promote_symtype(::Type{<:SampleTime}, t...) = Real Base.nameof(::SampleTime) = :SampleTime SymbolicUtils.isbinop(::SampleTime) = false +function validate_operator(op::SampleTime, args, iv; context = nothing) end + # Shift """ @@ -68,6 +70,13 @@ Base.hash(D::Shift, u::UInt) = hash(D.steps, hash(D.t, xor(u, 0x055640d6d952f101 Base.:^(D::Shift, n::Integer) = Shift(D.t, D.steps * n) Base.literal_pow(f::typeof(^), D::Shift, ::Val{n}) where {n} = Shift(D.t, D.steps * n) +function validate_operator(op::Shift, args, iv; context = nothing) + isequal(op.t, iv) || throw(OperatorIndepvarMismatchError(op, iv, context)) + op.steps <= 0 || error(""" + Only non-positive shifts are allowed. Found shift of $(op.steps) in $context. + """) +end + hasshift(eq::Equation) = hasshift(eq.lhs) || hasshift(eq.rhs) """ @@ -128,6 +137,13 @@ Base.show(io::IO, D::Sample) = print(io, "Sample(", D.clock, ")") Base.:(==)(D1::Sample, D2::Sample) = isequal(D1.clock, D2.clock) Base.hash(D::Sample, u::UInt) = hash(D.clock, xor(u, 0x055640d6d952f101)) +function validate_operator(op::Sample, args, iv; context = nothing) + arg = unwrap(only(args)) + if !is_variable_floatingpoint(arg) + throw(ContinuousOperatorDiscreteArgumentError(op, arg, context)) + end +end + """ hassample(O) @@ -156,6 +172,11 @@ SymbolicUtils.isbinop(::Hold) = false Hold(x) = Hold()(x) +function validate_operator(op::Hold, args, iv; context = nothing) + # TODO: maybe validate `VariableTimeDomain`? + return nothing +end + """ hashold(O) diff --git a/src/utils.jl b/src/utils.jl index b97614375c..2e84c32b51 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -563,6 +563,61 @@ function collect_scoped_vars!(unknowns, parameters, sys, iv; depth = 1, op = Dif end end +""" + $(TYPEDSIGNATURES) + +Check whether the usage of operator `op` is valid in a system with independent variable +`iv`. If the system is time-independent, `iv` should be `nothing`. Throw an appropriate +error if `op` is invalid. `args` are the arguments to `op`. + +# Keyword arguments + +- `context`: The place where the operator occurs in the system/expression, or any other + relevant information. Useful for providing extra information in the error message. +""" +function validate_operator(op, args, iv; context = nothing) + error("`$validate_operator` is not implemented for operator `$op` in $context.") +end + +function validate_operator(op::Differential, args, iv; context = nothing) + isequal(op.x, iv) || throw(OperatorIndepvarMismatchError(op, iv, context)) + arg = unwrap(only(args)) + if !is_variable_floatingpoint(arg) + throw(ContinuousOperatorDiscreteArgumentError(op, arg, context)) + end +end + +struct ContinuousOperatorDiscreteArgumentError <: Exception + op::Any + arg::Any + context::Any +end + +function Base.showerror(io::IO, err::ContinuousOperatorDiscreteArgumentError) + print(io, """ + Operator $(err.op) expects continuous arguments, with a `symtype` such as `Number`, + `Real`, `Complex` or a subtype of `AbstractFloat`. Found $(err.arg) with a symtype of + $(symtype(err.arg))$(err.context === nothing ? "." : "in $(err.context).") + """) +end + +struct OperatorIndepvarMismatchError <: Exception + op::Any + iv::Any + context::Any +end + +function Base.showerror(io::IO, err::OperatorIndepvarMismatchError) + print(io, """ + Encountered operator `$(err.op)` which has different independent variable than the \ + one used in the system `$(err.iv)`. + """) + if err.context !== nothing + println(io) + print(io, "Context:\n$(err.context)") + end +end + function collect_vars!(unknowns, parameters, expr, iv; depth = 0, op = Symbolics.Operator) if issym(expr) collect_var!(unknowns, parameters, expr, iv; depth) From f8c05682aae06757905518db610eded4cdeea00a Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Fri, 25 Apr 2025 15:51:29 +0530 Subject: [PATCH 103/185] refactor: document `collect_vars!` and use `validate_operator` --- src/utils.jl | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/src/utils.jl b/src/utils.jl index 2e84c32b51..1ebccf7c8f 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -618,16 +618,30 @@ function Base.showerror(io::IO, err::OperatorIndepvarMismatchError) end end +""" + $(TYPEDSIGNATURES) + +Search through `expr` for all symbolic variables present in it. Populate `dvs` with +unknowns and `ps` with parameters present. `iv` should be the independent variable of the +system or `nothing` for time-independent systems. Expressions where the operator `isa op` +go through `validate_operator`. + +`depth` is a keyword argument which indicates how many levels down `expr` is from the root +of the system hierarchy. This is used to resolve scoping operators. The scope of a variable +can be checked using `check_scope_depth`. + +This function should return `nothing`. +""" function collect_vars!(unknowns, parameters, expr, iv; depth = 0, op = Symbolics.Operator) if issym(expr) - collect_var!(unknowns, parameters, expr, iv; depth) - else - for var in vars(expr; op) - if iscall(var) && operation(var) isa Differential - var, _ = var_from_nested_derivative(var) - end - collect_var!(unknowns, parameters, var, iv; depth) + return collect_var!(unknowns, parameters, expr, iv; depth) + end + for var in vars(expr; op) + while iscall(var) && operation(var) isa op + validate_operator(operation(var), arguments(var), iv; context = expr) + var = arguments(var)[1] end + collect_var!(unknowns, parameters, var, iv; depth) end return nothing end From cac9011b9da0bdccdc457f20811028e655287844 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Fri, 25 Apr 2025 15:51:45 +0530 Subject: [PATCH 104/185] fix: fix error message for events in time-independent systems --- src/systems/system.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/systems/system.jl b/src/systems/system.jl index 4fcf0716b3..030db7504b 100644 --- a/src/systems/system.jl +++ b/src/systems/system.jl @@ -159,7 +159,7 @@ function System(eqs::Vector{Equation}, iv, dvs, ps, brownians = []; continuous_events = SymbolicContinuousCallbacks(continuous_events) discrete_events = SymbolicDiscreteCallbacks(discrete_events) - if iv === nothing && !isempty(continuous_events) || !isempty(discrete_events) + if iv === nothing && (!isempty(continuous_events) || !isempty(discrete_events)) throw(EventsInTimeIndependentSystemError(continuous_events, discrete_events)) end From bbdb6fb5bba65ae31ae56d2a73eeb99865580263 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Fri, 25 Apr 2025 15:52:23 +0530 Subject: [PATCH 105/185] refactor: do not use `process_equations` in `System` constructor --- src/systems/system.jl | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/systems/system.jl b/src/systems/system.jl index 030db7504b..1b25dc23fe 100644 --- a/src/systems/system.jl +++ b/src/systems/system.jl @@ -182,7 +182,13 @@ end function System(eqs::Vector{Equation}, iv; kwargs...) iv === nothing && return System(eqs; kwargs...) - diffvars, allunknowns, ps, eqs = process_equations(eqs, iv) + + allunknowns = Set() + ps = Set() + for eq in eqs + collect_vars!(allunknowns, ps, eq, iv) + end + brownians = Set() for x in allunknowns x = unwrap(x) @@ -218,7 +224,6 @@ function System(eqs::Vector{Equation}, iv; kwargs...) end new_ps = gather_array_params(ps) - algevars = setdiff(allunknowns, diffvars) noiseeqs = get(kwargs, :noise_eqs, nothing) if noiseeqs !== nothing @@ -232,8 +237,7 @@ function System(eqs::Vector{Equation}, iv; kwargs...) end end - return System(eqs, iv, collect(Iterators.flatten((diffvars, algevars))), - collect(new_ps), brownians; kwargs...) + return System(eqs, iv, collect(allunknowns), collect(new_ps), brownians; kwargs...) end function System(eqs::Vector{Equation}; kwargs...) From bf69b497cab241aeccca3cdd45754681a14afc74 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Fri, 25 Apr 2025 15:52:36 +0530 Subject: [PATCH 106/185] fix: fix default `costs` in `System` constructor --- src/systems/system.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/systems/system.jl b/src/systems/system.jl index 1b25dc23fe..6d20b87c1b 100644 --- a/src/systems/system.jl +++ b/src/systems/system.jl @@ -394,7 +394,7 @@ function flatten(sys::System, noeqs = false) return System(noeqs ? Equation[] : equations(sys), get_iv(sys), unknowns(sys), parameters(sys; initial_parameters = true), brownians(sys); - jumps = jumps(sys), constraints = constraints(sys), costs = cost(sys), + jumps = jumps(sys), constraints = constraints(sys), costs = [cost(sys)], consolidate = default_consolidate, observed = observed(sys), parameter_dependencies = parameter_dependencies(sys), defaults = defaults(sys), guesses = guesses(sys), continuous_events = continuous_events(sys), From efecbe54427fbe2dfa9c0119b98c68d7696e2fec Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Fri, 25 Apr 2025 17:22:07 +0530 Subject: [PATCH 107/185] feat: add `is_floatingpoint_symtype` --- src/utils.jl | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/utils.jl b/src/utils.jl index 1ebccf7c8f..f44b6dfbef 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -1107,6 +1107,16 @@ function is_variable_floatingpoint(sym) T = symtype(sym) return T == Real || T <: AbstractFloat || T <: AbstractArray{Real} || T <: AbstractArray{<:AbstractFloat} + +""" + $(TYPEDSIGNATURES) + +Check if `T` is an appropriate symtype for a symbolic variable representing a floating +point number or array of such numbers. +""" +function is_floatingpoint_symtype(T::Type) + return T == Real || T == Number || T <: AbstractFloat || + T <: AbstractArray && is_floatingpoint_symtype(eltype(T)) end """ From 519938f7ea88fc79b740339cec30987824144d3a Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Fri, 25 Apr 2025 17:22:22 +0530 Subject: [PATCH 108/185] refactor: use `is_floatingpoint_symtype` in `is_variable_floatingpoint` --- src/utils.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/utils.jl b/src/utils.jl index f44b6dfbef..95d4b4ffeb 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -1105,8 +1105,8 @@ Check if `sym` represents a symbolic floating point number or array of such numb function is_variable_floatingpoint(sym) sym = unwrap(sym) T = symtype(sym) - return T == Real || T <: AbstractFloat || T <: AbstractArray{Real} || - T <: AbstractArray{<:AbstractFloat} + is_floatingpoint_symtype(T) +end """ $(TYPEDSIGNATURES) From 29e38e8ca3fc4f7662316aeab71679e9540d1c81 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Fri, 25 Apr 2025 19:10:09 +0530 Subject: [PATCH 109/185] refactor: allow real values in `costs` --- src/systems/system.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/systems/system.jl b/src/systems/system.jl index 6d20b87c1b..8ec524a658 100644 --- a/src/systems/system.jl +++ b/src/systems/system.jl @@ -19,7 +19,7 @@ struct System <: AbstractSystem noise_eqs::Union{Nothing, AbstractVector, AbstractMatrix} jumps::Vector{Any} constraints::Vector{Union{Equation, Inequality}} - costs::Vector{<:BasicSymbolic} + costs::Vector{<:Union{BasicSymbolic, Real}} consolidate::Any unknowns::Vector ps::Vector From 4570a2f30b0505eb1e7ea7d5ac78e082a57912c8 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Fri, 25 Apr 2025 19:12:29 +0530 Subject: [PATCH 110/185] feat: add `initializesystem` field to `System` --- src/systems/system.jl | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/systems/system.jl b/src/systems/system.jl index 8ec524a658..e486fc1310 100644 --- a/src/systems/system.jl +++ b/src/systems/system.jl @@ -49,6 +49,7 @@ struct System <: AbstractSystem ignored_connections::Union{ Nothing, Tuple{Vector{IgnoredAnalysisPoint}, Vector{IgnoredAnalysisPoint}}} parent::Union{Nothing, System} + initializesystem::Union{Nothing, System} is_initializesystem::Bool isscheduled::Bool schedule::Union{Schedule, Nothing} @@ -61,9 +62,8 @@ struct System <: AbstractSystem metadata = nothing, gui_metadata = nothing, is_dde = false, tstops = [], tearing_state = nothing, namespacing = true, complete = false, index_cache = nothing, ignored_connections = nothing, - parent = nothing, is_initializesystem = false, isscheduled = false, - schedule = nothing; checks::Union{Bool, Int} = true) - + parent = nothing, initializesystem = nothing, is_initializesystem = false, + isscheduled = false, schedule = nothing; checks::Union{Bool, Int} = true) if is_initializesystem && iv !== nothing throw(ArgumentError(""" Expected initialization system to be time-independent. Found independent @@ -93,7 +93,7 @@ struct System <: AbstractSystem guesses, systems, initialization_eqs, continuous_events, discrete_events, connector_type, assertions, metadata, gui_metadata, is_dde, tstops, tearing_state, namespacing, complete, index_cache, ignored_connections, - parent, is_initializesystem, isscheduled, schedule) + parent, initializesystem, is_initializesystem, isscheduled, schedule) end end @@ -110,8 +110,8 @@ function System(eqs::Vector{Equation}, iv, dvs, ps, brownians = []; connector_type = nothing, assertions = Dict{BasicSymbolic, String}(), metadata = nothing, gui_metadata = nothing, is_dde = nothing, tstops = [], tearing_state = nothing, ignored_connections = nothing, parent = nothing, - description = "", name = nothing, discover_from_metadata = true, is_initializesystem = false, - checks = true) + description = "", name = nothing, discover_from_metadata = true, + initializesystem = nothing, is_initializesystem = false, checks = true) name === nothing && throw(NoNameError()) iv = unwrap(iv) @@ -173,7 +173,8 @@ function System(eqs::Vector{Equation}, iv, dvs, ps, brownians = []; costs, consolidate, dvs, ps, brownians, iv, observed, parameter_dependencies, var_to_name, name, description, defaults, guesses, systems, initialization_eqs, continuous_events, discrete_events, connector_type, assertions, metadata, gui_metadata, is_dde, - tstops, tearing_state, true, false, nothing, ignored_connections, parent, is_initializesystem; checks) + tstops, tearing_state, true, false, nothing, ignored_connections, parent, + initializesystem, is_initializesystem; checks) end function System(eqs::Vector{Equation}, dvs, ps; kwargs...) From 010ec831778c56114a790cba6faad721152061d4 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Fri, 25 Apr 2025 19:12:49 +0530 Subject: [PATCH 111/185] fix: fix brownians passed to `System` constructor --- src/systems/system.jl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/systems/system.jl b/src/systems/system.jl index e486fc1310..5667d0605f 100644 --- a/src/systems/system.jl +++ b/src/systems/system.jl @@ -238,7 +238,8 @@ function System(eqs::Vector{Equation}, iv; kwargs...) end end - return System(eqs, iv, collect(allunknowns), collect(new_ps), brownians; kwargs...) + return System( + eqs, iv, collect(allunknowns), collect(new_ps), collect(brownians); kwargs...) end function System(eqs::Vector{Equation}; kwargs...) From 274745d4d22d02d3ed815b89e97f7a6652a16229 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Fri, 25 Apr 2025 19:13:03 +0530 Subject: [PATCH 112/185] feat: add `System(::Equation, ...)` constructor --- src/systems/system.jl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/systems/system.jl b/src/systems/system.jl index 5667d0605f..330b2b34bb 100644 --- a/src/systems/system.jl +++ b/src/systems/system.jl @@ -262,6 +262,8 @@ function System(eqs::Vector{Equation}; kwargs...) return System(eqs, nothing, collect(allunknowns), collect(new_ps); kwargs...) end +System(eq::Equation, args...; kwargs...) = System([eq], args...; kwargs...) + function gather_array_params(ps) new_ps = OrderedSet() for p in ps From 6d4f366dc75e2ff64ce81cfad8eb0e192fef74d0 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Fri, 25 Apr 2025 19:13:19 +0530 Subject: [PATCH 113/185] fix: fix `flatten(::System)` --- src/systems/system.jl | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/systems/system.jl b/src/systems/system.jl index 330b2b34bb..7b5c8e5d8b 100644 --- a/src/systems/system.jl +++ b/src/systems/system.jl @@ -395,10 +395,15 @@ end function flatten(sys::System, noeqs = false) systems = get_systems(sys) isempty(systems) && return sys - + costs = cost(sys) + if _iszero(costs) + costs = Union{Real, BasicSymbolic}[] + else + costs = [costs] + end return System(noeqs ? Equation[] : equations(sys), get_iv(sys), unknowns(sys), parameters(sys; initial_parameters = true), brownians(sys); - jumps = jumps(sys), constraints = constraints(sys), costs = [cost(sys)], + jumps = jumps(sys), constraints = constraints(sys), costs = costs, consolidate = default_consolidate, observed = observed(sys), parameter_dependencies = parameter_dependencies(sys), defaults = defaults(sys), guesses = guesses(sys), continuous_events = continuous_events(sys), @@ -493,8 +498,8 @@ function Base.:(==)(sys1::System, sys2::System) _eq_unordered(get_tstops(sys1), get_tstops(sys2)) && # not comparing tearing states because checking if they're equal up to ordering # is difficult - get_namespacing(sys1) == get_namespacing(sys2) && - get_complete(sys1) == get_complete(sys2) && + getfield(sys1, :namespacing) == getfield(sys2, :namespacing) && + getfield(sys1, :complete) == getfield(sys2, :complete) && ignored_connections_equal(sys1, sys2) && get_parent(sys1) == get_parent(sys2) && get_isscheduled(sys1) == get_isscheduled(sys2) && From 1d22c639a35209dc462622a662c75d1532292912 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Fri, 25 Apr 2025 19:15:04 +0530 Subject: [PATCH 114/185] fix: unwrap costs in `System` constructor --- src/systems/system.jl | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/systems/system.jl b/src/systems/system.jl index 7b5c8e5d8b..d838b60789 100644 --- a/src/systems/system.jl +++ b/src/systems/system.jl @@ -128,6 +128,11 @@ function System(eqs::Vector{Equation}, iv, dvs, ps, brownians = []; noise_eqs = unwrap.(noise_eqs) end + costs = unwrap.(costs) + if isempty(costs) + costs = Union{BasicSymbolic, Real}[] + end + parameter_dependencies, ps = process_parameter_dependencies(parameter_dependencies, ps) defaults = anydict(defaults) guesses = anydict(guesses) From 424a5823f15551cfb26c9b75da86ed2a8f1f27ef Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Fri, 25 Apr 2025 19:15:14 +0530 Subject: [PATCH 115/185] test: don't pass dvs/ps to `ODEFunction` --- test/precompile_test/ODEPrecompileTest.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/precompile_test/ODEPrecompileTest.jl b/test/precompile_test/ODEPrecompileTest.jl index 911f45152e..2111f7ba64 100644 --- a/test/precompile_test/ODEPrecompileTest.jl +++ b/test/precompile_test/ODEPrecompileTest.jl @@ -15,7 +15,7 @@ function system(; kwargs...) @named de = System(eqs, t) de = complete(de) - return ODEFunction(de, [x, y, z], [σ, ρ, β]; kwargs...) + return ODEFunction(de; kwargs...) end # Build an ODEFunction as part of the module's precompilation. These cases From d7e4cd539e2b8a8339e3bac55d695cbd0ad7d235 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Fri, 25 Apr 2025 19:15:38 +0530 Subject: [PATCH 116/185] test: fix shadowing of `System` in tests --- test/domain_connectors.jl | 4 ++-- test/state_selection.jl | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/test/domain_connectors.jl b/test/domain_connectors.jl index 03d6ff264a..e35cee8f30 100644 --- a/test/domain_connectors.jl +++ b/test/domain_connectors.jl @@ -123,7 +123,7 @@ function Valve2Port(; p_s_int, p_r_int, p_int, name) System(eqs, t, vars, pars; name, systems) end -function System(; name) +function HydraulicSystem(; name) vars = [] pars = [] systems = @named begin @@ -142,7 +142,7 @@ function System(; name) return System(eqs, t, vars, pars; systems, name) end -@named odesys = System() +@named odesys = HydraulicSystem() esys = ModelingToolkit.expand_connections(odesys) @test length(equations(esys)) == length(unknowns(esys)) diff --git a/test/state_selection.jl b/test/state_selection.jl index b8bec5d7b7..6db8e8c5a0 100644 --- a/test/state_selection.jl +++ b/test/state_selection.jl @@ -100,7 +100,7 @@ let D(v) * rho * L ~ (fluid_port_a.p - fluid_port_b.p - dp_z)] compose(System(eqs, t, sts, ps; name = name), [fluid_port_a, fluid_port_b]) end - function System(; name, L = 10.0) + function HydraulicSystem(; name, L = 10.0) @named compensator = Compensator() @named source = Source() @named substation = Substation() @@ -116,7 +116,7 @@ let compose(System(eqs, t, [], ps; name = name), subs) end - @named system = System(L = 10) + @named system = HydraulicSystem(L = 10) @unpack supply_pipe, return_pipe = system sys = structural_simplify(system) u0 = [ From aeebf31ccf77bb5b34cc9715697e7a9d837451fb Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Fri, 25 Apr 2025 19:15:56 +0530 Subject: [PATCH 117/185] test: pass `u0map` and `tspan` to `ODEProblem` --- test/parameter_dependencies.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/parameter_dependencies.jl b/test/parameter_dependencies.jl index 4e3743b84f..ae533d2805 100644 --- a/test/parameter_dependencies.jl +++ b/test/parameter_dependencies.jl @@ -58,7 +58,7 @@ end t; parameter_dependencies = [p2 => 2p1] ) - prob = ODEProblem(complete(sys)) + prob = ODEProblem(complete(sys), [], (0.0, 1.0)) setp1! = setp(prob, p1) get_p1 = getp(prob, p1) get_p2 = getp(prob, p2) @@ -112,7 +112,7 @@ end t; parameter_dependencies = [p2 => 2p1] ) - prob = ODEProblem(complete(sys)) + prob = ODEProblem(complete(sys), [], (0.0, 1.0)) get_dep = getu(prob, 2p1) @test get_dep(prob) == [2.0, 4.0] end From 5bda91451a732cf04831dd00a52f70c9f1ac6d92 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Fri, 25 Apr 2025 19:16:56 +0530 Subject: [PATCH 118/185] test: create `JumpProblem` directly --- test/jumpsystem.jl | 74 +++++++++++++++++----------------- test/parameter_dependencies.jl | 8 ++-- 2 files changed, 41 insertions(+), 41 deletions(-) diff --git a/test/jumpsystem.jl b/test/jumpsystem.jl index 6c96055270..3e95e09ab6 100644 --- a/test/jumpsystem.jl +++ b/test/jumpsystem.jl @@ -68,8 +68,9 @@ p = (0.1 / 1000, 0.01); tspan = (0.0, 250.0); u₀map = [S => 999, I => 1, R => 0] parammap = [β => 0.1 / 1000, γ => 0.01] -dprob = DiscreteProblem(js2, u₀map, tspan, parammap) -jprob = JumpProblem(js2, dprob, Direct(); save_positions = (false, false), rng) +jprob = JumpProblem(js2, u₀map, tspan, parammap; aggregator = Direct(), + save_positions = (false, false), rng) +@test jprob.prob isa DiscreteProblem Nsims = 30000 function getmean(jprob, Nsims; use_stepper = true) m = 0.0 @@ -82,7 +83,7 @@ end m = getmean(jprob, Nsims) # test auto-alg selection works -jprobb = JumpProblem(js2, dprob; save_positions = (false, false), rng) +jprobb = JumpProblem(js2, u₀map, tspan, parammap; save_positions = (false, false), rng) mb = getmean(jprobb, Nsims; use_stepper = false) @test abs(m - mb) / m < 0.01 @@ -90,13 +91,15 @@ mb = getmean(jprobb, Nsims; use_stepper = false) obs = [S2 ~ 2 * S] @named js2b = JumpSystem([j₁, j₃], t, [S, I, R], [β, γ], observed = obs) js2b = complete(js2b) -dprob = DiscreteProblem(js2b, u₀map, tspan, parammap) -jprob = JumpProblem(js2b, dprob, Direct(); save_positions = (false, false), rng) +jprob = JumpProblem(js2b, u₀map, tspan, parammap; aggregator = Direct(), + save_positions = (false, false), rng) +@test jprob.prob isa DiscreteProblem sol = solve(jprob, SSAStepper(); saveat = tspan[2] / 10) @test all(2 .* sol[S] .== sol[S2]) # test save_positions is working -jprob = JumpProblem(js2, dprob, Direct(); save_positions = (false, false), rng) +jprob = JumpProblem(js2, u₀map, tspan, parammap; aggregator = Direct(), + save_positions = (false, false), rng) sol = solve(jprob, SSAStepper(); saveat = 1.0) @test all((sol.t) .== collect(0.0:tspan[2])) @@ -142,18 +145,20 @@ maj1 = MassActionJump(2 * β / 2, [S => 1, I => 1], [S => -1, I => 1]) maj2 = MassActionJump(γ, [I => 1], [I => -1, R => 1]) @named js3 = JumpSystem([maj1, maj2], t, [S, I, R], [β, γ]) js3 = complete(js3) -dprob = DiscreteProblem(js3, u₀map, tspan, parammap) -jprob = JumpProblem(js3, dprob, Direct(); rng) +jprob = JumpProblem(js3, u₀map, tspan, parammap; aggregator = Direct(), rng) +@test jprob.prob isa DiscreteProblem m3 = getmean(jprob, Nsims) @test abs(m - m3) / m < 0.01 # maj jump test with various dep graphs @named js3b = JumpSystem([maj1, maj2], t, [S, I, R], [β, γ]) js3b = complete(js3b) -jprobb = JumpProblem(js3b, dprob, NRM(); rng) +jprobb = JumpProblem(js3b, u₀map, tspan, parammap; aggregator = NRM(), rng) +@test jprobb.prob isa DiscreteProblem m4 = getmean(jprobb, Nsims) @test abs(m - m4) / m < 0.01 -jprobc = JumpProblem(js3b, dprob, RSSA(); rng) +jprobc = JumpProblem(js3b, u₀map, tspan, parammap; aggregator = RSSA(), rng) +@test jprobc.prob isa DiscreteProblem m4 = getmean(jprobc, Nsims) @test abs(m - m4) / m < 0.01 @@ -162,8 +167,9 @@ maj1 = MassActionJump(2.0, [0 => 1], [S => 1]) maj2 = MassActionJump(γ, [S => 1], [S => -1]) @named js4 = JumpSystem([maj1, maj2], t, [S], [β, γ]) js4 = complete(js4) -dprob = DiscreteProblem(js4, [S => 999], (0, 1000.0), [β => 100.0, γ => 0.01]) -jprob = JumpProblem(js4, dprob, Direct(); rng) +jprob = JumpProblem( + js4, [S => 999], (0, 1000.0), [β => 100.0, γ => 0.01]; aggregator = Direct(), rng) +@test jprob.prob isa DiscreteProblem m4 = getmean(jprob, Nsims) @test abs(m4 - 2.0 / 0.01) * 0.01 / 2.0 < 0.01 @@ -172,8 +178,9 @@ maj1 = MassActionJump(2.0, [0 => 1], [S => 1]) maj2 = MassActionJump(γ, [S => 2], [S => -1]) @named js4 = JumpSystem([maj1, maj2], t, [S], [β, γ]) js4 = complete(js4) -dprob = DiscreteProblem(js4, [S => 999], (0, 1000.0), [β => 100.0, γ => 0.01]) -jprob = JumpProblem(js4, dprob, Direct(); rng) +jprob = JumpProblem( + js4, [S => 999], (0, 1000.0), [β => 100.0, γ => 0.01]; aggregator = Direct(), rng) +@test jprob.prob isa DiscreteProblem sol = solve(jprob, SSAStepper()); # issue #819 @@ -195,8 +202,9 @@ let p = [k1 => 2.0, k2 => 0.0, k3 => 0.5] u₀ = [A => 100, B => 0] tspan = (0.0, 2000.0) - dprob = DiscreteProblem(js5, u₀, tspan, p) - jprob = JumpProblem(js5, dprob, Direct(); save_positions = (false, false), rng) + jprob = JumpProblem( + js5, u₀, tspan, p; aggregator = Direct(), save_positions = (false, false), rng) + @test jprob.prob isa DiscreteProblem @test all(jprob.massaction_jump.scaled_rates .== [1.0, 0.0]) pcondit(u, t, integrator) = t == 1000.0 @@ -259,15 +267,10 @@ u0 = [X => 10] tspan = (0.0, 1.0) ps = [k => 1.0] -dp1 = DiscreteProblem(js1, u0, tspan, ps) -dp2 = DiscreteProblem(js2, u0, tspan) -dp3 = DiscreteProblem(js3, u0, tspan, ps) -dp4 = DiscreteProblem(js4, u0, tspan) - -@test_nowarn jp1 = JumpProblem(js1, dp1, Direct()) -@test_nowarn jp2 = JumpProblem(js2, dp2, Direct()) -@test_nowarn jp3 = JumpProblem(js3, dp3, Direct()) -@test_nowarn jp4 = JumpProblem(js4, dp4, Direct()) +@test_nowarn jp1 = JumpProblem(js1, u0, tspan, ps; aggregator = Direct()) +@test_nowarn jp2 = JumpProblem(js2, u0, tspan; aggregator = Direct()) +@test_nowarn jp3 = JumpProblem(js3, u0, tspan, ps; aggregator = Direct()) +@test_nowarn jp4 = JumpProblem(js4, u0, tspan; aggregator = Direct()) # Ensure `structural_simplify` (and `@mtkbuild`) works on JumpSystem (by doing nothing) # Issue#2558 @@ -292,8 +295,7 @@ let for (N, algtype) in zip(Nv, algtypes) @named jsys = JumpSystem([deepcopy(j1) for _ in 1:N], t, [X], [k]) jsys = complete(jsys) - dprob = DiscreteProblem(jsys, [X => 10], (0.0, 10.0), [k => 1]) - jprob = JumpProblem(jsys, dprob) + jprob = JumpProblem(jsys, [X => 10], (0.0, 10.0), [k => 1]) @test jprob.aggregator isa algtype end end @@ -306,8 +308,9 @@ let @parameters k vrj = VariableRateJump(k * (sin(t) + 1), [A ~ A + 1, C ~ C + 2]) js = complete(JumpSystem([vrj], t, [A, C], [k]; name = :js, observed = [B ~ C * A])) - oprob = ODEProblem(js, [A => 0, C => 0], (0.0, 10.0), [k => 1.0]) - jprob = JumpProblem(js, oprob, Direct(); rng) + jprob = JumpProblem( + js, [A => 0, C => 0], (0.0, 10.0), [k => 1.0]; aggregtor = Direct(), rng) + @test jprob.prob isa ODEProblem sol = solve(jprob, Tsit5()) # test observed and symbolic indexing work @@ -439,8 +442,7 @@ let k2val = 20.0 p = [k1 => k1val, k2 => k2val] tspan = (0.0, 10.0) - oprob = ODEProblem(jsys, u0, tspan, p) - jprob = JumpProblem(jsys, oprob; rng, save_positions = (false, false)) + jprob = JumpProblem(jsys, u0, tspan, p; rng, save_positions = (false, false)) times = range(0.0, tspan[2], length = 100) Nsims = 4000 @@ -479,8 +481,7 @@ let u0map = [X => p.X₀, Y => p.Y₀] pmap = [α => p.α, β => p.β] tspan = (0.0, 20.0) - oprob = ODEProblem(jsys, u0map, tspan, pmap) - jprob = JumpProblem(jsys, oprob; rng, save_positions = (false, false)) + jprob = JumpProblem(jsys, u0, tspan, pmap; rng, save_positions = (false, false)) times = range(0.0, tspan[2], length = 100) Nsims = 4000 Xv = zeros(length(times)) @@ -518,8 +519,7 @@ let continuous_events = cevents) jsys = complete(jsys) tspan = (0.0, 200.0) - oprob = ODEProblem(jsys, u0map, tspan, pmap) - jprob = JumpProblem(jsys, oprob; rng, save_positions = (false, false)) + jprob = JumpProblem(jsys, u0, tspan, pmap; rng, save_positions = (false, false)) Xsamp = 0.0 Nsims = 4000 for n in 1:Nsims @@ -544,8 +544,8 @@ end # Works. @mtkbuild js = JumpSystem([j1, j2], t, [X], [p, d]) - dprob = DiscreteProblem(js, [X => 15], (0.0, 10.0), [p => 2.0, d => 0.5]) - jprob = JumpProblem(js, dprob, Direct()) + jprob = JumpProblem( + js, [X => 15], (0.0, 10.0), [p => 2.0, d => 0.5]; aggregator = Direct()) sol = solve(jprob, SSAStepper()) @test eltype(sol[X]) === Int64 end diff --git a/test/parameter_dependencies.jl b/test/parameter_dependencies.jl index ae533d2805..779b7c0b79 100644 --- a/test/parameter_dependencies.jl +++ b/test/parameter_dependencies.jl @@ -300,8 +300,8 @@ end tspan = (0.0, 250.0) u₀map = [S => 999, I => 1, R => 0] parammap = [γ => 0.01] - dprob = DiscreteProblem(js2, u₀map, tspan, parammap) - jprob = JumpProblem(js2, dprob, Direct(), save_positions = (false, false), rng = rng) + jprob = JumpProblem(js2, u₀map, tspan, parammap; aggregator = Direct(), + save_positions = (false, false), rng = rng) @test jprob.ps[γ] == 0.01 @test jprob.ps[β] == 0.0001 @test_nowarn solve(jprob, SSAStepper()) @@ -310,8 +310,8 @@ end [j₁, j₃], t, [S, I, R], [γ]; parameter_dependencies = [β => 0.01γ], discrete_events = [[10.0] => [γ ~ 0.02]]) js2 = complete(js2) - dprob = DiscreteProblem(js2, u₀map, tspan, parammap) - jprob = JumpProblem(js2, dprob, Direct(), save_positions = (false, false), rng = rng) + jprob = JumpProblem(js2, u₀map, tspan, parammap; aggregator = Direct(), + save_positions = (false, false), rng = rng) integ = init(jprob, SSAStepper()) @test integ.ps[γ] == 0.01 @test integ.ps[β] == 0.0001 From 9e42c7726e272856fd3c4e8f1dc25e1e0e443ffc Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Fri, 25 Apr 2025 19:17:09 +0530 Subject: [PATCH 119/185] test: pass `Vector{Equation}` to `System` --- test/structural_transformation/tearing.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/structural_transformation/tearing.jl b/test/structural_transformation/tearing.jl index 5bf88e1dd5..0cbabbfa3b 100644 --- a/test/structural_transformation/tearing.jl +++ b/test/structural_transformation/tearing.jl @@ -183,7 +183,7 @@ end m = 1.0 @named mass = Translational_Mass(m = m) -ms_eqs = [] +ms_eqs = Equation[] @named _ms_model = System(ms_eqs, t) @named ms_model = compose(_ms_model, From acef5e26b11c14838a5d712f45e98f2f5ae71685 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Sun, 27 Apr 2025 11:05:43 +0530 Subject: [PATCH 120/185] fix: correctly order unknowns in `System` constructor --- src/systems/system.jl | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/src/systems/system.jl b/src/systems/system.jl index d838b60789..0b6439cb01 100644 --- a/src/systems/system.jl +++ b/src/systems/system.jl @@ -189,12 +189,35 @@ end function System(eqs::Vector{Equation}, iv; kwargs...) iv === nothing && return System(eqs; kwargs...) - allunknowns = Set() + diffvars = OrderedSet() + othervars = OrderedSet() ps = Set() + diffeqs = Equation[] + othereqs = Equation[] for eq in eqs - collect_vars!(allunknowns, ps, eq, iv) + if !(eq.lhs isa Union{Symbolic, Number}) + push!(othereqs, eq) + continue + end + collect_vars!(othervars, ps, eq, iv) + if iscall(eq.lhs) && operation(eq.lhs) isa Differential + var, _ = var_from_nested_derivative(eq.lhs) + if var in diffvars + throw(ArgumentError(""" + The differential variable $var is not unique in the system of \ + equations. + """)) + end + push!(diffvars, var) + push!(diffeqs, eq) + else + push!(othereqs, eq) + end end + allunknowns = union(diffvars, othervars) + eqs = [diffeqs; othereqs] + brownians = Set() for x in allunknowns x = unwrap(x) From aee746a8c5d0630d109656e5bef43006f4d39971 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Sun, 27 Apr 2025 13:38:32 +0530 Subject: [PATCH 121/185] refactor: remove `build_torn_function`, `tearing_assignments` --- .../StructuralTransformations.jl | 4 +- src/structural_transformation/codegen.jl | 139 ------------------ .../symbolics_tearing.jl | 13 -- 3 files changed, 2 insertions(+), 154 deletions(-) diff --git a/src/structural_transformation/StructuralTransformations.jl b/src/structural_transformation/StructuralTransformations.jl index 16d3a75464..06d8e440cc 100644 --- a/src/structural_transformation/StructuralTransformations.jl +++ b/src/structural_transformation/StructuralTransformations.jl @@ -57,11 +57,11 @@ using DocStringExtensions export tearing, partial_state_selection, dae_index_lowering, check_consistency export dummy_derivative -export build_torn_function, build_observed_function, ODAEProblem +export build_observed_function, ODAEProblem export sorted_incidence_matrix, pantelides!, pantelides_reassemble, tearing_reassemble, find_solvables!, linear_subsys_adjmat! -export tearing_assignments, tearing_substitution +export tearing_substitution export torn_system_jacobian_sparsity export full_equations export but_ordered_incidence, lowest_order_variable_mask, highest_order_variable_mask diff --git a/src/structural_transformation/codegen.jl b/src/structural_transformation/codegen.jl index d2ca5b8748..144e19aa31 100644 --- a/src/structural_transformation/codegen.jl +++ b/src/structural_transformation/codegen.jl @@ -226,145 +226,6 @@ function gen_nlsolve!(is_not_prepended_assignment, eqs, vars, u0map::AbstractDic nlsolve_expr end -function build_torn_function(sys; - expression = false, - jacobian_sparsity = true, - checkbounds = false, - max_inlining_size = nothing, - kw...) - max_inlining_size = something(max_inlining_size, MAX_INLINE_NLSOLVE_SIZE) - rhss = [] - eqs = equations(sys) - eqs_idxs = Int[] - for (i, eq) in enumerate(eqs) - isdiffeq(eq) || continue - push!(eqs_idxs, i) - push!(rhss, eq.rhs) - end - - state = get_or_construct_tearing_state(sys) - fullvars = state.fullvars - var_eq_matching, var_sccs = algebraic_variables_scc(state) - condensed_graph = MatchedCondensationGraph( - DiCMOBiGraph{true}(complete(state.structure.graph), - complete(var_eq_matching)), - var_sccs) - toporder = topological_sort_by_dfs(condensed_graph) - var_sccs = var_sccs[toporder] - - unknowns_idxs = collect(diffvars_range(state.structure)) - mass_matrix_diag = ones(length(unknowns_idxs)) - - assignments, deps, sol_states = tearing_assignments(sys) - invdeps = map(_ -> BitSet(), deps) - for (i, d) in enumerate(deps) - for a in d - push!(invdeps[a], i) - end - end - var2assignment = Dict{Any, Int}(eq.lhs => i for (i, eq) in enumerate(assignments)) - is_not_prepended_assignment = trues(length(assignments)) - - torn_expr = Assignment[] - - defs = defaults(sys) - nlsolve_scc_idxs = Int[] - - needs_extending = false - @views for (i, scc) in enumerate(var_sccs) - torn_vars_idxs = Int[var for var in scc if var_eq_matching[var] !== unassigned] - torn_eqs_idxs = [var_eq_matching[var] for var in torn_vars_idxs] - isempty(torn_eqs_idxs) && continue - if length(torn_eqs_idxs) <= max_inlining_size - nlsolve_expr = gen_nlsolve!(is_not_prepended_assignment, eqs[torn_eqs_idxs], - fullvars[torn_vars_idxs], defs, assignments, - (deps, invdeps), var2assignment, - checkbounds = checkbounds) - append!(torn_expr, nlsolve_expr) - push!(nlsolve_scc_idxs, i) - else - needs_extending = true - append!(eqs_idxs, torn_eqs_idxs) - append!(rhss, map(x -> x.rhs, eqs[torn_eqs_idxs])) - append!(unknowns_idxs, torn_vars_idxs) - append!(mass_matrix_diag, zeros(length(torn_eqs_idxs))) - end - end - sort!(unknowns_idxs) - - mass_matrix = needs_extending ? Diagonal(mass_matrix_diag) : I - - out = Sym{Any}(gensym("out")) - funbody = SetArray(!checkbounds, - out, - rhss) - - unknown_vars = Any[fullvars[i] for i in unknowns_idxs] - @set! sys.solved_unknowns = unknown_vars - - pre = get_postprocess_fbody(sys) - cpre = get_preprocess_constants(rhss) - pre2 = x -> pre(cpre(x)) - - expr = SymbolicUtils.Code.toexpr( - Func( - [out - DestructuredArgs(unknown_vars, - inbounds = !checkbounds) - DestructuredArgs(parameters(sys), - inbounds = !checkbounds) - independent_variables(sys)], - [], - pre2(Let([torn_expr; - assignments[is_not_prepended_assignment]], - funbody, - false))), - sol_states) - if expression - expr, unknown_vars - else - observedfun = let state = state, - dict = Dict(), - is_solver_unknown_idxs = insorted.(1:length(fullvars), (unknowns_idxs,)), - assignments = assignments, - deps = (deps, invdeps), - sol_states = sol_states, - var2assignment = var2assignment - - function generated_observed(obsvar, args...) - obs = get!(dict, value(obsvar)) do - build_observed_function(state, obsvar, var_eq_matching, var_sccs, - is_solver_unknown_idxs, assignments, deps, - sol_states, var2assignment, - checkbounds = checkbounds) - end - if args === () - let obs = obs - (u, p, t) -> obs(u, p, t) - end - else - obs(args...) - end - end - end - - ODEFunction{true, SciMLBase.AutoSpecialize}( - drop_expr(@RuntimeGeneratedFunction(expr)), - sparsity = jacobian_sparsity ? - torn_system_with_nlsolve_jacobian_sparsity(state, - var_eq_matching, - var_sccs, - nlsolve_scc_idxs, - eqs_idxs, - unknowns_idxs) : - nothing, - observed = observedfun, - mass_matrix = mass_matrix, - sys = sys), - unknown_vars - end -end - """ find_solve_sequence(sccs, vars) diff --git a/src/structural_transformation/symbolics_tearing.jl b/src/structural_transformation/symbolics_tearing.jl index cb828a00a6..df4f5bb838 100644 --- a/src/structural_transformation/symbolics_tearing.jl +++ b/src/structural_transformation/symbolics_tearing.jl @@ -154,19 +154,6 @@ function tearing_substitution(sys::AbstractSystem; kwargs...) @set! sys.schedule = nothing end -function tearing_assignments(sys::AbstractSystem) - if empty_substitutions(sys) - assignments = [] - deps = Int[] - sol_states = Code.LazyState() - else - @unpack subs, deps = get_substitutions(sys) - assignments = [Assignment(eq.lhs, eq.rhs) for eq in subs] - sol_states = Code.NameState(Dict(eq.lhs => Symbol(eq.lhs) for eq in subs)) - end - return assignments, deps, sol_states -end - function solve_equation(eq, var, simplify) rhs = value(symbolic_linear_solve(eq, var; simplify = simplify, check = false)) occursin(var, rhs) && throw(EquationSolveErrors(eq, var, rhs)) From 27094a83188421b9b39f2611da9b3b6aa61d0fac Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Sun, 27 Apr 2025 13:40:26 +0530 Subject: [PATCH 122/185] refactor: remove `get_substitutions`, `has_substitutions` field getters --- src/systems/abstractsystem.jl | 1 - 1 file changed, 1 deletion(-) diff --git a/src/systems/abstractsystem.jl b/src/systems/abstractsystem.jl index e8f46064ba..d9b438aba9 100644 --- a/src/systems/abstractsystem.jl +++ b/src/systems/abstractsystem.jl @@ -895,7 +895,6 @@ for prop in [:eqs :initialization_eqs :schedule :tearing_state - :substitutions :metadata :gui_metadata :is_initializesystem From 1f578ccecc39210c5b40e072170b18171d9dbd44 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Sun, 27 Apr 2025 13:43:43 +0530 Subject: [PATCH 123/185] refactor: implement `empty_substitutions` and `get_substitutions` using `observed` --- src/utils.jl | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/utils.jl b/src/utils.jl index 95d4b4ffeb..c861a6cd20 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -818,12 +818,6 @@ end isarray(x) = x isa AbstractArray || x isa Symbolics.Arr -function empty_substitutions(sys) - has_substitutions(sys) || return true - subs = get_substitutions(sys) - isnothing(subs) || isempty(subs.deps) -end - function get_cmap(sys, exprs = nothing) #Inject substitutions for constants => values buffer = [] @@ -866,6 +860,12 @@ function get_substitutions_and_solved_unknowns(sys, exprs = nothing; no_postproc end end return pre, sol_states +function empty_substitutions(sys) + isempty(observed(sys)) +end + +function get_substitutions(sys) + Dict([eq.lhs => eq.rhs for eq in observed(sys)]) end function mergedefaults(defaults, varmap, vars) From bb286e02900f5924f383b114b7a5e8a7bd5622a5 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Sun, 27 Apr 2025 13:43:51 +0530 Subject: [PATCH 124/185] refactor: remove `get_substitutions_and_solved_unknowns` --- src/utils.jl | 23 ----------------------- 1 file changed, 23 deletions(-) diff --git a/src/utils.jl b/src/utils.jl index c861a6cd20..938539876c 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -837,29 +837,6 @@ function get_cmap(sys, exprs = nothing) return cmap, cs end -function get_substitutions_and_solved_unknowns(sys, exprs = nothing; no_postprocess = false) - cmap, cs = get_cmap(sys, exprs) - if empty_substitutions(sys) && isempty(cs) - sol_states = Code.LazyState() - pre = no_postprocess ? (ex -> ex) : get_postprocess_fbody(sys) - else # Have to do some work - if !empty_substitutions(sys) - @unpack subs = get_substitutions(sys) - else - subs = [] - end - subs = [cmap; subs] # The constants need to go first - sol_states = Code.NameState(Dict(eq.lhs => Symbol(eq.lhs) for eq in subs)) - if no_postprocess - pre = ex -> Let(Assignment[Assignment(eq.lhs, eq.rhs) for eq in subs], ex, - false) - else - process = get_postprocess_fbody(sys) - pre = ex -> Let(Assignment[Assignment(eq.lhs, eq.rhs) for eq in subs], - process(ex), false) - end - end - return pre, sol_states function empty_substitutions(sys) isempty(observed(sys)) end From e5e06dd53717bb32450b722867027b1faf1ac98e Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Sun, 27 Apr 2025 13:39:06 +0530 Subject: [PATCH 125/185] refactor: update `tearing_substitute_expr`, `full_equations` to use `observed` --- .../symbolics_tearing.jl | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/src/structural_transformation/symbolics_tearing.jl b/src/structural_transformation/symbolics_tearing.jl index df4f5bb838..8979ac77c8 100644 --- a/src/structural_transformation/symbolics_tearing.jl +++ b/src/structural_transformation/symbolics_tearing.jl @@ -107,8 +107,7 @@ end function tearing_substitute_expr(sys::AbstractSystem, expr; simplify = false) empty_substitutions(sys) && return expr substitutions = get_substitutions(sys) - @unpack subs = substitutions - solved = Dict(eq.lhs => eq.rhs for eq in subs) + solved = Dict(eq.lhs => eq.rhs for eq in substitutions) return tearing_sub(expr, solved, simplify) end @@ -121,20 +120,17 @@ These equations matches generated numerical code. See also [`equations`](@ref) and [`ModelingToolkit.get_eqs`](@ref). """ function full_equations(sys::AbstractSystem; simplify = false) - empty_substitutions(sys) && return equations(sys) - substitutions = get_substitutions(sys) - substitutions.subed_eqs === nothing || return substitutions.subed_eqs - @unpack subs = substitutions - solved = Dict(eq.lhs => eq.rhs for eq in subs) + isempty(observed(sys)) && return equations(sys) + subs = Dict([eq.lhs => eq.rhs for eq in observed(sys)]) neweqs = map(equations(sys)) do eq if iscall(eq.lhs) && operation(eq.lhs) isa Union{Shift, Differential} - return tearing_sub(eq.lhs, solved, simplify) ~ tearing_sub(eq.rhs, solved, + return tearing_sub(eq.lhs, subs, simplify) ~ tearing_sub(eq.rhs, subs, simplify) else if !(eq.lhs isa Number && eq.lhs == 0) eq = 0 ~ eq.rhs - eq.lhs end - rhs = tearing_sub(eq.rhs, solved, simplify) + rhs = tearing_sub(eq.rhs, subs, simplify) if rhs isa Symbolic return 0 ~ rhs else # a number @@ -143,7 +139,6 @@ function full_equations(sys::AbstractSystem; simplify = false) end eq end - substitutions.subed_eqs = neweqs return neweqs end From a6bc7748dab22a2786aaa10a754f9a22ce5c9ce7 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Sun, 27 Apr 2025 13:39:43 +0530 Subject: [PATCH 126/185] refactor: do not use `get_substitutions` in `get_cmap` --- src/utils.jl | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/utils.jl b/src/utils.jl index 938539876c..6d8e8c2e66 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -826,9 +826,6 @@ function get_cmap(sys, exprs = nothing) has_op(sys) && push!(buffer, get_op(sys)) has_constraints(sys) && append!(buffer, get_constraints(sys)) cs = collect_constants(buffer) #ctrls? what else? - if !empty_substitutions(sys) - cs = [cs; collect_constants(get_substitutions(sys).subs)] - end if exprs !== nothing cs = [cs; collect_constants(exprs)] end From 2a35cfd4ac2ee68370f3d24ed12449c983fb7546 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Sun, 27 Apr 2025 13:40:44 +0530 Subject: [PATCH 127/185] fix: fix noise equations unit checking --- src/systems/system.jl | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/systems/system.jl b/src/systems/system.jl index 0b6439cb01..cf21915f29 100644 --- a/src/systems/system.jl +++ b/src/systems/system.jl @@ -83,8 +83,11 @@ struct System <: AbstractSystem end if checks == true || (checks & CheckUnits) > 0 u = __get_unit_type(unknowns, ps, iv) - check_units(u, eqs) - noise_eqs !== nothing && check_units(u, noise_eqs) + if noise_eqs === nothing + check_units(u, eqs) + else + check_units(u, eqs, noise_eqs) + end isempty(constraints) || check_units(u, constraints) end new(tag, eqs, noise_eqs, jumps, constraints, costs, From d678ac226c04e486a5eec133fdb2526cfbdb9eca Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Sun, 27 Apr 2025 13:41:11 +0530 Subject: [PATCH 128/185] fix: convert `constraints` to appropriate type --- src/systems/system.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/systems/system.jl b/src/systems/system.jl index cf21915f29..e4386bc997 100644 --- a/src/systems/system.jl +++ b/src/systems/system.jl @@ -234,7 +234,7 @@ function System(eqs::Vector{Equation}, iv; kwargs...) collect_vars!(allunknowns, ps, eq, iv) end - cstrs = get(kwargs, :constraints, Equation[]) + cstrs = Vector{Union{Equation, Inequality}}(get(kwargs, :constraints, [])) cstrunknowns, cstrps = process_constraint_system(cstrs, allunknowns, ps, iv) union!(allunknowns, cstrunknowns) union!(ps, cstrps) @@ -323,7 +323,7 @@ end Process variables in constraints of the (ODE) System. """ function process_constraint_system( - constraints::Vector{Equation}, sts, ps, iv; consname = :cons) + constraints::Vector{Union{Equation, Inequality}}, sts, ps, iv; consname = :cons) isempty(constraints) && return Set(), Set() constraintsts = OrderedSet() From fb4ee04e3df109ed5a550e242ad2e46b0e62fda0 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Sun, 27 Apr 2025 13:41:41 +0530 Subject: [PATCH 129/185] test: fix mass matrix tests --- test/mass_matrix.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/mass_matrix.jl b/test/mass_matrix.jl index 25027c16c0..d2ed720ffe 100644 --- a/test/mass_matrix.jl +++ b/test/mass_matrix.jl @@ -10,7 +10,7 @@ eqs = [D(y[1]) ~ -k[1] * y[1] + k[3] * y[2] * y[3], @named sys = System(eqs, t, collect(y), [k]) sys = complete(sys) -@test_throws ArgumentError System(eqs, y[1]) +@test_throws ModelingToolkit.OperatorIndepvarMismatchError System(eqs, y[1]) M = calculate_massmatrix(sys) @test M == [1 0 0 0 1 0 From 926c46061b0df2cbb5205c3a0b2145b31575f453 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Sun, 27 Apr 2025 18:23:44 +0530 Subject: [PATCH 130/185] test: fix odesystem tests --- test/odesystem.jl | 72 ++++++++++++++++++++++------------------------- 1 file changed, 33 insertions(+), 39 deletions(-) diff --git a/test/odesystem.jl b/test/odesystem.jl index d4c5365c3b..b2b2d519a3 100644 --- a/test/odesystem.jl +++ b/test/odesystem.jl @@ -37,8 +37,6 @@ ssort(eqs) = sort(eqs, by = string) @test eval(toexpr(de)) == de @test hash(deepcopy(de)) == hash(de) -generate_function(de) - function test_diffeq_inference(name, sys, iv, dvs, ps) @testset "System construction: $name" begin @test isequal(independent_variables(sys)[1], value(iv)) @@ -49,13 +47,12 @@ function test_diffeq_inference(name, sys, iv, dvs, ps) end test_diffeq_inference("standard", de, t, [x, y, z], [ρ, σ, β]) -generate_function(de, [x, y, z], [σ, ρ, β]) jac_expr = generate_jacobian(de) jac = calculate_jacobian(de) jacfun = eval(jac_expr[2]) de = complete(de) -f = ODEFunction(de, [x, y, z], [σ, ρ, β], tgrad = true, jac = true) +f = ODEFunction(de, tgrad = true, jac = true) # system @test f.sys === de @@ -86,7 +83,7 @@ f.jac(J, u, p, t) @test J == f.jac(u, p, t) #check iip_config -f = ODEFunction(de, [x, y, z], [σ, ρ, β], iip_config = (false, true)) +f = ODEFunction(de; iip_config = (false, true)) du = zeros(3) u = collect(1:3) p = ModelingToolkit.MTKParameters(de, [σ, ρ, β] .=> 4.0:6.0) @@ -142,7 +139,7 @@ eqs = [D(x) ~ σ(t - 1) * (y - x), D(z) ~ x * y - β * z * κ] @named de = System(eqs, t) test_diffeq_inference("single internal iv-varying", de, t, (x, y, z), (σ, ρ, β)) -f = generate_function(de, [x, y, z], [σ, ρ, β], expression = Val{false})[2] +f = generate_rhs(de, [x, y, z], [σ, ρ, β], expression = Val{false}) du = [0.0, 0.0, 0.0] f(du, [1.0, 2.0, 3.0], [x -> x + 7, 2, 3], 5.0) @test du ≈ [11, -3, -7] @@ -150,37 +147,36 @@ f(du, [1.0, 2.0, 3.0], [x -> x + 7, 2, 3], 5.0) eqs = [D(x) ~ x + 10σ(t - 1) + 100σ(t - 2) + 1000σ(t^2)] @named de = System(eqs, t) test_diffeq_inference("many internal iv-varying", de, t, (x,), (σ,)) -f = generate_function(de, [x], [σ], expression = Val{false})[2] +f = generate_rhs(de, [x], [σ], expression = Val{false}) du = [0.0] f(du, [1.0], [t -> t + 2], 5.0) @test du ≈ [27561] -# Conversion to first-order ODEs #17 -D3 = D^3 -D2 = D^2 -@variables u(t) uˍtt(t) uˍt(t) xˍt(t) -eqs = [D3(u) ~ 2(D2(u)) + D(u) + D(x) + 1 - D2(x) ~ D(x) + 2] -@named de = System(eqs, t) -de1 = ode_order_lowering(de) -lowered_eqs = [D(uˍtt) ~ 2uˍtt + uˍt + xˍt + 1 - D(xˍt) ~ xˍt + 2 - D(uˍt) ~ uˍtt - D(u) ~ uˍt - D(x) ~ xˍt] - -#@test de1 == System(lowered_eqs) - -# issue #219 -@test all(isequal.( - [ModelingToolkit.var_from_nested_derivative(eq.lhs)[1] - for eq in equations(de1)], - unknowns(@named lowered = System(lowered_eqs, t)))) - -test_diffeq_inference("first-order transform", de1, t, [uˍtt, xˍt, uˍt, u, x], []) -du = zeros(5) -ODEFunction(complete(de1), [uˍtt, xˍt, uˍt, u, x], [])(du, ones(5), nothing, 0.1) -@test du == [5.0, 3.0, 1.0, 1.0, 1.0] +@testset "Issue#17: Conversion to first order ODEs" begin + D3 = D^3 + D2 = D^2 + @variables u(t) uˍtt(t) uˍt(t) xˍt(t) + eqs = [D3(u) ~ 2(D2(u)) + D(u) + D(x) + 1 + D2(x) ~ D(x) + 2] + @named de = System(eqs, t) + de1 = ode_order_lowering(de) + + @testset "Issue#219: Ordering of equations in `ode_order_lowering`" begin + lowered_eqs = [D(uˍtt) ~ 2uˍtt + uˍt + xˍt + 1 + D(xˍt) ~ xˍt + 2 + D(uˍt) ~ uˍtt + D(u) ~ uˍt + D(x) ~ xˍt] + @test isequal( + [ModelingToolkit.var_from_nested_derivative(eq.lhs)[1] for eq in equations(de1)], + unknowns(@named lowered = System(lowered_eqs, t))) + end + + test_diffeq_inference("first-order transform", de1, t, [uˍtt, xˍt, uˍt, u, x], []) + du = zeros(5) + ODEFunction(complete(de1))(du, ones(5), nothing, 0.1) + @test du == [5.0, 3.0, 1.0, 1.0, 1.0] +end # Internal calculations @parameters σ @@ -189,12 +185,11 @@ eqs = [D(x) ~ σ * a, D(y) ~ x * (ρ - z) - y, D(z) ~ x * y - β * z * κ] @named de = System(eqs, t) -generate_function(de, [x, y, z], [σ, ρ, β]) jac = calculate_jacobian(de) @test ModelingToolkit.jacobian_sparsity(de).colptr == sparse(jac).colptr @test ModelingToolkit.jacobian_sparsity(de).rowval == sparse(jac).rowval -f = ODEFunction(complete(de), [x, y, z], [σ, ρ, β]) +f = ODEFunction(complete(de)) @parameters A B C _x = y / C @@ -203,11 +198,10 @@ eqs = [D(x) ~ -A * x, @named de = System(eqs, t) @test begin local f, du - f = generate_function(de, [x, y], [A, B, C], expression = Val{false})[2] + f = generate_rhs(de, [x, y], [A, B, C], expression = Val{false}) du = [0.0, 0.0] f(du, [1.0, 2.0], [1, 2, 3], 0.0) du ≈ [-1, -1 / 3] - f = generate_function(de, [x, y], [A, B, C], expression = Val{false})[1] du ≈ f([1.0, 2.0], [1, 2, 3], 0.0) end @@ -1286,11 +1280,11 @@ end [D(u) ~ (sum(u) + sum(x) + sum(p) + sum(o)) * x, o ~ prod(u) * x], t, [u..., x..., o...], [p...]) sys1, = structural_simplify(sys, ([x...], [])) - fn1, = ModelingToolkit.generate_function(sys1; expression = Val{false}) + fn1, = ModelingToolkit.generate_rhs(sys1; expression = Val{false}) ps = MTKParameters(sys1, [x => 2ones(2), p => 3ones(2, 2)]) @test_nowarn fn1(ones(4), ps, 4.0) sys2, = structural_simplify(sys, ([x...], []); split = false) - fn2, = ModelingToolkit.generate_function(sys2; expression = Val{false}) + fn2, = ModelingToolkit.generate_rhs(sys2; expression = Val{false}) ps = zeros(8) setp(sys2, x)(ps, 2ones(2)) setp(sys2, p)(ps, 2ones(2, 2)) From a9d698511fadd9452146730ffd37dceb5c46197b Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Sun, 27 Apr 2025 13:42:35 +0530 Subject: [PATCH 131/185] fix: fix bugs in `@fallback_iip_specialize`, handle static array problems --- src/systems/problem_utils.jl | 54 ++++++++++++++++++++++++++++++++++-- 1 file changed, 51 insertions(+), 3 deletions(-) diff --git a/src/systems/problem_utils.jl b/src/systems/problem_utils.jl index ba84decd3e..092c6b717b 100644 --- a/src/systems/problem_utils.jl +++ b/src/systems/problem_utils.jl @@ -1304,35 +1304,83 @@ parameters for `iip` and `specialize`. Generates fallbacks with """ macro fallback_iip_specialize(ex) @assert Meta.isexpr(ex, :function) + # fnname is ODEProblem{iip, spec}(args...) where {iip, spec} + # body is function body fnname, body = ex.args @assert Meta.isexpr(fnname, :where) + # fnname_call is ODEProblem{iip, spec}(args...) + # where_args are `iip, spec` fnname_call, where_args... = fnname.args @assert length(where_args) == 2 iiparg, specarg = where_args @assert Meta.isexpr(fnname_call, :call) + # fnname_curly is ODEProblem{iip, spec} fnname_curly, args... = fnname_call.args - args = map(args) do arg + # the function should have keyword arguments + @assert Meta.isexpr(args[1], :parameters) + + # arguments to call with + call_args = map(args) do arg + # keyword args are in `Expr(:parameters)` so any `Expr(:kw)` here + # are optional positional arguments. Analyze `:(f(a, b = 1; k = 1, l...))` + # to understand Meta.isexpr(arg, :kw) && return arg.args[1] return arg end + call_kwargs = map(call_args[1].args) do arg + Meta.isexpr(arg, :...) && return arg + @assert Meta.isexpr(arg, :kw) + return Expr(:kw, arg.args[1], arg.args[1]) + end + call_args[1] = Expr(:parameters, call_kwargs...) @assert Meta.isexpr(fnname_curly, :curly) + # fnname_name is `ODEProblem` + # curly_args is `iip, spec` fnname_name, curly_args... = fnname_curly.args @assert curly_args == where_args + # callexpr_iip is `ODEProblem{iip, FullSpecialize}(call_args...)` callexpr_iip = Expr( - :call, Expr(:curly, fnname_name, curly_args[1], SciMLBase.FullSpecialize), args...) + :call, Expr(:curly, fnname_name, curly_args[1], SciMLBase.FullSpecialize), call_args...) + # `ODEProblem{iip}` fnname_iip = Expr(:curly, fnname_name, curly_args[1]) + # `ODEProblem{iip}(args...)` fncall_iip = Expr(:call, fnname_iip, args...) + # ODEProblem{iip}(args...) where {iip} fnwhere_iip = Expr(:where, fncall_iip, where_args[1]) fn_iip = Expr(:function, fnwhere_iip, callexpr_iip) - callexpr_base = Expr(:call, Expr(:curly, fnname_name, true), args...) + # `ODEProblem{true}(call_args...)` + callexpr_base = Expr(:call, Expr(:curly, fnname_name, true), call_args...) + # `ODEProblem(args...)` fncall_base = Expr(:call, fnname_name, args...) fn_base = Expr(:function, fncall_base, callexpr_base) + + # Handle case when this is a problem constructor and `u0map` is a `StaticArray`, + # where `iip` should default to `false`. + fn_sarr = nothing + if endswith(string(fnname_name), "Problem") + # args should at least contain an argument for the `u0map` + @assert length(args) > 3 + u0_arg = args[3] + # should not have a type-annotation + @assert !Meta.isexpr(u0_arg, :(::)) + if Meta.isexpr(u0_arg, :kw) + argname, default = u0_arg.args + u0_arg = Expr(:kw, Expr(:(::), argname, StaticArray), default) + else + u0_arg = Expr(:(::), u0_arg, StaticArray) + end + + callexpr_sarr = Expr(:call, Expr(:curly, fnname_name, false), call_args...) + fncall_sarr = Expr(:call, fnname_name, args[1], args[2], u0_arg, args[4:end]...) + fn_sarr = Expr(:function, fncall_sarr, callexpr_sarr) + end return quote $fn_base + $fn_sarr $fn_iip Base.@__doc__ $ex end |> esc From 69e6dc362a5219c9480c6f1837cc475d3e6d2ed7 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Sun, 27 Apr 2025 13:44:47 +0530 Subject: [PATCH 132/185] refactor: rename `generate_function` to `generate_rhs` --- src/ModelingToolkit.jl | 2 +- src/systems/abstractsystem.jl | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ModelingToolkit.jl b/src/ModelingToolkit.jl index e644441a29..8d96da7601 100644 --- a/src/ModelingToolkit.jl +++ b/src/ModelingToolkit.jl @@ -305,7 +305,7 @@ export structural_simplify, expand_connections, linearize, linearization_functio LinearizationProblem export solve -export calculate_jacobian, generate_jacobian, generate_function, generate_custom_function, +export calculate_jacobian, generate_jacobian, generate_rhs, generate_custom_function, generate_W export calculate_control_jacobian, generate_control_jacobian export calculate_tgrad, generate_tgrad diff --git a/src/systems/abstractsystem.jl b/src/systems/abstractsystem.jl index d9b438aba9..5dda624c0b 100644 --- a/src/systems/abstractsystem.jl +++ b/src/systems/abstractsystem.jl @@ -132,7 +132,7 @@ generate_function(sys::AbstractSystem, dvs = unknowns(sys), ps = parameters(sys) Generate a function to evaluate the system's equations. """ -function generate_function end +function generate_rhs end """ ```julia From d599ff22840288f34ec59b573e625a19b39007e4 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Sun, 27 Apr 2025 23:01:16 +0530 Subject: [PATCH 133/185] refactor: move `eval_or_rgf` to `codegen_utils.jl` --- src/systems/codegen_utils.jl | 21 +++++++++++++++++++++ src/utils.jl | 8 -------- 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/src/systems/codegen_utils.jl b/src/systems/codegen_utils.jl index 8fba085ec4..8f9e3e5615 100644 --- a/src/systems/codegen_utils.jl +++ b/src/systems/codegen_utils.jl @@ -1,3 +1,24 @@ +""" + $(TYPEDSIGNATURES) + +Given a function expression `expr`, return a callable version of it. + +# Keyword arguments +- `eval_expression`: Whether to use `eval` to make `expr` callable. If `false`, uses + RuntimeGeneratedFunctions.jl. +- `eval_module`: The module to `eval` the expression `expr` in. If `!eval_expression`, + this is the cache and context module for the `RuntimeGeneratedFunction`. +""" +function eval_or_rgf(expr::Expr; eval_expression = false, eval_module = @__MODULE__) + if eval_expression + return eval_module.eval(expr) + else + return drop_expr(RuntimeGeneratedFunction(eval_module, eval_module, expr)) + end +end + +eval_or_rgf(::Nothing; kws...) = nothing + """ $(TYPEDSIGNATURES) diff --git a/src/utils.jl b/src/utils.jl index 6d8e8c2e66..42fd296752 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -1048,14 +1048,6 @@ function restrict_array_to_union(arr) return Array{T, ndims(arr)}(arr) end -function eval_or_rgf(expr::Expr; eval_expression = false, eval_module = @__MODULE__) - if eval_expression - return eval_module.eval(expr) - else - return drop_expr(RuntimeGeneratedFunction(eval_module, eval_module, expr)) - end -end - function _with_unit(f, x, t, args...) x = f(x, args...) if hasmetadata(x, VariableUnit) && (t isa Symbolic && hasmetadata(t, VariableUnit)) From 251d7dd0775eb8f93884f14469e845ff2bf3caba Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Sun, 27 Apr 2025 23:01:50 +0530 Subject: [PATCH 134/185] feat: allow `GeneratedFunctionWrapper` to compile functions and build expressions --- src/systems/codegen_utils.jl | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/systems/codegen_utils.jl b/src/systems/codegen_utils.jl index 8f9e3e5615..095a09220e 100644 --- a/src/systems/codegen_utils.jl +++ b/src/systems/codegen_utils.jl @@ -333,6 +333,14 @@ function GeneratedFunctionWrapper{P}(foop::O, fiip::I) where {P, O, I} GeneratedFunctionWrapper{P, O, I}(foop, fiip) end +function GeneratedFunctionWrapper{P}(::Type{Val{true}}, foop, fiip; kwargs...) where {P} + :($(GeneratedFunctionWrapper{P})($foop, $fiip)) +end + +function GeneratedFunctionWrapper{P}(::Type{Val{false}}, foop, fiip; kws...) where {P} + GeneratedFunctionWrapper{P}(eval_or_rgf(foop; kws...), eval_or_rgf(fiip; kws...)) +end + function (gfw::GeneratedFunctionWrapper)(args...) _generated_call(gfw, args...) end From 7a01657c282ab5bfd92f69eb5096a051834ad737 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Mon, 28 Apr 2025 12:15:19 +0530 Subject: [PATCH 135/185] feat: add `maybe_compile_function` --- src/systems/codegen_utils.jl | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/src/systems/codegen_utils.jl b/src/systems/codegen_utils.jl index 095a09220e..e7ba2659d7 100644 --- a/src/systems/codegen_utils.jl +++ b/src/systems/codegen_utils.jl @@ -378,3 +378,37 @@ end return :($f($(fargs...))) end end + +""" + $(TYPEDSIGNATURES) + +Optionally compile a method and optionally wrap it in a `GeneratedFunctionWrapper` on the +basis of `expression` `wrap_gfw`, both of type `Union{Type{Val{true}}, Type{Val{false}}}`. +`gfw_args` is the first type parameter of `GeneratedFunctionWrapper`. `f` is a tuple of +function expressions of the form `(oop, iip)` or a single out-of-place function expression. +Keyword arguments are forwarded to `eval_or_rgf`. +""" +function maybe_compile_function(expression, wrap_gfw::Type{Val{true}}, + gfw_args::Tuple{Int, Int, Bool}, f::NTuple{2, Expr}; kwargs...) + GeneratedFunctionWrapper{gfw_args}(expression, f...; kwargs...) +end + +function maybe_compile_function(expression::Type{Val{false}}, wrap_gfw::Type{Val{false}}, + gfw_args::Tuple{Int, Int, Bool}, f::NTuple{2, Expr}; kwargs...) + eval_or_rgf.(f; kwargs...) +end + +function maybe_compile_function(expression::Type{Val{true}}, wrap_gfw::Type{Val{false}}, + gfw_args::Tuple{Int, Int, Bool}, f::Union{Expr, NTuple{2, Expr}}; kwargs...) + return f +end + +function maybe_compile_function(expression, wrap_gfw::Type{Val{true}}, + gfw_args::Tuple{Int, Int, Bool}, f::Expr; kwargs...) + GeneratedFunctionWrapper{gfw_args}(expression, f, nothing; kwargs...) +end + +function maybe_compile_function(expression::Type{Val{false}}, wrap_gfw::Type{Val{false}}, + gfw_args::Tuple{Int, Int, Bool}, f::Expr; kwargs...) + eval_or_rgf(f; kwargs...) +end From 3a261326291150d0fdc25424ffe584cb7a0ee267 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Mon, 28 Apr 2025 12:15:31 +0530 Subject: [PATCH 136/185] refactor: use `maybe_compile_function` in codegen --- src/problems/bvproblem.jl | 3 +- src/problems/daeproblem.jl | 7 +- src/problems/ddeproblem.jl | 2 +- src/problems/discreteproblem.jl | 2 +- src/problems/implicitdiscreteproblem.jl | 4 +- src/problems/intervalnonlinearproblem.jl | 5 +- src/problems/nonlinearproblem.jl | 5 +- src/problems/odeproblem.jl | 8 +- src/problems/optimizationproblem.jl | 25 ++-- src/problems/sddeproblem.jl | 4 +- src/problems/sdeproblem.jl | 10 +- src/systems/codegen.jl | 149 ++++++++--------------- 12 files changed, 96 insertions(+), 128 deletions(-) diff --git a/src/problems/bvproblem.jl b/src/problems/bvproblem.jl index 5a78101b23..df947f63d8 100644 --- a/src/problems/bvproblem.jl +++ b/src/problems/bvproblem.jl @@ -64,7 +64,8 @@ If the `System` has algebraic equations, like `x(t)^2 + y(t)^2`, the resulting stidxmap = Dict([v => i for (i, v) in enumerate(dvs)]) u0_idxs = has_alg_eqs(sys) ? collect(1:length(dvs)) : [stidxmap[k] for (k, v) in u0map] fbc = generate_boundary_conditions( - sys, u0, u0_idxs, tspan; expression = Val{false}, cse, checkbounds) + sys, u0, u0_idxs, tspan; expression = Val{false}, + wrap_gfw = Val{true}, cse, checkbounds) if (length(constraints(sys)) + length(u0map) > length(dvs)) @warn "The BVProblem is overdetermined. The total number of conditions (# constraints + # fixed initial values given by u0map) exceeds the total number of states. The BVP solvers will default to doing a nonlinear least-squares optimization." diff --git a/src/problems/daeproblem.jl b/src/problems/daeproblem.jl index 743fab51b4..de6c4cd0a8 100644 --- a/src/problems/daeproblem.jl +++ b/src/problems/daeproblem.jl @@ -9,8 +9,8 @@ dvs = unknowns(sys) ps = parameters(sys) - f = generate_rhs(sys, dvs, ps; expression = Val{false}, implicit_dae = true, - eval_expression, eval_module, checkbounds = checkbounds, cse, + f = generate_rhs(sys, dvs, ps; expression = Val{false}, wrap_gfw = Val{true}, + implicit_dae = true, eval_expression, eval_module, checkbounds = checkbounds, cse, kwargs...) if spec === SciMLBase.FunctionWrapperSpecialize && iip @@ -22,7 +22,8 @@ if jac _jac = generate_dae_jacobian(sys, dvs, ps; expression = Val{false}, - simplify, sparse, cse, eval_expression, eval_module, checkbounds, kwargs...) + wrap_gfw = Val{true}, simplify, sparse, cse, eval_expression, eval_module, + checkbounds, kwargs...) else _jac = nothing end diff --git a/src/problems/ddeproblem.jl b/src/problems/ddeproblem.jl index 9693dd4f1f..be8d35265e 100644 --- a/src/problems/ddeproblem.jl +++ b/src/problems/ddeproblem.jl @@ -10,7 +10,7 @@ dvs = unknowns(sys) ps = parameters(sys) - f = generate_rhs(sys, dvs, ps; expression = Val{false}, + f = generate_rhs(sys, dvs, ps; expression = Val{false}, wrap_gfw = Val{true}, eval_expression, eval_module, checkbounds = checkbounds, cse, kwargs...) diff --git a/src/problems/discreteproblem.jl b/src/problems/discreteproblem.jl index 26c463b837..04b54828db 100644 --- a/src/problems/discreteproblem.jl +++ b/src/problems/discreteproblem.jl @@ -9,7 +9,7 @@ dvs = unknowns(sys) ps = parameters(sys) - f = generate_rhs(sys, dvs, ps; expression = Val{false}, + f = generate_rhs(sys, dvs, ps; expression = Val{false}, wrap_gfw = Val{true}, eval_expression, eval_module, checkbounds = checkbounds, cse, kwargs...) diff --git a/src/problems/implicitdiscreteproblem.jl b/src/problems/implicitdiscreteproblem.jl index e63738a257..730fdbf016 100644 --- a/src/problems/implicitdiscreteproblem.jl +++ b/src/problems/implicitdiscreteproblem.jl @@ -10,8 +10,8 @@ iv = get_iv(sys) dvs = unknowns(sys) ps = parameters(sys) - f = generate_rhs(sys, dvs, ps; expression = Val{false}, implicit_dae = true, - eval_expression, eval_module, checkbounds = checkbounds, cse, + f = generate_rhs(sys, dvs, ps; expression = Val{false}, wrap_gfw = Val{true}, + implicit_dae = true, eval_expression, eval_module, checkbounds = checkbounds, cse, kwargs...) if spec === SciMLBase.FunctionWrapperSpecialize && iip diff --git a/src/problems/intervalnonlinearproblem.jl b/src/problems/intervalnonlinearproblem.jl index 02d18ef7d3..fbea658d6c 100644 --- a/src/problems/intervalnonlinearproblem.jl +++ b/src/problems/intervalnonlinearproblem.jl @@ -9,9 +9,8 @@ function SciMLBase.IntervalNonlinearFunction( dvs = unknowns(sys) ps = parameters(sys) - f = generate_rhs(sys, dvs, ps; expression = Val{false}, scalar = true, - eval_expression, eval_module, checkbounds, cse, - kwargs...) + f = generate_rhs(sys, dvs, ps; expression = Val{false}, wrap_gfw = Val{true}, + scalar = true, eval_expression, eval_module, checkbounds, cse, kwargs...) observedfun = ObservedFunctionCache( sys; steady_state = false, eval_expression, eval_module, checkbounds, cse) diff --git a/src/problems/nonlinearproblem.jl b/src/problems/nonlinearproblem.jl index 768210b624..4a04bcfd37 100644 --- a/src/problems/nonlinearproblem.jl +++ b/src/problems/nonlinearproblem.jl @@ -9,7 +9,7 @@ dvs = unknowns(sys) ps = parameters(sys) - f = generate_rhs(sys, dvs, ps; expression = Val{false}, + f = generate_rhs(sys, dvs, ps; expression = Val{false}, wrap_gfw = Val{true}, eval_expression, eval_module, checkbounds = checkbounds, cse, kwargs...) @@ -22,7 +22,8 @@ if jac _jac = generate_jacobian(sys, dvs, ps; expression = Val{false}, - simplify, sparse, cse, eval_expression, eval_module, checkbounds, kwargs...) + wrap_gfw = Val{true}, simplify, sparse, cse, eval_expression, eval_module, + checkbounds, kwargs...) else _jac = nothing end diff --git a/src/problems/odeproblem.jl b/src/problems/odeproblem.jl index da79eb86fc..9e9a4d31d3 100644 --- a/src/problems/odeproblem.jl +++ b/src/problems/odeproblem.jl @@ -9,7 +9,7 @@ dvs = unknowns(sys) ps = parameters(sys) - f = generate_rhs(sys, dvs, ps; expression = Val{false}, + f = generate_rhs(sys, dvs, ps; expression = Val{false}, wrap_gfw = Val{true}, eval_expression, eval_module, checkbounds = checkbounds, cse, kwargs...) @@ -21,14 +21,16 @@ end if tgrad - _tgrad = generate_tgrad(sys, dvs, ps; expression = Val{false}, + _tgrad = generate_tgrad( + sys, dvs, ps; expression = Val{false}, wrap_gfw = Val{true}, simplify, cse, eval_expression, eval_module, checkbounds, kwargs...) else _tgrad = nothing end if jac - _jac = generate_jacobian(sys, dvs, ps; expression = Val{false}, + _jac = generate_jacobian( + sys, dvs, ps; expression = Val{false}, wrap_gfw = Val{true}, simplify, sparse, cse, eval_expression, eval_module, checkbounds, kwargs...) else _jac = nothing diff --git a/src/problems/optimizationproblem.jl b/src/problems/optimizationproblem.jl index 55a256a712..241841e229 100644 --- a/src/problems/optimizationproblem.jl +++ b/src/problems/optimizationproblem.jl @@ -14,19 +14,20 @@ function SciMLBase.OptimizationFunction{iip}(sys::System; ps = parameters(sys) cstr = constraints(sys) - f = generate_cost(sys; expression = Val{false}, eval_expression, + f = generate_cost(sys; expression = Val{false}, wrap_gfw = Val{true}, eval_expression, eval_module, checkbounds, cse, kwargs...) if grad - _grad = generate_cost_gradient(sys; expression = Val{false}, eval_expression, - eval_module, checkbounds, cse, kwargs...) + _grad = generate_cost_gradient(sys; expression = Val{false}, wrap_gfw = Val{true}, + eval_expression, eval_module, checkbounds, cse, kwargs...) else _grad = nothing end if hess _hess, hess_prototype = generate_cost_hessian( - sys; expression = Val{false}, eval_expression, eval_module, - checkbounds, cse, sparse, simplify, return_sparsity = true, kwargs...) + sys; expression = Val{false}, wrap_gfw = Val{true}, eval_expression, + eval_module, checkbounds, cse, sparse, simplify, return_sparsity = true, + kwargs...) else _hess = hess_prototype = nothing end @@ -34,19 +35,21 @@ function SciMLBase.OptimizationFunction{iip}(sys::System; cons = lcons = ucons = _cons_j = cons_jac_prototype = _cons_h = nothing cons_hess_prototype = cons_expr = nothing else - cons = generate_cons(sys; expression = Val{false}, eval_expression, - eval_module, checkbounds, cse, kwargs...) + cons = generate_cons(sys; expression = Val{false}, wrap_gfw = Val{true}, + eval_expression, eval_module, checkbounds, cse, kwargs...) if cons_j _cons_j, cons_jac_prototype = generate_constraint_jacobian( - sys; expression = Val{false}, eval_expression, eval_module, checkbounds, - cse, simplify, sparse = cons_sparse, return_sparsity = true, kwargs...) + sys; expression = Val{false}, wrap_gfw = Val{true}, eval_expression, + eval_module, checkbounds, cse, simplify, sparse = cons_sparse, + return_sparsity = true, kwargs...) else _cons_j = cons_jac_prototype = nothing end if cons_h _cons_h, cons_hess_prototype = generate_constraint_hessian( - sys; expression = Val{false}, eval_expression, eval_module, checkbounds, - cse, simplify, sparse = cons_sparse, return_sparsity = true, kwargs...) + sys; expression = Val{false}, wrap_gfw = Val{true}, eval_expression, + eval_module, checkbounds, cse, simplify, sparse = cons_sparse, + return_sparsity = true, kwargs...) else _cons_h = cons_hess_prototype = nothing end diff --git a/src/problems/sddeproblem.jl b/src/problems/sddeproblem.jl index a95c1e341f..690741f60b 100644 --- a/src/problems/sddeproblem.jl +++ b/src/problems/sddeproblem.jl @@ -10,11 +10,11 @@ dvs = unknowns(sys) ps = parameters(sys) - f = generate_rhs(sys, dvs, ps; expression = Val{false}, + f = generate_rhs(sys, dvs, ps; expression = Val{false}, wrap_gfw = Val{true}, eval_expression, eval_module, checkbounds = checkbounds, cse, kwargs...) g = generate_diffusion_function(sys, dvs, ps; expression = Val{false}, - eval_expression, eval_module, checkbounds, cse, kwargs...) + wrap_gfw = Val{true}, eval_expression, eval_module, checkbounds, cse, kwargs...) if spec === SciMLBase.FunctionWrapperSpecialize && iip if u0 === nothing || p === nothing || t === nothing diff --git a/src/problems/sdeproblem.jl b/src/problems/sdeproblem.jl index 84d1f0b0ee..2f582982c3 100644 --- a/src/problems/sdeproblem.jl +++ b/src/problems/sdeproblem.jl @@ -9,11 +9,11 @@ dvs = unknowns(sys) ps = parameters(sys) - f = generate_rhs(sys, dvs, ps; expression = Val{false}, + f = generate_rhs(sys, dvs, ps; expression = Val{false}, wrap_gfw = Val{true}, eval_expression, eval_module, checkbounds = checkbounds, cse, kwargs...) g = generate_diffusion_function(sys, dvs, ps; expression = Val{false}, - eval_expression, eval_module, checkbounds, cse, kwargs...) + wrap_gfw = Val{true}, eval_expression, eval_module, checkbounds, cse, kwargs...) if spec === SciMLBase.FunctionWrapperSpecialize && iip if u0 === nothing || p === nothing || t === nothing @@ -24,14 +24,16 @@ if tgrad _tgrad = generate_tgrad(sys, dvs, ps; expression = Val{false}, - simplify, cse, eval_expression, eval_module, checkbounds, kwargs...) + wrap_gfw = Val{true}, simplify, cse, eval_expression, eval_module, checkbounds, + kwargs...) else _tgrad = nothing end if jac _jac = generate_jacobian(sys, dvs, ps; expression = Val{false}, - simplify, sparse, cse, eval_expression, eval_module, checkbounds, kwargs...) + wrap_gfw = Val{true}, simplify, sparse, cse, eval_expression, eval_module, + checkbounds, kwargs...) else _jac = nothing end diff --git a/src/systems/codegen.jl b/src/systems/codegen.jl index 22e0fd6501..c169a098d5 100644 --- a/src/systems/codegen.jl +++ b/src/systems/codegen.jl @@ -10,8 +10,8 @@ Generate the RHS function for the `equations` of a `System`. """ function generate_rhs(sys::System, dvs = unknowns(sys), ps = parameters(sys; initial_parameters = true); implicit_dae = false, - scalar = false, expression = Val{true}, eval_expression = false, - eval_module = @__MODULE__, kwargs...) + scalar = false, expression = Val{true}, wrap_gfw = Val{false}, + eval_expression = false, eval_module = @__MODULE__, kwargs...) eqs = equations(sys) obs = observed(sys) u = dvs @@ -70,22 +70,14 @@ function generate_rhs(sys::System, dvs = unknowns(sys), res = build_function_wrapper(sys, rhss, args...; p_start, extra_assignments, expression = Val{true}, expression_module = eval_module, kwargs...) - if expression == Val{true} - return res - end - - if res isa Tuple - f_oop, f_iip = eval_or_rgf.(res; eval_expression, eval_module) - else - f_oop = eval_or_rgf(res; eval_expression, eval_module) - f_iip = nothing - end - return GeneratedFunctionWrapper{(p_start, length(args) - length(p) + 1, is_split(sys))}( - f_oop, f_iip) + return maybe_compile_function( + expression, wrap_gfw, (p_start, length(args) - length(p) + 1, is_split(sys)), + res; eval_expression, eval_module) end function generate_diffusion_function(sys::System, dvs = unknowns(sys), - ps = parameters(sys; initial_parameters = true); expression = Val{true}, eval_expression = false, + ps = parameters(sys; initial_parameters = true); expression = Val{true}, + wrap_gfw = Val{false}, eval_expression = false, eval_module = @__MODULE__, kwargs...) eqs = get_noise_eqs(sys) p = reorder_parameters(sys, ps) @@ -94,7 +86,8 @@ function generate_diffusion_function(sys::System, dvs = unknowns(sys), return res end f_oop, f_iip = eval_or_rgf.(res; eval_expression, eval_module) - return GeneratedFunctionWrapper{(2, 3, is_split(sys))}(f_oop, f_iip) + return maybe_compile_function( + expression, wrap_gfw, (2, 3, is_split(sys)), res; eval_expression, eval_module) end function calculate_tgrad(sys::System; simplify = false) @@ -141,7 +134,8 @@ end function generate_jacobian(sys::System, dvs = unknowns(sys), ps = parameters(sys; initial_parameters = true); simplify = false, sparse = false, eval_expression = false, - eval_module = @__MODULE__, expression = Val{true}, kwargs...) + eval_module = @__MODULE__, expression = Val{true}, wrap_gfw = Val{false}, + kwargs...) jac = calculate_jacobian(sys; simplify, sparse, dvs) p = reorder_parameters(sys, ps) t = get_iv(sys) @@ -152,11 +146,8 @@ function generate_jacobian(sys::System, dvs = unknowns(sys), end res = build_function_wrapper(sys, jac, dvs, p..., t; wrap_code, expression = Val{true}, expression_module = eval_module, kwargs...) - if expression == Val{true} - return res - end - f_oop, f_iip = eval_or_rgf.(res; eval_expression, eval_module) - return GeneratedFunctionWrapper{(2, 3, is_split(sys))}(f_oop, f_iip) + return maybe_compile_function( + expression, wrap_gfw, (2, 3, is_split(sys)), res; eval_expression, eval_module) end function assert_jac_length_header(sys) @@ -173,7 +164,7 @@ function generate_tgrad( sys::System, dvs = unknowns(sys), ps = parameters( sys; initial_parameters = true); simplify = false, eval_expression = false, eval_module = @__MODULE__, - expression = Val{true}, kwargs...) + expression = Val{true}, wrap_gfw = Val{false}, kwargs...) tgrad = calculate_tgrad(sys, simplify = simplify) p = reorder_parameters(sys, ps) res = build_function_wrapper(sys, tgrad, @@ -184,18 +175,15 @@ function generate_tgrad( expression_module = eval_module, kwargs...) - if expression == Val{true} - return res - end - f_oop, f_iip = eval_or_rgf.(res; eval_expression, eval_module) - return GeneratedFunctionWrapper{(2, 3, is_split(sys))}(f_oop, f_iip) + return maybe_compile_function( + expression, wrap_gfw, (2, 3, is_split(sys)), res; eval_expression, eval_module) end const W_GAMMA = only(@variables ˍ₋gamma) function generate_W(sys::System, γ = 1.0, dvs = unknowns(sys), ps = parameters(sys; initial_parameters = true); - simplify = false, sparse = false, expression = Val{true}, + simplify = false, sparse = false, expression = Val{true}, wrap_gfw = Val{false}, eval_expression = false, eval_module = @__MODULE__, kwargs...) M = calculate_massmatrix(sys; simplify) if sparse @@ -211,17 +199,14 @@ function generate_W(sys::System, γ = 1.0, dvs = unknowns(sys), p = reorder_parameters(sys, ps) res = build_function_wrapper(sys, W, dvs, p..., W_GAMMA, t; wrap_code, p_end = 1 + length(p), kwargs...) - if expression == Val{true} - return res - end - f_oop, f_iip = eval_or_rgf.(res; eval_expression, eval_module) - return GeneratedFunctionWrapper{(2, 4, is_split(sys))}(f_oop, f_iip) + return maybe_compile_function( + expression, wrap_gfw, (2, 4, is_split(sys)), res; eval_expression, eval_module) end function generate_dae_jacobian(sys::System, dvs = unknowns(sys), ps = parameters(sys; initial_parameters = true); simplify = false, sparse = false, - expression = Val{true}, eval_expression = false, eval_module = @__MODULE__, - kwargs...) + expression = Val{true}, wrap_gfw = Val{false}, eval_expression = false, + eval_module = @__MODULE__, kwargs...) jac_u = calculate_jacobian(sys; simplify = simplify, sparse = sparse) t = get_iv(sys) derivatives = Differential(t).(unknowns(sys)) @@ -232,25 +217,18 @@ function generate_dae_jacobian(sys::System, dvs = unknowns(sys), p = reorder_parameters(sys, ps) res = build_function_wrapper(sys, jac, derivatives, dvs, p..., W_GAMMA, t; p_start = 3, p_end = 2 + length(p), kwargs...) - if expression == Val{true} - return res - end - f_oop, f_iip = eval_or_rgf.(res; eval_expression, eval_module) - return GeneratedFunctionWrapper{(3, 5, is_split(sys))}(f_oop, f_iip) + return maybe_compile_function( + expression, wrap_gfw, (3, 5, is_split(sys)), res; eval_expression, eval_module) end -function generate_history(sys::System, u0; expression = Val{true}, +function generate_history(sys::System, u0; expression = Val{true}, wrap_gfw = Val{false}, eval_expression = false, eval_module = @__MODULE__, kwargs...) p = reorder_parameters(sys) res = build_function_wrapper(sys, u0, p..., get_iv(sys); expression = Val{true}, expression_module = eval_module, p_start = 1, p_end = length(p), similarto = typeof(u0), wrap_delays = false, kwargs...) - - if expression == Val{true} - return res - end - f_oop, f_iip = eval_or_rgf.(res; eval_expression, eval_module) - return GeneratedFunctionWrapper{(1, 2, is_split(sys))}(f_oop, f_iip) + return maybe_compile_function( + expression, wrap_gfw, (1, 2, is_split(sys)), res; eval_expression, eval_module) end function calculate_massmatrix(sys::System; simplify = false) @@ -336,7 +314,8 @@ function get_constraint_unknown_subs!(subs::Dict, cons::Vector, stidxmap::Dict, end function generate_boundary_conditions(sys::System, u0, u0_idxs, t0; expression = Val{true}, - eval_expression = false, eval_module = @__MODULE__, kwargs...) + wrap_gfw = Val{false}, eval_expression = false, eval_module = @__MODULE__, + kwargs...) iv = get_iv(sys) sts = unknowns(sys) ps = parameters(sys) @@ -362,16 +341,12 @@ function generate_boundary_conditions(sys::System, u0, u0_idxs, t0; expression = _p = reorder_parameters(sys, ps) res = build_function_wrapper(sys, exprs, sol, _p..., iv; output_type = Array, kwargs...) - if expression == Val{true} - return res - end - - f_oop, f_iip = eval_or_rgf.(res; eval_expression, eval_module) - return GeneratedFunctionWrapper{(2, 3, is_split(sys))}(f_oop, f_iip) + return maybe_compile_function( + expression, wrap_gfw, (2, 3, is_split(sys)), res; eval_expression, eval_module) end -function generate_cost(sys::System; expression = Val{true}, eval_expression = false, - eval_module = @__MODULE__, kwargs...) +function generate_cost(sys::System; expression = Val{true}, wrap_gfw = Val{false}, + eval_expression = false, eval_module = @__MODULE__, kwargs...) obj = cost(sys) dvs = unknowns(sys) ps = reorder_parameters(sys) @@ -380,27 +355,25 @@ function generate_cost(sys::System; expression = Val{true}, eval_expression = fa return res end f_oop = eval_or_rgf(res; eval_expression, eval_module) - return GeneratedFunctionWrapper{(2, 2, is_split(sys))}(f_oop, nothing) + return maybe_compile_function( + expression, wrap_gfw, (2, 2, is_split(sys)), res; eval_expression, eval_module) end function generate_cost_gradient( - sys::System; expression = Val{true}, eval_expression = false, - eval_module = @__MODULE__, simplify = false, kwargs...) + sys::System; expression = Val{true}, wrap_gfw = Val{false}, + eval_expression = false, eval_module = @__MODULE__, simplify = false, kwargs...) obj = cost(sys) dvs = unknowns(sys) ps = reorder_parameters(sys) exprs = Symbolics.gradient(obj, dvs; simplify) res = build_function_wrapper(sys, exprs, dvs, ps...; expression = Val{true}, kwargs...) - if expression == Val{true} - return res - end - f_oop, f_iip = eval_or_rgf.(res; eval_expression, eval_module) - return GeneratedFunctionWrapper{(2, 2, is_split(sys))}(f_oop, f_iip) + return maybe_compile_function( + expression, wrap_gfw, (2, 2, is_split(sys)), res; eval_expression, eval_module) end function generate_cost_hessian( - sys::System; expression = Val{true}, eval_expression = false, - eval_module = @__MODULE__, simplify = false, + sys::System; expression = Val{true}, wrap_gfw = Val{false}, + eval_expression = false, eval_module = @__MODULE__, simplify = false, sparse = false, return_sparsity = false, kwargs...) obj = cost(sys) dvs = unknowns(sys) @@ -413,12 +386,8 @@ function generate_cost_hessian( exprs = Symbolics.hessian(obj, dvs; simplify) end res = build_function_wrapper(sys, exprs, dvs, ps...; expression = Val{true}, kwargs...) - if expression == Val{true} - return return_sparsity ? (res, sparsity) : res - end - f_oop, f_iip = eval_or_rgf.(res; eval_expression, eval_module) - fn = GeneratedFunctionWrapper{(2, 2, is_split(sys))}(f_oop, f_iip) - return return_sparsity ? (fn, sparsity) : fn + return maybe_compile_function( + expression, wrap_gfw, (2, 2, is_split(sys)), res; eval_expression, eval_module) end function canonical_constraints(sys::System) @@ -427,23 +396,19 @@ function canonical_constraints(sys::System) end end -function generate_cons(sys::System; expression = Val{true}, eval_expression = false, - eval_module = @__MODULE__, kwargs...) +function generate_cons(sys::System; expression = Val{true}, wrap_gfw = Val{false}, + eval_expression = false, eval_module = @__MODULE__, kwargs...) cons = canonical_constraints(sys) dvs = unknowns(sys) ps = reorder_parameters(sys) res = build_function_wrapper(sys, cons, dvs, ps...; expression = Val{true}, kwargs...) - if expression == Val{true} - return res - end - f_oop, f_iip = eval_or_rgf.(res; eval_expression, eval_module) - fn = GeneratedFunctionWrapper{(2, 2, is_split(sys))}(f_oop, f_iip) - return fn + return maybe_compile_function( + expression, wrap_gfw, (2, 2, is_split(sys)), res; eval_expression, eval_module) end function generate_constraint_jacobian( - sys::System; expression = Val{true}, eval_expression = false, - eval_module = @__MODULE__, return_sparsity = false, + sys::System; expression = Val{true}, wrap_gfw = Val{false}, + eval_expression = false, eval_module = @__MODULE__, return_sparsity = false, simplify = false, sparse = false, kwargs...) cons = canonical_constraints(sys) dvs = unknowns(sys) @@ -456,17 +421,14 @@ function generate_constraint_jacobian( jac = Symbolics.jacobian(cons, dvs; simplify) end res = build_function_wrapper(sys, jac, dvs, ps...; expression = Val{true}, kwargs...) - if expression == Val{true} - return return_sparsity ? (res, sparsity) : res - end - f_oop, f_iip = eval_or_rgf.(res; eval_expression, eval_module) - fn = GeneratedFunctionWrapper{(2, 2, is_split(sys))}(f_oop, f_iip) + fn = maybe_compile_function( + expression, wrap_gfw, (2, 2, is_split(sys)), res; eval_expression, eval_module) return return_sparsity ? (fn, sparsity) : fn end function generate_constraint_hessian( - sys::System; expression = Val{true}, eval_expression = false, - eval_module = @__MODULE__, return_sparsity = false, + sys::System; expression = Val{true}, wrap_gfw = Val{false}, + eval_expression = false, eval_module = @__MODULE__, return_sparsity = false, simplify = false, sparse = false, kwargs...) cons = canonical_constraints(sys) dvs = unknowns(sys) @@ -481,11 +443,8 @@ function generate_constraint_hessian( hess = [Symbolics.hessian(cstr, dvs; simplify) for cstr in cons] end res = build_function_wrapper(sys, hess, dvs, ps...; expression = Val{true}, kwargs...) - if expression == Val{true} - return return_sparsity ? (res, sparsity) : res - end - f_oop, f_iip = eval_or_rgf.(res; eval_expression, eval_module) - fn = GeneratedFunctionWrapper{(2, 2, is_split(sys))}(f_oop, f_iip) + fn = maybe_compile_function( + expression, wrap_gfw, (2, 2, is_split(sys)), res; eval_expression, eval_module) return return_sparsity ? (fn, sparsity) : fn end From 44d3032e586ea4962b820150d0cdaaeb7bc5f78f Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Mon, 28 Apr 2025 15:09:15 +0530 Subject: [PATCH 137/185] feat: support returning `Expr` from `SymbolicTstops` and `ObservedFunctionCache` --- src/systems/abstractsystem.jl | 11 ++++++++--- src/systems/problem_utils.jl | 14 ++++++++++---- 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/src/systems/abstractsystem.jl b/src/systems/abstractsystem.jl index 5dda624c0b..509f0c438d 100644 --- a/src/systems/abstractsystem.jl +++ b/src/systems/abstractsystem.jl @@ -1875,10 +1875,15 @@ struct ObservedFunctionCache{S} end function ObservedFunctionCache( - sys; steady_state = false, eval_expression = false, + sys; expression = Val{false}, steady_state = false, eval_expression = false, eval_module = @__MODULE__, checkbounds = true, cse = true) - return ObservedFunctionCache( - sys, Dict(), steady_state, eval_expression, eval_module, checkbounds, cse) + if expression == Val{true} + :($ObservedFunctionCache($sys, Dict(), $steady_state, $eval_expression, + $eval_module, $checkbounds, $cse)) + else + ObservedFunctionCache( + sys, Dict(), steady_state, eval_expression, eval_module, checkbounds, cse) + end end # This is hit because ensemble problems do a deepcopy diff --git a/src/systems/problem_utils.jl b/src/systems/problem_utils.jl index 092c6b717b..1b21d26b9e 100644 --- a/src/systems/problem_utils.jl +++ b/src/systems/problem_utils.jl @@ -1271,7 +1271,8 @@ function (st::SymbolicTstops)(p, tspan) end function SymbolicTstops( - sys::AbstractSystem; eval_expression = false, eval_module = @__MODULE__) + sys::AbstractSystem; expression = Val{false}, eval_expression = false, + eval_module = @__MODULE__) tstops = symbolic_tstops(sys) isempty(tstops) && return nothing t0 = gensym(:t0) @@ -1290,9 +1291,14 @@ function SymbolicTstops( t1; expression = Val{true}, p_start = 1, p_end = length(rps), add_observed = false, force_SA = true) - tstops = eval_or_rgf(tstops; eval_expression, eval_module) - tstops = GeneratedFunctionWrapper{(1, 3, is_split(sys))}(tstops, nothing) - return SymbolicTstops(tstops) + tstops = GeneratedFunctionWrapper{(1, 3, is_split(sys))}( + expression, tstops, nothing; eval_expression, eval_module) + + if expression == Val{true} + return :($SymbolicTstops($tstops)) + else + return SymbolicTstops(tstops) + end end """ From a41e141cb014766cf50c7c45c76a163a649548a6 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Mon, 28 Apr 2025 15:09:33 +0530 Subject: [PATCH 138/185] feat: handle `expression = Val{true}` in `process_kwargs` --- src/systems/problem_utils.jl | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/systems/problem_utils.jl b/src/systems/problem_utils.jl index 1b21d26b9e..1af1fd9bf1 100644 --- a/src/systems/problem_utils.jl +++ b/src/systems/problem_utils.jl @@ -1234,18 +1234,20 @@ function SciMLBase.detect_cycles(sys::AbstractSystem, varmap::Dict{Any, Any}, va return !isempty(cycles) end -function process_kwargs(sys::System; callback = nothing, eval_expression = false, - eval_module = @__MODULE__, kwargs...) +function process_kwargs(sys::System; expression = Val{false}, callback = nothing, + eval_expression = false, eval_module = @__MODULE__, kwargs...) kwargs = filter_kwargs(kwargs) kwargs1 = (;) if is_time_dependent(sys) - cbs = process_events(sys; callback, eval_expression, eval_module, kwargs...) - if cbs !== nothing - kwargs1 = merge(kwargs1, (callback = cbs,)) + if expression == Val{false} + cbs = process_events(sys; callback, eval_expression, eval_module, kwargs...) + if cbs !== nothing + kwargs1 = merge(kwargs1, (callback = cbs,)) + end end - tstops = SymbolicTstops(sys; eval_expression, eval_module) + tstops = SymbolicTstops(sys; expression, eval_expression, eval_module) if tstops !== nothing kwargs1 = merge(kwargs1, (; tstops)) end From 48b9ea50e26b94a79c42a4d28d43d78bfed2f04b Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Mon, 28 Apr 2025 18:43:39 +0530 Subject: [PATCH 139/185] feat: add `maybe_codegen_scimlfn` and `maybe_codegen_scimlproblem` --- src/systems/problem_utils.jl | 94 ++++++++++++++++++++++++++++++++++++ 1 file changed, 94 insertions(+) diff --git a/src/systems/problem_utils.jl b/src/systems/problem_utils.jl index 1af1fd9bf1..70d7b8ad69 100644 --- a/src/systems/problem_utils.jl +++ b/src/systems/problem_utils.jl @@ -1394,6 +1394,100 @@ macro fallback_iip_specialize(ex) end |> esc end +""" + $(TYPEDSIGNATURES) + +Turn key-value pairs in `kws` into assignments and appent them to `block.args`. `head` is +the head of the `Expr` used to create the assignment. `filter` is a function that takes the +key and returns whether or not to include it in the assignments. +""" +function namedtuple_to_assignments!( + block, kws::NamedTuple; head = :(=), filter = Returns(true)) + for (k, v) in pairs(kws) + filter(k) || continue + push!(block.args, Expr(head, k, v)) + end +end + +""" + $(TYPEDSIGNATURES) + +Build an expression that constructs SciMLFunction `T`. `args` is a `NamedTuple` mapping +names of positional arguments to `T` to their (expression) values. `kwargs` are parsed +as keyword arguments to the constructor. +""" +function build_scimlfn_expr(T, args::NamedTuple; kwargs...) + kwargs = NamedTuple(kwargs) + let_args = Expr(:block) + namedtuple_to_assignments!(let_args, args) + + kwexpr = Expr(:parameters) + # don't include initialization data in the generated expression + filter = !isequal(:initialization_data) + namedtuple_to_assignments!(let_args, kwargs; filter = filter) + namedtuple_to_assignments!(kwexpr, kwargs; head = :kw, filter) + let_body = Expr(:call, T, kwexpr, keys(args)...) + return Expr(:let, let_args, let_body) +end + +""" + $(TYPEDSIGNATURES) + +Build an expression that constructs SciMLProblem `T`. `args` is a `NamedTuple` mapping +names of positional arguments to `T` to their (expression) values. `kwargs` are parsed +as keyword arguments to the constructor. +""" +function build_scimlproblem_expr(T, args::NamedTuple; kwargs...) + kwargs = NamedTuple(kwargs) + let_args = Expr(:block) + namedtuple_to_assignments!(let_args, args) + + kwexpr = Expr(:parameters) + namedtuple_to_assignments!(let_args, kwargs) + namedtuple_to_assignments!(kwexpr, kwargs; head = :kw) + let_body = Expr(:call, remake, Expr(:call, T, kwexpr, keys(args)...)) + return Expr(:let, let_args, let_body) +end + +""" + $(TYPEDSIGNATURES) + +Return an expression constructing SciMLFunction `T` with positional arguments `args` +and keywords `kwargs`. +""" +function maybe_codegen_scimlfn(::Type{Val{true}}, T, args::NamedTuple; kwargs...) + build_scimlfn_expr(T, args; kwargs...) +end + +""" + $(TYPEDSIGNATURES) + +Construct SciMLFunction `T` with positional arguments `args` and keywords `kwargs`. +""" +function maybe_codegen_scimlfn(::Type{Val{false}}, T, args::NamedTuple; kwargs...) + T(args...; kwargs...) +end + +""" + $(TYPEDSIGNATURES) + +Return an expression constructing SciMLProblem `T` with positional arguments `args` +and keywords `kwargs`. +""" +function maybe_codegen_scimlproblem(::Type{Val{true}}, T, args::NamedTuple; kwargs...) + build_scimlproblem_expr(T, args; kwargs...) +end + +""" + $(TYPEDSIGNATURES) + +Construct SciMLProblem `T` with positional arguments `args` and keywords `kwargs`. +""" +function maybe_codegen_scimlproblem(::Type{Val{false}}, T, args::NamedTuple; kwargs...) + # Call `remake` so it runs initialization if it is trivial + remake(T(args...; kwargs...)) +end + ############## # Legacy functions for backward compatibility ############## From 0645dcee5555ede1022fa44ea9647657e72d5e16 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Mon, 28 Apr 2025 15:23:34 +0530 Subject: [PATCH 140/185] feat: add `expression` kwarg to `ODEFunction`, `ODEProblem` --- src/problems/odeproblem.jl | 35 ++++++++++++++++++++++------------- src/systems/problem_utils.jl | 2 +- 2 files changed, 23 insertions(+), 14 deletions(-) diff --git a/src/problems/odeproblem.jl b/src/problems/odeproblem.jl index 9e9a4d31d3..f126651f5d 100644 --- a/src/problems/odeproblem.jl +++ b/src/problems/odeproblem.jl @@ -2,14 +2,14 @@ sys::System; u0 = nothing, p = nothing, tgrad = false, jac = false, t = nothing, eval_expression = false, eval_module = @__MODULE__, sparse = false, steady_state = false, checkbounds = false, sparsity = false, analytic = nothing, - simplify = false, cse = true, initialization_data = nothing, + simplify = false, cse = true, initialization_data = nothing, expression = Val{false}, check_compatibility = true, kwargs...) where {iip, spec} check_complete(sys, ODEFunction) check_compatibility && check_compatible_system(ODEFunction, sys) dvs = unknowns(sys) ps = parameters(sys) - f = generate_rhs(sys, dvs, ps; expression = Val{false}, wrap_gfw = Val{true}, + f = generate_rhs(sys, dvs, ps; expression, wrap_gfw = Val{true}, eval_expression, eval_module, checkbounds = checkbounds, cse, kwargs...) @@ -17,12 +17,16 @@ if u0 === nothing || p === nothing || t === nothing error("u0, p, and t must be specified for FunctionWrapperSpecialize on ODEFunction.") end - f = SciMLBase.wrapfun_iip(f, (u0, u0, p, t)) + if expression == Val{true} + f = :($(SciMLBase.wrapfun_iip)($f, ($u0, $u0, $p, $t))) + else + f = SciMLBase.wrapfun_iip(f, (u0, u0, p, t)) + end end if tgrad _tgrad = generate_tgrad( - sys, dvs, ps; expression = Val{false}, wrap_gfw = Val{true}, + sys, dvs, ps; expression, wrap_gfw = Val{true}, simplify, cse, eval_expression, eval_module, checkbounds, kwargs...) else _tgrad = nothing @@ -30,7 +34,7 @@ if jac _jac = generate_jacobian( - sys, dvs, ps; expression = Val{false}, wrap_gfw = Val{true}, + sys, dvs, ps; expression, wrap_gfw = Val{true}, simplify, sparse, cse, eval_expression, eval_module, checkbounds, kwargs...) else _jac = nothing @@ -40,12 +44,13 @@ _M = concrete_massmatrix(M; sparse, u0) observedfun = ObservedFunctionCache( - sys; steady_state, eval_expression, eval_module, checkbounds, cse) + sys; expression, steady_state, eval_expression, eval_module, checkbounds, cse) _W_sparsity = W_sparsity(sys) W_prototype = calculate_W_prototype(_W_sparsity; u0, sparse) - ODEFunction{iip, spec}(f; + args = (; f) + kwargs = (; sys = sys, jac = _jac, tgrad = _tgrad, @@ -55,23 +60,27 @@ sparsity = sparsity ? _W_sparsity : nothing, analytic = analytic, initialization_data) + + maybe_codegen_scimlfn(expression, ODEFunction{iip, spec}, args; kwargs...) end @fallback_iip_specialize function SciMLBase.ODEProblem{iip, spec}( sys::System, u0map, tspan, parammap = SciMLBase.NullParameters(); callback = nothing, check_length = true, eval_expression = false, - eval_module = @__MODULE__, check_compatibility = true, kwargs...) where {iip, spec} + expression = Val{false}, eval_module = @__MODULE__, check_compatibility = true, + kwargs...) where {iip, spec} check_complete(sys, ODEProblem) check_compatibility && check_compatible_system(ODEProblem, sys) f, u0, p = process_SciMLProblem(ODEFunction{iip, spec}, sys, u0map, parammap; t = tspan !== nothing ? tspan[1] : tspan, check_length, eval_expression, - eval_module, check_compatibility, kwargs...) + eval_module, expression, check_compatibility, kwargs...) - kwargs = process_kwargs(sys; callback, eval_expression, eval_module, kwargs...) - # Call `remake` so it runs initialization if it is trivial - return remake(ODEProblem{iip}( - f, u0, tspan, p, StandardODEProblem(); kwargs...)) + kwargs = process_kwargs( + sys; expression, callback, eval_expression, eval_module, kwargs...) + + args = (; f, u0, tspan, p, ptype = StandardODEProblem()) + maybe_codegen_scimlproblem(expression, ODEProblem{iip}, args; kwargs...) end """ diff --git a/src/systems/problem_utils.jl b/src/systems/problem_utils.jl index 70d7b8ad69..23b58316b3 100644 --- a/src/systems/problem_utils.jl +++ b/src/systems/problem_utils.jl @@ -1369,7 +1369,7 @@ macro fallback_iip_specialize(ex) # Handle case when this is a problem constructor and `u0map` is a `StaticArray`, # where `iip` should default to `false`. fn_sarr = nothing - if endswith(string(fnname_name), "Problem") + if occursin("Problem", string(fnname_name)) # args should at least contain an argument for the `u0map` @assert length(args) > 3 u0_arg = args[3] From 9e58feb961c663a92db42e6c7ce952dffcf504df Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Mon, 28 Apr 2025 15:28:38 +0530 Subject: [PATCH 141/185] feat: add `expression` kwarg to `SteadyStateProblem` --- src/problems/odeproblem.jl | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/problems/odeproblem.jl b/src/problems/odeproblem.jl index f126651f5d..99ae57f56c 100644 --- a/src/problems/odeproblem.jl +++ b/src/problems/odeproblem.jl @@ -98,19 +98,19 @@ Generates an SteadyStateProblem from a `System` of ODEs and allows for automatic symbolically calculating numerical enhancements. """ @fallback_iip_specialize function DiffEqBase.SteadyStateProblem{iip, spec}( - sys::System, u0map, - parammap = SciMLBase.NullParameters(); check_length = true, - check_compatibility = true, kwargs...) where {iip, spec} + sys::System, u0map, parammap; check_length = true, check_compatibility = true, + expression = Val{false}, kwargs...) where {iip, spec} check_complete(sys, SteadyStateProblem) check_compatibility && check_compatible_system(SteadyStateProblem, sys) f, u0, p = process_SciMLProblem(ODEFunction{iip}, sys, u0map, parammap; - steady_state = true, check_length, check_compatibility, + steady_state = true, check_length, check_compatibility, expression, force_initialization_time_independent = true, kwargs...) - kwargs = process_kwargs(sys; kwargs...) - # Call `remake` so it runs initialization if it is trivial - remake(SteadyStateProblem{iip}(f, u0, p; kwargs...)) + kwargs = process_kwargs(sys; expression, kwargs...) + args = (; f, u0, p) + + maybe_codegen_scimlproblem(expression, SteadyStateProblem{iip}, args; kwargs...) end function check_compatible_system( From 54e13a0781909e7bcb9deeaa3afe1a313a4795b3 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Mon, 28 Apr 2025 15:47:30 +0530 Subject: [PATCH 142/185] feat: add `expression` kwarg to `BVProblem` --- src/problems/bvproblem.jl | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/src/problems/bvproblem.jl b/src/problems/bvproblem.jl index df947f63d8..de4442b610 100644 --- a/src/problems/bvproblem.jl +++ b/src/problems/bvproblem.jl @@ -44,10 +44,10 @@ If the `System` has algebraic equations, like `x(t)^2 + y(t)^2`, the resulting `BVProblem` must be solved using BVDAE solvers, such as Ascher. """ @fallback_iip_specialize function SciMLBase.BVProblem{iip, spec}( - sys::System, u0map, tspan, parammap = SciMLBase.NullParameters(); - check_compatibility = true, cse = true, checkbounds = false, eval_expression = false, - eval_module = @__MODULE__, guesses = Dict(), callback = nothing, kwargs...) where { - iip, spec} + sys::System, u0map, tspan, parammap; check_compatibility = true, cse = true, + checkbounds = false, eval_expression = false, eval_module = @__MODULE__, + expression = Val{false}, guesses = Dict(), callback = nothing, + kwargs...) where {iip, spec} check_complete(sys, BVProblem) check_compatibility && check_compatible_system(BVProblem, sys) isnothing(callback) || error("BVP solvers do not support callbacks.") @@ -55,10 +55,11 @@ If the `System` has algebraic equations, like `x(t)^2 + y(t)^2`, the resulting # Systems without algebraic equations should use both fixed values + guesses # for initialization. _u0map = has_alg_eqs(sys) ? u0map : merge(Dict(u0map), Dict(guesses)) + fode, u0, p = process_SciMLProblem( ODEFunction{iip, spec}, sys, _u0map, parammap; guesses, - t = tspan !== nothing ? tspan[1] : tspan, check_compatibility = false, cse, checkbounds, - time_dependent_init = false, kwargs...) + t = tspan !== nothing ? tspan[1] : tspan, check_compatibility = false, cse, + checkbounds, time_dependent_init = false, expression, kwargs...) dvs = unknowns(sys) stidxmap = Dict([v => i for (i, v) in enumerate(dvs)]) @@ -71,9 +72,10 @@ If the `System` has algebraic equations, like `x(t)^2 + y(t)^2`, the resulting @warn "The BVProblem is overdetermined. The total number of conditions (# constraints + # fixed initial values given by u0map) exceeds the total number of states. The BVP solvers will default to doing a nonlinear least-squares optimization." end - kwargs = process_kwargs(sys; kwargs...) - # Call `remake` so it runs initialization if it is trivial - return remake(BVProblem{iip}(fode, fbc, u0, tspan[1], p; kwargs...)) + kwargs = process_kwargs(sys; expression, kwargs...) + args = (; fode, fbc, u0, t0 = tspan[1], p) + + return maybe_codegen_scimlproblem(expression, BVProblem{iip}, args; kwargs...) end function check_compatible_system(T::Type{BVProblem}, sys::System) From 760ea137da1b17679647e63c02730113bad7202c Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Mon, 28 Apr 2025 16:22:23 +0530 Subject: [PATCH 143/185] feat: add `expression` kwarg to `DAEFunction`, `DAEProblem` --- src/problems/daeproblem.jl | 34 ++++++++++++++++++++++------------ 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/src/problems/daeproblem.jl b/src/problems/daeproblem.jl index de6c4cd0a8..d1f8893cd5 100644 --- a/src/problems/daeproblem.jl +++ b/src/problems/daeproblem.jl @@ -3,13 +3,13 @@ t = nothing, eval_expression = false, eval_module = @__MODULE__, sparse = false, steady_state = false, checkbounds = false, sparsity = false, analytic = nothing, simplify = false, cse = true, initialization_data = nothing, - check_compatibility = true, kwargs...) where {iip, spec} + expression = Val{false}, check_compatibility = true, kwargs...) where {iip, spec} check_complete(sys, DAEFunction) check_compatibility && check_compatible_system(DAEFunction, sys) dvs = unknowns(sys) ps = parameters(sys) - f = generate_rhs(sys, dvs, ps; expression = Val{false}, wrap_gfw = Val{true}, + f = generate_rhs(sys, dvs, ps; expression, wrap_gfw = Val{true}, implicit_dae = true, eval_expression, eval_module, checkbounds = checkbounds, cse, kwargs...) @@ -17,11 +17,15 @@ if u0 === nothing || p === nothing || t === nothing error("u0, p, and t must be specified for FunctionWrapperSpecialize on ODEFunction.") end - f = SciMLBase.wrapfun_iip(f, (u0, u0, p, t)) + if expression == Val{true} + f = :($(SciMLBase.wrapfun_iip)($f, ($u0, $u0, $u0, $p, $t))) + else + f = SciMLBase.wrapfun_iip(f, (u0, u0, u0, p, t)) + end end if jac - _jac = generate_dae_jacobian(sys, dvs, ps; expression = Val{false}, + _jac = generate_dae_jacobian(sys, dvs, ps; expression, wrap_gfw = Val{true}, simplify, sparse, cse, eval_expression, eval_module, checkbounds, kwargs...) else @@ -29,7 +33,7 @@ end observedfun = ObservedFunctionCache( - sys; steady_state, eval_expression, eval_module, checkbounds, cse) + sys; expression, steady_state, eval_expression, eval_module, checkbounds, cse) jac_prototype = if sparse uElType = u0 === nothing ? Float64 : eltype(u0) @@ -45,33 +49,39 @@ nothing end - DAEFunction{iip, spec}(f; + kwargs = (; sys = sys, jac = _jac, jac_prototype = jac_prototype, observed = observedfun, analytic = analytic, initialization_data) + args = (; f) + + return maybe_codegen_scimlfn(expression, DAEFunction{iip, spec}, args; kwargs...) end @fallback_iip_specialize function SciMLBase.DAEProblem{iip, spec}( sys::System, du0map, u0map, tspan, parammap = SciMLBase.NullParameters(); callback = nothing, check_length = true, eval_expression = false, - eval_module = @__MODULE__, check_compatibility = true, kwargs...) where {iip, spec} + eval_module = @__MODULE__, check_compatibility = true, + expression = Val{false}, kwargs...) where {iip, spec} check_complete(sys, DAEProblem) check_compatibility && check_compatible_system(DAEProblem, sys) f, du0, u0, p = process_SciMLProblem(DAEFunction{iip, spec}, sys, u0map, parammap; du0map, t = tspan !== nothing ? tspan[1] : tspan, check_length, eval_expression, - eval_module, check_compatibility, implicit_dae = true, kwargs...) + eval_module, check_compatibility, implicit_dae = true, expression, kwargs...) - kwargs = process_kwargs(sys; callback, eval_expression, eval_module, kwargs...) + kwargs = process_kwargs(sys; expression, callback, eval_expression, eval_module, + kwargs...) diffvars = collect_differential_variables(sys) sts = unknowns(sys) differential_vars = map(Base.Fix2(in, diffvars), sts) - # Call `remake` so it runs initialization if it is trivial - return remake(DAEProblem{iip}( - f, du0, u0, tspan, p; differential_vars, kwargs...)) + args = (; f, du0, u0, tspan, p) + kwargs = (; differential_vars, kwargs...) + + return maybe_codegen_scimlproblem(expression, DAEProblem{iip}, args; kwargs...) end From 6c2f20d429bc39b8be25bfca6c4d72c970d71efd Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Mon, 28 Apr 2025 16:38:55 +0530 Subject: [PATCH 144/185] feat: add `expression` kwarg to `DDEFunction`, `DDEProblem` --- src/problems/ddeproblem.jl | 49 ++++++++++++++++++++++++-------------- 1 file changed, 31 insertions(+), 18 deletions(-) diff --git a/src/problems/ddeproblem.jl b/src/problems/ddeproblem.jl index be8d35265e..24ade8156c 100644 --- a/src/problems/ddeproblem.jl +++ b/src/problems/ddeproblem.jl @@ -1,16 +1,15 @@ @fallback_iip_specialize function SciMLBase.DDEFunction{iip, spec}( - sys::System; u0 = nothing, p = nothing, - eval_expression = false, eval_module = @__MODULE__, checkbounds = false, + sys::System; u0 = nothing, p = nothing, eval_expression = false, + eval_module = @__MODULE__, expression = Val{false}, checkbounds = false, initialization_data = nothing, cse = true, check_compatibility = true, - sparse = false, simplify = false, analytic = nothing, kwargs...) where { - iip, spec} + sparse = false, simplify = false, analytic = nothing, kwargs...) where {iip, spec} check_complete(sys, DDEFunction) check_compatibility && check_compatible_system(DDEFunction, sys) dvs = unknowns(sys) ps = parameters(sys) - f = generate_rhs(sys, dvs, ps; expression = Val{false}, wrap_gfw = Val{true}, + f = generate_rhs(sys, dvs, ps; expression, wrap_gfw = Val{true}, eval_expression, eval_module, checkbounds = checkbounds, cse, kwargs...) @@ -18,48 +17,62 @@ if u0 === nothing || p === nothing || t === nothing error("u0, p, and t must be specified for FunctionWrapperSpecialize on DDEFunction.") end - f = SciMLBase.wrapfun_iip(f, (u0, u0, p, t)) + if expression == Val{true} + f = :($(SciMLBase.wrapfun_iip)($f, ($u0, $u0, $p, $t))) + else + f = SciMLBase.wrapfun_iip(f, (u0, u0, p, t)) + end end M = calculate_massmatrix(sys) _M = concrete_massmatrix(M; sparse, u0) observedfun = ObservedFunctionCache( - sys; eval_expression, eval_module, checkbounds, cse) + sys; expression, eval_expression, eval_module, checkbounds, cse) - DDEFunction{iip, spec}(f; + kwargs = (; sys = sys, mass_matrix = _M, observed = observedfun, analytic = analytic, initialization_data) + args = (; f) + + return maybe_codegen_scimlfn(expression, DDEFunction{iip, spec}, args; kwargs...) end @fallback_iip_specialize function SciMLBase.DDEProblem{iip, spec}( sys::System, u0map, tspan, parammap = SciMLBase.NullParameters(); callback = nothing, check_length = true, cse = true, checkbounds = false, eval_expression = false, eval_module = @__MODULE__, check_compatibility = true, - u0_constructor = identity, - kwargs...) where {iip, spec} + u0_constructor = identity, expression = Val{false}, kwargs...) where {iip, spec} check_complete(sys, DDEProblem) check_compatibility && check_compatible_system(DDEProblem, sys) f, u0, p = process_SciMLProblem(DDEFunction{iip, spec}, sys, u0map, parammap; t = tspan !== nothing ? tspan[1] : tspan, check_length, cse, checkbounds, - eval_expression, eval_module, check_compatibility, symbolic_u0 = true, kwargs...) + eval_expression, eval_module, check_compatibility, symbolic_u0 = true, + expression, kwargs...) h = generate_history( - sys, u0; expression = Val{false}, cse, eval_expression, eval_module, + sys, u0; expression, wrap_gfw = Val{true}, cse, eval_expression, eval_module, checkbounds) - u0 = float.(h(p, tspan[1])) - if u0 !== nothing - u0 = u0_constructor(u0) + + if expression == Val{true} + if u0 !== nothing + u0 = :($u0_constructor($map($float, h(p, tspan[1])))) + end + else + if u0 !== nothing + u0 = u0_constructor(float.(h(p, tspan[1]))) + end end - kwargs = process_kwargs(sys; callback, eval_expression, eval_module, kwargs...) + kwargs = process_kwargs( + sys; expression, callback, eval_expression, eval_module, kwargs...) + args = (; f, u0, h, tspan, p) - # Call `remake` so it runs initialization if it is trivial - return remake(DDEProblem{iip}(f, u0, h, tspan, p; kwargs...)) + return maybe_codegen_scimlproblem(expression, DDEProblem{iip}, args; kwargs...) end function check_compatible_system(T::Union{Type{DDEFunction}, Type{DDEProblem}}, sys::System) From 29ed82eb6a6be7329aae83f03c75c6d800ba965b Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Mon, 28 Apr 2025 16:59:04 +0530 Subject: [PATCH 145/185] feat: add `expression` kwarg to `SDEFunction`, `SDEProblem` --- src/problems/sdeproblem.jl | 39 ++++++++++++++++++++++++-------------- 1 file changed, 25 insertions(+), 14 deletions(-) diff --git a/src/problems/sdeproblem.jl b/src/problems/sdeproblem.jl index 2f582982c3..c068e9bf41 100644 --- a/src/problems/sdeproblem.jl +++ b/src/problems/sdeproblem.jl @@ -3,27 +3,31 @@ t = nothing, eval_expression = false, eval_module = @__MODULE__, sparse = false, steady_state = false, checkbounds = false, sparsity = false, analytic = nothing, simplify = false, cse = true, initialization_data = nothing, - check_compatibility = true, kwargs...) where {iip, spec} + check_compatibility = true, expression = Val{false}, kwargs...) where {iip, spec} check_complete(sys, SDEFunction) check_compatibility && check_compatible_system(SDEFunction, sys) dvs = unknowns(sys) ps = parameters(sys) - f = generate_rhs(sys, dvs, ps; expression = Val{false}, wrap_gfw = Val{true}, + f = generate_rhs(sys, dvs, ps; expression, wrap_gfw = Val{true}, eval_expression, eval_module, checkbounds = checkbounds, cse, kwargs...) - g = generate_diffusion_function(sys, dvs, ps; expression = Val{false}, + g = generate_diffusion_function(sys, dvs, ps; expression, wrap_gfw = Val{true}, eval_expression, eval_module, checkbounds, cse, kwargs...) if spec === SciMLBase.FunctionWrapperSpecialize && iip if u0 === nothing || p === nothing || t === nothing - error("u0, p, and t must be specified for FunctionWrapperSpecialize on ODEFunction.") + error("u0, p, and t must be specified for FunctionWrapperSpecialize on SDEFunction.") + end + if expression == Val{true} + f = :($(SciMLBase.wrapfun_iip)($f, ($u0, $u0, $p, $t))) + else + f = SciMLBase.wrapfun_iip(f, (u0, u0, p, t)) end - f = SciMLBase.wrapfun_iip(f, (u0, u0, p, t)) end if tgrad - _tgrad = generate_tgrad(sys, dvs, ps; expression = Val{false}, + _tgrad = generate_tgrad(sys, dvs, ps; expression, wrap_gfw = Val{true}, simplify, cse, eval_expression, eval_module, checkbounds, kwargs...) else @@ -31,7 +35,7 @@ end if jac - _jac = generate_jacobian(sys, dvs, ps; expression = Val{false}, + _jac = generate_jacobian(sys, dvs, ps; expression, wrap_gfw = Val{true}, simplify, sparse, cse, eval_expression, eval_module, checkbounds, kwargs...) else @@ -42,12 +46,12 @@ _M = concrete_massmatrix(M; sparse, u0) observedfun = ObservedFunctionCache( - sys; steady_state, eval_expression, eval_module, checkbounds, cse) + sys; expression, steady_state, eval_expression, eval_module, checkbounds, cse) _W_sparsity = W_sparsity(sys) W_prototype = calculate_W_prototype(_W_sparsity; u0, sparse) - SDEFunction{iip, spec}(f, g; + kwargs = (; sys = sys, jac = _jac, tgrad = _tgrad, @@ -57,24 +61,31 @@ sparsity = sparsity ? _W_sparsity : nothing, analytic = analytic, initialization_data) + args = (; f, g) + + return maybe_codegen_scimlfn(expression, SDEFunction{iip, spec}, args; kwargs...) end @fallback_iip_specialize function SciMLBase.SDEProblem{iip, spec}( sys::System, u0map, tspan, parammap = SciMLBase.NullParameters(); callback = nothing, check_length = true, eval_expression = false, eval_module = @__MODULE__, check_compatibility = true, sparse = false, - sparsenoise = sparse, kwargs...) where {iip, spec} + sparsenoise = sparse, expression = Val{false}, kwargs...) where {iip, spec} check_complete(sys, SDEProblem) check_compatibility && check_compatible_system(SDEProblem, sys) f, u0, p = process_SciMLProblem(SDEFunction{iip, spec}, sys, u0map, parammap; t = tspan !== nothing ? tspan[1] : tspan, check_length, eval_expression, - eval_module, check_compatibility, sparse, kwargs...) + eval_module, check_compatibility, sparse, expression, kwargs...) noise, noise_rate_prototype = calculate_noise_and_rate_prototype(sys, u0; sparsenoise) - kwargs = process_kwargs(sys; callback, eval_expression, eval_module, kwargs...) - # Call `remake` so it runs initialization if it is trivial - return remake(SDEProblem{iip}(f, u0, tspan, p; noise, noise_rate_prototype, kwargs...)) + kwargs = process_kwargs(sys; expression, callback, eval_expression, eval_module, + kwargs...) + + args = (; f, u0, tspan, p) + kwargs = (; noise, noise_rate_prototype, kwargs...) + + return maybe_codegen_scimlproblem(expression, SDEProblem{iip}, args; kwargs...) end function check_compatible_system(T::Union{Type{SDEFunction}, Type{SDEProblem}}, sys::System) From ac3de03c1839d640cc942061ab13c2edaac4b6f9 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Mon, 28 Apr 2025 17:13:00 +0530 Subject: [PATCH 146/185] feat: add `expression` kwarg to `SDDEFunction`, `SDDEProblem` --- src/problems/sddeproblem.jl | 56 ++++++++++++++++++++++++------------- 1 file changed, 37 insertions(+), 19 deletions(-) diff --git a/src/problems/sddeproblem.jl b/src/problems/sddeproblem.jl index 690741f60b..c444ffa652 100644 --- a/src/problems/sddeproblem.jl +++ b/src/problems/sddeproblem.jl @@ -1,40 +1,45 @@ @fallback_iip_specialize function SciMLBase.SDDEFunction{iip, spec}( - sys::System; u0 = nothing, p = nothing, + sys::System; u0 = nothing, p = nothing, expression = Val{false}, eval_expression = false, eval_module = @__MODULE__, checkbounds = false, initialization_data = nothing, cse = true, check_compatibility = true, - sparse = false, simplify = false, analytic = nothing, kwargs...) where { - iip, spec} + sparse = false, simplify = false, analytic = nothing, kwargs...) where {iip, spec} check_complete(sys, SDDEFunction) check_compatibility && check_compatible_system(SDDEFunction, sys) dvs = unknowns(sys) ps = parameters(sys) - f = generate_rhs(sys, dvs, ps; expression = Val{false}, wrap_gfw = Val{true}, - eval_expression, eval_module, checkbounds = checkbounds, cse, - kwargs...) - g = generate_diffusion_function(sys, dvs, ps; expression = Val{false}, + f = generate_rhs(sys, dvs, ps; expression, wrap_gfw = Val{true}, + eval_expression, eval_module, checkbounds = checkbounds, cse, kwargs...) + g = generate_diffusion_function(sys, dvs, ps; expression, wrap_gfw = Val{true}, eval_expression, eval_module, checkbounds, cse, kwargs...) if spec === SciMLBase.FunctionWrapperSpecialize && iip if u0 === nothing || p === nothing || t === nothing error("u0, p, and t must be specified for FunctionWrapperSpecialize on SDDEFunction.") end - f = SciMLBase.wrapfun_iip(f, (u0, u0, p, t)) + if expression == Val{true} + f = :($(SciMLBase.wrapfun_iip)($f, ($u0, $u0, $p, $t))) + else + f = SciMLBase.wrapfun_iip(f, (u0, u0, p, t)) + end end M = calculate_massmatrix(sys) _M = concrete_massmatrix(M; sparse, u0) observedfun = ObservedFunctionCache( - sys; eval_expression, eval_module, checkbounds, cse) + sys; expression, eval_expression, eval_module, checkbounds, cse) - SDDEFunction{iip, spec}(f, g; + kwargs = (; sys = sys, mass_matrix = _M, observed = observedfun, analytic = analytic, initialization_data) + args = (; f, g) + + return maybe_codegen_scimlfn(expression, SDDEFunction{iip, spec}, args; kwargs...) end @fallback_iip_specialize function SciMLBase.SDDEProblem{iip, spec}( @@ -42,28 +47,41 @@ end callback = nothing, check_length = true, cse = true, checkbounds = false, eval_expression = false, eval_module = @__MODULE__, check_compatibility = true, u0_constructor = identity, sparse = false, sparsenoise = sparse, - kwargs...) where {iip, spec} + expression = Val{false}, kwargs...) where {iip, spec} check_complete(sys, SDDEProblem) check_compatibility && check_compatible_system(SDDEProblem, sys) f, u0, p = process_SciMLProblem(SDDEFunction{iip, spec}, sys, u0map, parammap; t = tspan !== nothing ? tspan[1] : tspan, check_length, cse, checkbounds, - eval_expression, eval_module, check_compatibility, sparse, symbolic_u0 = true, kwargs...) + eval_expression, eval_module, check_compatibility, sparse, symbolic_u0 = true, + expression, kwargs...) h = generate_history( - sys, u0; expression = Val{false}, cse, eval_expression, eval_module, + sys, u0; expression, wrap_gfw = Val{true}, cse, eval_expression, eval_module, checkbounds) - u0 = float.(h(p, tspan[1])) - if u0 !== nothing - u0 = u0_constructor(u0) + + if expression == Val{true} + if u0 !== nothing + u0 = :($u0_constructor($map($float, h(p, tspan[1])))) + end + else + if u0 !== nothing + u0 = u0_constructor(float.(h(p, tspan[1]))) + end end noise, noise_rate_prototype = calculate_noise_and_rate_prototype(sys, u0; sparsenoise) kwargs = process_kwargs(sys; callback, eval_expression, eval_module, kwargs...) - # Call `remake` so it runs initialization if it is trivial - return remake(SDDEProblem{iip}( - f, f.g, u0, h, tspan, p; noise, noise_rate_prototype, kwargs...)) + if expression == Val{true} + g = :(f.g) + else + g = f.g + end + args = (; f, g, u0, h, tspan, p) + kwargs = (; noise, noise_rate_prototype, kwargs...) + + return maybe_codegen_scimlproblem(expression, SDDEProblem{iip}, args; kwargs...) end function check_compatible_system( From 58530f11476c6274dc2241ca64794588f45bbdcd Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Mon, 28 Apr 2025 17:26:14 +0530 Subject: [PATCH 147/185] feat: add `expression` kwarg to `DiscreteFunction`, `DiscreteProblem` --- src/problems/discreteproblem.jl | 42 +++++++++++++++++++++++---------- 1 file changed, 29 insertions(+), 13 deletions(-) diff --git a/src/problems/discreteproblem.jl b/src/problems/discreteproblem.jl index 04b54828db..e95d5c20a2 100644 --- a/src/problems/discreteproblem.jl +++ b/src/problems/discreteproblem.jl @@ -1,15 +1,15 @@ @fallback_iip_specialize function SciMLBase.DiscreteFunction{iip, spec}( - sys::System; u0 = nothing, p = nothing, - t = nothing, eval_expression = false, eval_module = @__MODULE__, + sys::System; u0 = nothing, p = nothing, t = nothing, + eval_expression = false, eval_module = @__MODULE__, expression = Val{false}, checkbounds = false, analytic = nothing, simplify = false, cse = true, - initialization_data = nothing, check_compatibility = true, kwargs...) where { - iip, spec} + initialization_data = nothing, check_compatibility = true, + kwargs...) where {iip, spec} check_complete(sys, DiscreteFunction) check_compatibility && check_compatible_system(DiscreteFunction, sys) dvs = unknowns(sys) ps = parameters(sys) - f = generate_rhs(sys, dvs, ps; expression = Val{false}, wrap_gfw = Val{true}, + f = generate_rhs(sys, dvs, ps; expression, wrap_gfw = Val{true}, eval_expression, eval_module, checkbounds = checkbounds, cse, kwargs...) @@ -17,35 +17,51 @@ if u0 === nothing || p === nothing || t === nothing error("u0, p, and t must be specified for FunctionWrapperSpecialize on DiscreteFunction.") end - f = SciMLBase.wrapfun_iip(f, (u0, u0, p, t)) + if expression == Val{true} + f = :($(SciMLBase.wrapfun_iip)($f, ($u0, $u0, $p, $t))) + else + f = SciMLBase.wrapfun_iip(f, (u0, u0, p, t)) + end end observedfun = ObservedFunctionCache( - sys; steady_state = false, eval_expression, eval_module, checkbounds, cse) + sys; steady_state = false, expression, eval_expression, eval_module, checkbounds, + cse) - DiscreteFunction{iip, spec}(f; + kwargs = (; sys = sys, observed = observedfun, analytic = analytic, initialization_data) + args = (; f) + + return maybe_codegen_scimlfn(expression, DiscreteFunction{iip, spec}, args; kwargs...) end @fallback_iip_specialize function SciMLBase.DiscreteProblem{iip, spec}( sys::System, u0map, tspan, parammap = SciMLBase.NullParameters(); - check_compatibility = true, kwargs...) where {iip, spec} + check_compatibility = true, expression = Val{false}, kwargs...) where {iip, spec} check_complete(sys, DiscreteProblem) check_compatibility && check_compatible_system(DiscreteProblem, sys) dvs = unknowns(sys) u0map = to_varmap(u0map, dvs) u0map = shift_u0map_forward(sys, u0map, defaults(sys)) + f, u0, p = process_SciMLProblem(DiscreteFunction{iip, spec}, sys, u0map, parammap; - t = tspan !== nothing ? tspan[1] : tspan, check_compatibility, kwargs...) - u0 = f(u0, p, tspan[1]) + t = tspan !== nothing ? tspan[1] : tspan, check_compatibility, expression, + kwargs...) + + if expression == Val{true} + u0 = :(f($u0, p, tspan[1])) + else + u0 = f(u0, p, tspan[1]) + end kwargs = process_kwargs(sys; kwargs...) - # Call `remake` so it runs initialization if it is trivial - return remake(DiscreteProblem{iip}(f, u0, tspan, p; kwargs...)) + args = (; f, u0, tspan, p) + + return maybe_codegen_scimlproblem(expression, DiscreteProblem{iip}, args; kwargs...) end function check_compatible_system( From 6fe32dcbc37eb99b742483024770a832662797dc Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Mon, 28 Apr 2025 19:44:56 +0530 Subject: [PATCH 148/185] feat: add `expression` kwarg for `NonlinearFunction`, `NonlinearProblem`, `NonlinearLeastSquaresProblem` --- src/problems/nonlinearproblem.jl | 40 ++++++++++++++++++++------------ 1 file changed, 25 insertions(+), 15 deletions(-) diff --git a/src/problems/nonlinearproblem.jl b/src/problems/nonlinearproblem.jl index 4a04bcfd37..1a0eb9fa07 100644 --- a/src/problems/nonlinearproblem.jl +++ b/src/problems/nonlinearproblem.jl @@ -3,13 +3,13 @@ eval_expression = false, eval_module = @__MODULE__, sparse = false, checkbounds = false, sparsity = false, analytic = nothing, simplify = false, cse = true, initialization_data = nothing, - check_compatibility = true, kwargs...) where {iip, spec} + check_compatibility = true, expression = Val{false}, kwargs...) where {iip, spec} check_complete(sys, NonlinearFunction) check_compatibility && check_compatible_system(NonlinearFunction, sys) dvs = unknowns(sys) ps = parameters(sys) - f = generate_rhs(sys, dvs, ps; expression = Val{false}, wrap_gfw = Val{true}, + f = generate_rhs(sys, dvs, ps; expression, wrap_gfw = Val{true}, eval_expression, eval_module, checkbounds = checkbounds, cse, kwargs...) @@ -17,11 +17,15 @@ if u0 === nothing || p === nothing error("u0, and p must be specified for FunctionWrapperSpecialize on NonlinearFunction.") end - f = SciMLBase.wrapfun_iip(f, (u0, u0, p)) + if expression == Val{true} + f = :($(SciMLBase.wrapfun_iip)($f, ($u0, $u0, $p))) + else + f = SciMLBase.wrapfun_iip(f, (u0, u0, p)) + end end if jac - _jac = generate_jacobian(sys, dvs, ps; expression = Val{false}, + _jac = generate_jacobian(sys, dvs, ps; expression, wrap_gfw = Val{true}, simplify, sparse, cse, eval_expression, eval_module, checkbounds, kwargs...) else @@ -29,7 +33,8 @@ end observedfun = ObservedFunctionCache( - sys; steady_state = false, eval_expression, eval_module, checkbounds, cse) + sys; steady_state = false, expression, eval_expression, eval_module, checkbounds, + cse) if length(dvs) == length(equations(sys)) resid_prototype = nothing @@ -43,7 +48,7 @@ jac_prototype = nothing end - NonlinearFunction{iip, spec}(f; + kwargs = (; sys = sys, jac = _jac, observed = observedfun, @@ -51,35 +56,40 @@ jac_prototype, resid_prototype, initialization_data) + args = (; f) + + return maybe_codegen_scimlfn(expression, NonlinearFunction{iip, spec}, args; kwargs...) end @fallback_iip_specialize function SciMLBase.NonlinearProblem{iip, spec}( - sys::System, u0map, parammap = SciMLBase.NullParameters(); + sys::System, u0map, parammap = SciMLBase.NullParameters(); expression = Val{false}, check_length = true, check_compatibility = true, kwargs...) where {iip, spec} check_complete(sys, NonlinearProblem) check_compatibility && check_compatible_system(NonlinearProblem, sys) f, u0, p = process_SciMLProblem(NonlinearFunction{iip, spec}, sys, u0map, parammap; - check_length, check_compatibility, kwargs...) + check_length, check_compatibility, expression, kwargs...) kwargs = process_kwargs(sys; kwargs...) - # Call `remake` so it runs initialization if it is trivial - return remake(NonlinearProblem{iip}( - f, u0, p, StandardNonlinearProblem(); kwargs...)) + args = (; f, u0, p, ptype = StandardNonlinearProblem()) + + return maybe_codegen_scimlproblem(expression, NonlinearProblem{iip}, args; kwargs...) end @fallback_iip_specialize function SciMLBase.NonlinearLeastSquaresProblem{iip, spec}( sys::System, u0map, parammap = DiffEqBase.NullParameters(); check_length = false, - check_compatibility = true, kwargs...) where {iip, spec} + check_compatibility = true, expression = Val{false}, kwargs...) where {iip, spec} check_complete(sys, NonlinearLeastSquaresProblem) check_compatibility && check_compatible_system(NonlinearLeastSquaresProblem, sys) f, u0, p = process_SciMLProblem(NonlinearFunction{iip}, sys, u0map, parammap; - check_length, kwargs...) + check_length, expression, kwargs...) kwargs = process_kwargs(sys; kwargs...) - # Call `remake` so it runs initialization if it is trivial - return remake(NonlinearLeastSquaresProblem{iip}(f, u0, p; kwargs...)) + args = (; f, u0, p) + + return maybe_codegen_scimlproblem( + expression, NonlinearLeastSquaresProblem{iip}, args; kwargs...) end function check_compatible_system( From 7024bcf24067b47a0139935b7e0a11b43f959577 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Mon, 28 Apr 2025 19:49:48 +0530 Subject: [PATCH 149/185] feat: add `expression` kwarg to `OptimizationFunction`, `OptimizationProblem` --- src/problems/optimizationproblem.jl | 34 +++++++++++++++++------------ 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/src/problems/optimizationproblem.jl b/src/problems/optimizationproblem.jl index 241841e229..dae7c05243 100644 --- a/src/problems/optimizationproblem.jl +++ b/src/problems/optimizationproblem.jl @@ -7,25 +7,25 @@ function SciMLBase.OptimizationFunction{iip}(sys::System; sparse = false, cons_j = false, cons_h = false, cons_sparse = false, linenumbers = true, eval_expression = false, eval_module = @__MODULE__, simplify = false, check_compatibility = true, checkbounds = false, cse = true, - kwargs...) where {iip} + expression = Val{false}, kwargs...) where {iip} check_complete(sys, OptimizationFunction) check_compatibility && check_compatible_system(OptimizationFunction, sys) dvs = unknowns(sys) ps = parameters(sys) cstr = constraints(sys) - f = generate_cost(sys; expression = Val{false}, wrap_gfw = Val{true}, eval_expression, + f = generate_cost(sys; expression, wrap_gfw = Val{true}, eval_expression, eval_module, checkbounds, cse, kwargs...) if grad - _grad = generate_cost_gradient(sys; expression = Val{false}, wrap_gfw = Val{true}, + _grad = generate_cost_gradient(sys; expression, wrap_gfw = Val{true}, eval_expression, eval_module, checkbounds, cse, kwargs...) else _grad = nothing end if hess _hess, hess_prototype = generate_cost_hessian( - sys; expression = Val{false}, wrap_gfw = Val{true}, eval_expression, + sys; expression, wrap_gfw = Val{true}, eval_expression, eval_module, checkbounds, cse, sparse, simplify, return_sparsity = true, kwargs...) else @@ -35,11 +35,11 @@ function SciMLBase.OptimizationFunction{iip}(sys::System; cons = lcons = ucons = _cons_j = cons_jac_prototype = _cons_h = nothing cons_hess_prototype = cons_expr = nothing else - cons = generate_cons(sys; expression = Val{false}, wrap_gfw = Val{true}, + cons = generate_cons(sys; expression, wrap_gfw = Val{true}, eval_expression, eval_module, checkbounds, cse, kwargs...) if cons_j _cons_j, cons_jac_prototype = generate_constraint_jacobian( - sys; expression = Val{false}, wrap_gfw = Val{true}, eval_expression, + sys; expression, wrap_gfw = Val{true}, eval_expression, eval_module, checkbounds, cse, simplify, sparse = cons_sparse, return_sparsity = true, kwargs...) else @@ -47,7 +47,7 @@ function SciMLBase.OptimizationFunction{iip}(sys::System; end if cons_h _cons_h, cons_hess_prototype = generate_constraint_hessian( - sys; expression = Val{false}, wrap_gfw = Val{true}, eval_expression, + sys; expression, wrap_gfw = Val{true}, eval_expression, eval_module, checkbounds, cse, simplify, sparse = cons_sparse, return_sparsity = true, kwargs...) else @@ -58,9 +58,11 @@ function SciMLBase.OptimizationFunction{iip}(sys::System; obj_expr = subs_constants(cost(sys)) - observedfun = ObservedFunctionCache(sys; eval_expression, eval_module, checkbounds, cse) + observedfun = ObservedFunctionCache( + sys; expression, eval_expression, eval_module, checkbounds, cse) - return OptimizationFunction{iip}(f, SciMLBase.NoAD(); + args = (; f, ad = SciMLBase.NoAD()) + kwargs = (; sys = sys, grad = _grad, hess = _hess, @@ -73,6 +75,8 @@ function SciMLBase.OptimizationFunction{iip}(sys::System; cons_expr = cons_expr, expr = obj_expr, observed = observedfun) + + return maybe_codegen_scimlfn(expression, OptimizationFunction{iip}, args; kwargs...) end function SciMLBase.OptimizationProblem(sys::System, args...; kwargs...) @@ -80,13 +84,14 @@ function SciMLBase.OptimizationProblem(sys::System, args...; kwargs...) end function SciMLBase.OptimizationProblem{iip}( - sys::System, u0map, parammap = SciMLBase.NullParameters(); lb = nothing, ub = nothing, - check_compatibility = true, kwargs...) where {iip} + sys::System, u0map, parammap = SciMLBase.NullParameters(); lb = nothing, + ub = nothing, check_compatibility = true, expression = Val{false}, + kwargs...) where {iip} check_complete(sys, OptimizationProblem) check_compatibility && check_compatible_system(OptimizationProblem, sys) f, u0, p = process_SciMLProblem(OptimizationFunction{iip}, sys, u0map, parammap; - check_compatibility, tofloat = false, check_length = false, kwargs...) + check_compatibility, tofloat = false, check_length = false, expression, kwargs...) dvs = unknowns(sys) int = symtype.(unwrap.(dvs)) .<: Integer @@ -125,8 +130,9 @@ function SciMLBase.OptimizationProblem{iip}( end kwargs = process_kwargs(sys; kwargs...) - # Call `remake` so it runs initialization if it is trivial - return remake(OptimizationProblem{iip}(f, u0, p; lb, ub, int, lcons, ucons, kwargs...)) + kwargs = (; lb, ub, int, lcons, ucons, kwargs...) + args = (; f, u0, p) + return maybe_codegen_scimlproblem(expression, OptimizationProblem{iip}, args; kwargs...) end function check_compatible_system( From 54d7f6d9ae849bfc10d43837404b2f59a720184e Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Mon, 28 Apr 2025 19:53:38 +0530 Subject: [PATCH 150/185] feat: add `expression` kwarg to `ImplicitDiscreteFunction`, `ImplicitDiscreteProblem` --- src/problems/implicitdiscreteproblem.jl | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/src/problems/implicitdiscreteproblem.jl b/src/problems/implicitdiscreteproblem.jl index 730fdbf016..675fa50d8c 100644 --- a/src/problems/implicitdiscreteproblem.jl +++ b/src/problems/implicitdiscreteproblem.jl @@ -1,6 +1,6 @@ @fallback_iip_specialize function SciMLBase.ImplicitDiscreteFunction{iip, spec}( - sys::System; u0 = nothing, p = nothing, - t = nothing, eval_expression = false, eval_module = @__MODULE__, + sys::System; u0 = nothing, p = nothing, t = nothing, eval_expression = false, + eval_module = @__MODULE__, expression = Val{false}, checkbounds = false, analytic = nothing, simplify = false, cse = true, initialization_data = nothing, check_compatibility = true, kwargs...) where { iip, spec} @@ -10,7 +10,7 @@ iv = get_iv(sys) dvs = unknowns(sys) ps = parameters(sys) - f = generate_rhs(sys, dvs, ps; expression = Val{false}, wrap_gfw = Val{true}, + f = generate_rhs(sys, dvs, ps; expression, wrap_gfw = Val{true}, implicit_dae = true, eval_expression, eval_module, checkbounds = checkbounds, cse, kwargs...) @@ -22,29 +22,35 @@ end observedfun = ObservedFunctionCache( - sys; steady_state = false, eval_expression, eval_module, checkbounds, cse) + sys; steady_state = false, expression, eval_expression, eval_module, checkbounds, cse) - ImplicitDiscreteFunction{iip, spec}(f; + args = (; f) + kwargs = (; sys = sys, observed = observedfun, analytic = analytic, initialization_data) + + return maybe_codegen_scimlfn( + expression, ImplicitDiscreteFunction{iip, spec}, args; kwargs...) end @fallback_iip_specialize function SciMLBase.ImplicitDiscreteProblem{iip, spec}( sys::System, u0map, tspan, parammap = SciMLBase.NullParameters(); - check_compatibility = true, kwargs...) where {iip, spec} + check_compatibility = true, expression = Val{false}, kwargs...) where {iip, spec} check_complete(sys, ImplicitDiscreteProblem) check_compatibility && check_compatible_system(ImplicitDiscreteProblem, sys) dvs = unknowns(sys) f, u0, p = process_SciMLProblem( ImplicitDiscreteFunction{iip, spec}, sys, u0map, parammap; - t = tspan !== nothing ? tspan[1] : tspan, check_compatibility, kwargs...) + t = tspan !== nothing ? tspan[1] : tspan, check_compatibility, + expression, kwargs...) kwargs = process_kwargs(sys; kwargs...) - # Call `remake` so it runs initialization if it is trivial - return remake(ImplicitDiscreteProblem{iip}(f, u0, tspan, p; kwargs...)) + args = (; f, u0, tspan, p) + return maybe_codegen_scimlproblem( + expression, ImplicitDiscreteProblem{iip}, args; kwargs...) end function check_compatible_system( From 585fc80634da79488673e3d3d757a72d098ffe21 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Mon, 28 Apr 2025 20:03:56 +0530 Subject: [PATCH 151/185] feat: add `expression` kwarg to `IntervalNonlinearFunction`, `IntervalNonlinearProblem` --- src/problems/intervalnonlinearproblem.jl | 26 ++++++++++++++---------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/src/problems/intervalnonlinearproblem.jl b/src/problems/intervalnonlinearproblem.jl index fbea658d6c..9c9e5d8688 100644 --- a/src/problems/intervalnonlinearproblem.jl +++ b/src/problems/intervalnonlinearproblem.jl @@ -1,40 +1,44 @@ function SciMLBase.IntervalNonlinearFunction( - sys::System; u0 = nothing, p = nothing, - eval_expression = false, eval_module = @__MODULE__, - checkbounds = false, analytic = nothing, - cse = true, initialization_data = nothing, + sys::System; u0 = nothing, p = nothing, eval_expression = false, + eval_module = @__MODULE__, expression = Val{false}, checkbounds = false, + analytic = nothing, cse = true, initialization_data = nothing, check_compatibility = true, kwargs...) check_complete(sys, IntervalNonlinearFunction) check_compatibility && check_compatible_system(IntervalNonlinearFunction, sys) dvs = unknowns(sys) ps = parameters(sys) - f = generate_rhs(sys, dvs, ps; expression = Val{false}, wrap_gfw = Val{true}, + f = generate_rhs(sys, dvs, ps; expression, wrap_gfw = Val{true}, scalar = true, eval_expression, eval_module, checkbounds, cse, kwargs...) observedfun = ObservedFunctionCache( - sys; steady_state = false, eval_expression, eval_module, checkbounds, cse) + sys; steady_state = false, expression, eval_expression, eval_module, checkbounds, + cse) - IntervalNonlinearFunction{false}(f; + args = (; f) + kwargs = (; sys = sys, observed = observedfun, analytic = analytic, initialization_data) + + return maybe_codegen_scimlfn( + expression, IntervalNonlinearFunction{false}, args; kwargs...) end function SciMLBase.IntervalNonlinearProblem( sys::System, uspan::NTuple{2}, parammap = SciMLBase.NullParameters(); - check_compatibility = true, kwargs...) + check_compatibility = true, expression = Val{false}, kwargs...) check_complete(sys, IntervalNonlinearProblem) check_compatibility && check_compatible_system(IntervalNonlinearProblem, sys) u0map = unknowns(sys) .=> uspan[1] f, u0, p = process_SciMLProblem(IntervalNonlinearFunction, sys, u0map, parammap; - check_compatibility, kwargs...) + check_compatibility, expression, kwargs...) kwargs = process_kwargs(sys; kwargs...) - # Call `remake` so it runs initialization if it is trivial - return remake(IntervalNonlinearProblem(f, uspan, p; kwargs...)) + args = (; f, uspan, p) + return maybe_codegen_scimlproblem(expression, IntervalNonlinearProblem, args; kwargs...) end function check_compatible_system( From 7400e58cdf053187760762be1bf4c2701053f348 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Tue, 29 Apr 2025 15:23:56 +0530 Subject: [PATCH 152/185] fix: fix type-piracy of `Symbolics.rename` --- src/systems/abstractsystem.jl | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/systems/abstractsystem.jl b/src/systems/abstractsystem.jl index 509f0c438d..416707dbca 100644 --- a/src/systems/abstractsystem.jl +++ b/src/systems/abstractsystem.jl @@ -982,7 +982,7 @@ end end end -rename(x, name) = @set x.name = name +Symbolics.rename(x::AbstractSystem, name) = @set x.name = name function Base.propertynames(sys::AbstractSystem; private = false) if private @@ -2296,6 +2296,10 @@ function _named_idxs(name::Symbol, idxs, call; extra_args = "") end, $idxs)) end +function setname(x, name) + @set x.name = name +end + function single_named_expr(expr) name, call = split_assign(expr) if Meta.isexpr(name, :ref) @@ -2304,7 +2308,7 @@ function single_named_expr(expr) var = gensym(name) ex = quote $var = $(_named(name, call)) - $name = map(i -> $rename($var, Symbol($(Meta.quot(name)), :_, i)), $idxs) + $name = map(i -> $setname($var, Symbol($(Meta.quot(name)), :_, i)), $idxs) end ex else From 3efe756dbc8ba64e45894ae5bf5f15fc1361e556 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Tue, 29 Apr 2025 15:24:28 +0530 Subject: [PATCH 153/185] refactor: remove `systems/diffeqs/modelingtoolkitize.jl` --- src/ModelingToolkit.jl | 1 - 1 file changed, 1 deletion(-) diff --git a/src/ModelingToolkit.jl b/src/ModelingToolkit.jl index 8d96da7601..5e14cd32c1 100644 --- a/src/ModelingToolkit.jl +++ b/src/ModelingToolkit.jl @@ -184,7 +184,6 @@ include("systems/nonlinear/homotopy_continuation.jl") include("systems/nonlinear/modelingtoolkitize.jl") include("systems/nonlinear/initializesystem.jl") include("systems/diffeqs/first_order_transform.jl") -include("systems/diffeqs/modelingtoolkitize.jl") include("systems/diffeqs/basic_transformations.jl") include("systems/pde/pdesystem.jl") From 8005721bccf10f03012bbd4dfa77c22a48fbe0e5 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Tue, 29 Apr 2025 15:24:56 +0530 Subject: [PATCH 154/185] feat: add `modelingtoolkitize` for `ODEProblem` --- src/ModelingToolkit.jl | 3 + src/modelingtoolkitize/common.jl | 351 +++++++++++++++++++++++++++ src/modelingtoolkitize/odeproblem.jl | 59 +++++ 3 files changed, 413 insertions(+) create mode 100644 src/modelingtoolkitize/common.jl create mode 100644 src/modelingtoolkitize/odeproblem.jl diff --git a/src/ModelingToolkit.jl b/src/ModelingToolkit.jl index 5e14cd32c1..e63f588131 100644 --- a/src/ModelingToolkit.jl +++ b/src/ModelingToolkit.jl @@ -178,6 +178,9 @@ include("problems/initializationproblem.jl") include("problems/jumpproblem.jl") include("problems/optimizationproblem.jl") +include("modelingtoolkitize/common.jl") +include("modelingtoolkitize/odeproblem.jl") + include("systems/optimization/modelingtoolkitize.jl") include("systems/nonlinear/homotopy_continuation.jl") diff --git a/src/modelingtoolkitize/common.jl b/src/modelingtoolkitize/common.jl new file mode 100644 index 0000000000..d2f494b1f1 --- /dev/null +++ b/src/modelingtoolkitize/common.jl @@ -0,0 +1,351 @@ + +""" + $(TYPEDSIGNATURES) + +Check if the length of variables `vars` matches the number of names for those variables, +given by `names`. `is_unknowns` denotes whether the variable are unknowns or parameters. +""" +function varnames_length_check(vars::Array, names::Array; is_unknowns = false) + length(names) == length(vars) || return + throw(ArgumentError(""" + Number of $(is_unknowns ? "unknowns" : "parameters") ($(length(vars))) \ + does not match number of names ($(length(names))). + """)) +end + +""" + $(TYPEDSIGNATURES) + +Define a subscripted time-dependent variable with name `x` and subscript `i`. Equivalent +to `@variables \$name(..)`. `T` is the desired symtype of the variable when called with +the independent variable. +""" +_defvaridx(x, i; T = Real) = variable(x, i, T = SymbolicUtils.FnType{Tuple, T}) +""" + $(TYPEDSIGNATURES) + +Define a time-dependent variable with name `x`. Equivalent to `@variables \$x(..)`. +`T` is the desired symtype of the variable when called with the independent variable. +""" +_defvar(x; T = Real) = variable(x, T = SymbolicUtils.FnType{Tuple, T}) + +""" + $(TYPEDSIGNATURES) + +Define an array of symbolic unknowns of the appropriate type and size for `u` with +independent variable `t`. +""" +function define_vars(u, t) + [_defvaridx(:x, i)(t) for i in eachindex(u)] +end + +""" + $(TYPEDSIGNATURES) + +Return a symbolic state for the given proble `prob.`. `t` is the independent variable. +`u_names` optionally contains the names to use for the created symbolic variables. +""" +function construct_vars(prob, t, u_names = nothing) + # construct `_vars`, AbstractSciMLFunction, AbstractSciMLFunction, a list of MTK variables for `prob.u0`. + if u_names !== nothing + # explicitly provided names + varnames_length_check(state_values(prob), u_names; is_unknowns = true) + _vars = [_defvar(name)(t) for name in u_names] + elseif SciMLBase.has_sys(prob.f) + # get names from the system + varnames = getname.(variable_symbols(prob.f.sys)) + varidxs = variable_index.((prob.f.sys,), varnames) + invpermute!(varnames, varidxs) + _vars = [_defvar(name)(t) for name in varnames] + else + # auto-generate names + _vars = define_vars(state_values(prob), t) + end + + # Handle different types of arrays + return prob.u0 isa Number ? _vars : ArrayInterface.restructure(prob.u0, _vars) +end + +""" + $(METHODLIST) + +Define symbolic names for each value in parameter object `p`. `t` is the independent +variable of the system. `names` is a collection mapping indexes of `p` to their +names, or `nothing` to automatically generate names. + +The returned value has the same structure as `p`, but symbolic variables instead of +values. +""" +function define_params(p, t, _ = nothing) + throw(ModelingtoolkitizeParametersNotSupportedError(typeof(p))) +end + +function define_params(p::AbstractArray, t, names = nothing) + if names === nothing + [toparam(variable(:α, i)) for i in eachindex(p)] + else + varnames_length_check(p, names) + [toparam(variable(names[i])) for i in eachindex(p)] + end +end + +function define_params(p::Number, t, names = nothing) + if names === nothing + [toparam(variable(:α))] + elseif names isa Union{AbstractArray, AbstractDict} + varnames_length_check(p, names) + [toparam(variable(names[i])) for i in eachindex(p)] + else + [toparam(variable(names))] + end +end + +function define_params(p::AbstractDict, t, names = nothing) + if names === nothing + OrderedDict(k => toparam(variable(:α, i)) for (i, k) in zip(1:length(p), keys(p))) + else + varnames_length_check(p, names) + OrderedDict(k => toparam(variable(names[k])) for k in keys(p)) + end +end + +function define_params(p::Tuple, t, names = nothing) + if names === nothing + tuple((toparam(variable(:α, i)) for i in eachindex(p))...) + else + varnames_length_check(p, names) + tuple((toparam(variable(names[i])) for i in eachindex(p))...) + end +end + +function define_params(p::NamedTuple, t, names = nothing) + if names === nothing + NamedTuple(x => toparam(variable(x)) for x in keys(p)) + else + varnames_length_check(p, names) + NamedTuple(x => toparam(variable(names[x])) for x in keys(p)) + end +end + +function define_params(p::MTKParameters, t, names = nothing) + if names === nothing + ps = [] + i = 1 + # tunables are all treated as scalar reals + for x in p.tunable + push!(ps, toparam(variable(:α, i))) + i += 1 + end + # ignore initials + # discretes should be time-dependent + for buf in p.discrete + T = eltype(buf) + for val in buf + # respect array sizes + shape = val isa AbstractArray ? axes(val) : nothing + push!(ps, declare_timevarying_parameter(:α, i, t; T, shape)) + i += 1 + end + end + # handle constants + for buf in p.constant + T = eltype(buf) + for val in buf + # respect array sizes + shape = val isa AbstractArray ? axes(val) : nothing + push!(ps, declare_parameter(:α, i; T, shape)) + i += 1 + end + end + # handle nonnumerics + for buf in p.nonnumeric + T = eltype(buf) + for val in buf + # respect array sizes + shape = val isa AbstractArray ? axes(val) : nothing + push!(ps, declare_parameter(:α, i; T, shape)) + i += 1 + end + end + return identity.(ps) + else + new_p = as_any_buffer(p) + for (k, v) in names + val = p[k] + shape = val isa AbstractArray ? axes(val) : nothing + T = typeof(val) + if val.portion == SciMLStructures.Initials() + continue + end + if val.portion == SciMLStructures.Tunable() + T = Real + end + if val.portion == SciMLStructures.Discrete() + var = declare_timevarying_parameter(v, nothing, t; T, shape) + else + var = declare_parameter(v, nothing; T, shape) + end + new_p[k] = var + end + return reduce(vcat, new_p; init = []) + end +end + +""" + $(TYPEDSIGNATURES) + +Create a time-varying parameter with name `x`, subscript `i`, independent variable `t` +which stores values of type `T`. `shape` denotes the shape of array values, or `nothing` +for scalars. + +To ignore the subscript, pass `nothing` for `i`. +""" +function declare_timevarying_parameter(x::Symbol, i, t; T, shape = nothing) + # turn specific floating point numbers to `Real` + if T <: Union{AbstractFloat, ForwardDiff.Dual} + T = Real + end + if T <: Array{<:Union{AbstractFloat, ForwardDiff.Dual}, N} where {N} + T = Array{Real, ndims(T)} + end + + if i === nothing + var = _defvar(x; T) + else + var = _defvaridx(x, i; T) + end + var = toparam(unwrap(var(t))) + if shape !== nothing + var = setmetadata(var, Symbolics.ArrayShapeCtx, shape) + end + return var +end + +""" + $(TYPEDSIGNATURES) + +Create a time-varying parameter with name `x` and subscript `i`, which stores values of +type `T`. `shape` denotes the shape of array values, or `nothing` for scalars. + +To ignore the subscript, pass `nothing` for `i`. +""" +function declare_parameter(x::Symbol, i; T, shape = nothing) + # turn specific floating point numbers to `Real` + if T <: Union{AbstractFloat, ForwardDiff.Dual} + T = Real + end + if T <: Array{<:Union{AbstractFloat, ForwardDiff.Dual}, N} where {N} + T = Array{Real, ndims(T)} + end + + i = i === nothing ? () : (i,) + var = toparam(unwrap(variable(:α, i...; T))) + if shape !== nothing + var = setmetadata(var, Symbolics.ArrayShapeCtx, shape) + end + return var +end + +""" + $(TYPEDSIGNATURES) + +Return a symbolic parameter object for the given proble `prob.`. `t` is the independent +variable. `p_names` optionally contains the names to use for the created symbolic +variables. +""" +function construct_params(prob, t, p_names = nothing) + p = parameter_values(prob) + has_p = !(p isa Union{DiffEqBase.NullParameters, Nothing}) + + # Get names of parameters + if has_p + if p_names === nothing && SciMLBase.has_sys(prob.f) + # get names from the system + p_names = Dict(parameter_index(prob.f.sys, sym) => sym + for sym in parameter_symbols(prob.f.sys)) + end + params = define_params(p, p_names) + if p isa Number + params = params[1] + elseif p isa AbstractArray + params = ArrayInterface.restructure(p, params) + end + else + params = [] + end + + return params +end + +""" + $(TYPEDSIGNATURES) + +Given the differential operator `D`, mass matrix `mm` and ordered list of unknowns `vars`, +return the list of +""" +function lhs_from_mass_matrix(D, mm, vars) + var_set = Set(vars) + # calculate equation LHS from mass matrix + if mm === I + lhs = map(v -> D(v), vars) + else + lhs = map(mm * vars) do v + if iszero(v) + 0 + elseif v in var_set + D(v) + else + error("Non-permutation mass matrix is not supported.") + end + end + end + return lhs +end + +""" + $(TYPEDSIGNATURES) + +Given a problem `prob`, the symbolic unknowns and params and the independent variable, +trace through `prob.f` and return the resultant expression. +""" +function trace_rhs(prob, vars, params, t) + # trace prob.f to get equation RHS + if SciMLBase.isinplace(prob.f) + rhs = ArrayInterface.restructure(prob.u0, similar(vars, Num)) + fill!(rhs, 0) + if prob.f isa SciMLBase.AbstractSciMLFunction && + prob.f.f isa FunctionWrappersWrappers.FunctionWrappersWrapper + prob.f.f.fw[1].obj[](rhs, vars, params, t) + else + prob.f(rhs, vars, params, t) + end + else + rhs = prob.f(vars, params, t) + end + return rhs +end + +""" + $(TYPEDSIGNATURES) + +Obtain default values for unknowns `vars` and parameters `paramvec` +given the problem `prob` and symbolic parameter object `paramobj`. +""" +function defaults_from_u0_p(prob, vars, paramobj, paramvec) + u0 = state_values(prob) + p = parameter_values(prob) + defaults = Dict{Any, Any}(vec(vars) .=> vec(collect(u0))) + if !(p isa Union{SciMLBase.NullParameters, Nothing}) + if p isa Union{NamedTuple, AbstractDict} + merge!(defaults, Dict(v => p[k] for (k, v) in pairs(paramobj))) + elseif p isa MTKParameters + pvals = [p.tunable; reduce(vcat, p.discrete; init = []); + reduce(vcat, p.constant; init = []); + reduce(vcat, p.nonnumeric; init = [])] + merge!(defaults, Dict(paramvec .=> pvals)) + else + merge!(defaults, Dict(paramvec .=> vec(collect(p)))) + end + end + return defaults +end diff --git a/src/modelingtoolkitize/odeproblem.jl b/src/modelingtoolkitize/odeproblem.jl new file mode 100644 index 0000000000..b130a21d69 --- /dev/null +++ b/src/modelingtoolkitize/odeproblem.jl @@ -0,0 +1,59 @@ +""" + $(TYPEDSIGNATURES) + +Convert an `ODEProblem` to a `ModelingToolkit.System`. + +# Keyword arguments + +- `u_names`: An array of names of the same size as `prob.u0` to use as the names of the + unknowns of the system. The names should be given as `Symbol`s. +- `p_names`: A collection of names to use for parameters of the system. The collection + should have keys corresponding to indexes of `prob.p`. For example, if `prob.p` is an + associative container like `NamedTuple`, then `p_names` should map keys of `prob.p` to + the name that the corresponding parameter should have in the returned system. The names + should be given as `Symbol`s. +- INTERNAL `return_symbolic_u0_p`: Also return the symbolic state and parameter objects. + +All other keyword arguments are forwarded to the created `System`. +""" +function modelingtoolkitize(prob::ODEProblem; u_names = nothing, p_names = nothing, + return_symbolic_u0_p = false, kwargs...) + if prob.f isa DiffEqBase.AbstractParameterizedFunction + return prob.f.sys + end + + t = t_nounits + p = prob.p + has_p = !(p isa Union{DiffEqBase.NullParameters, Nothing}) + + vars = construct_vars(prob, t, u_names) + params = construct_params(prob, t, p_names) + + lhs = lhs_from_mass_matrix(D_nounits, prob.f.mass_matrix, vars) + rhs = trace_rhs(prob, vars, params, t) + eqs = vcat([lhs[i] ~ rhs[i] for i in eachindex(prob.u0)]...) + + sts = vec(collect(vars)) + + # turn `params` into a list of symbolic variables as opposed to + # a parameter object containing symbolic variables. + _params = params + params = vec(collect(values(params))) + + defaults = defaults_from_u0_p(prob, vars, _params, params) + # In case initials crept in, specifically from when we constructed parameters + # using prob.f.sys + filter!(x -> !iscall(x) || !(operation(x) isa Initial), params) + filter!(x -> !iscall(x[1]) || !(operation(x[1]) isa Initial), defaults) + + sys = System(eqs, t, sts, params; + defaults, + name = gensym(:MTKizedODE), + kwargs...) + + if return_symbolic_u0_p + return sys, vars, _params + else + return sys + end +end From 3a6a460a7fd362fb58276d94d98da76c68ad6fb3 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Tue, 29 Apr 2025 15:25:04 +0530 Subject: [PATCH 155/185] feat: add `modelingtoolkitize` for `SDEProblem` --- src/ModelingToolkit.jl | 1 + src/modelingtoolkitize/sdeproblem.jl | 53 ++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+) create mode 100644 src/modelingtoolkitize/sdeproblem.jl diff --git a/src/ModelingToolkit.jl b/src/ModelingToolkit.jl index e63f588131..6e80a61bdc 100644 --- a/src/ModelingToolkit.jl +++ b/src/ModelingToolkit.jl @@ -180,6 +180,7 @@ include("problems/optimizationproblem.jl") include("modelingtoolkitize/common.jl") include("modelingtoolkitize/odeproblem.jl") +include("modelingtoolkitize/sdeproblem.jl") include("systems/optimization/modelingtoolkitize.jl") diff --git a/src/modelingtoolkitize/sdeproblem.jl b/src/modelingtoolkitize/sdeproblem.jl new file mode 100644 index 0000000000..ff8c238559 --- /dev/null +++ b/src/modelingtoolkitize/sdeproblem.jl @@ -0,0 +1,53 @@ +""" + $(TYPEDSIGNATURES) + +Convert an `SDEProblem` to a `ModelingToolkit.System`. + +# Keyword arguments + +- `u_names`: an array of names of the same size as `prob.u0` to use as the names of the + unknowns of the system. The names should be given as `Symbol`s. +- `p_names`: a collection of names to use for parameters of the system. The collection + should have keys corresponding to indexes of `prob.p`. For example, if `prob.p` is an + associative container like `NamedTuple`, then `p_names` should map keys of `prob.p` to + the name that the corresponding parameter should have in the returned system. The names + should be given as `Symbol`s. + +All other keyword arguments are forwarded to the created `System`. +""" +function modelingtoolkitize( + prob::SDEProblem; u_names = nothing, p_names = nothing, kwargs...) + if prob.f isa DiffEqBase.AbstractParameterizedFunction + return prob.f.sys + end + + # just create the equivalent ODEProblem, `modelingtoolkitize` that + # and add on the noise + odefn = ODEFunction{SciMLBase.isinplace(prob)}( + prob.f.f; mass_matrix = prob.f.mass_matrix, sys = prob.f.sys) + odeprob = ODEProblem(odefn, prob.u0, prob.tspan, prob.p) + sys, vars, params = modelingtoolkitize( + odeprob; u_names, p_names, return_symbolic_u0_p = true, + name = gensym(:MTKizedSDE), kwargs...) + t = get_iv(sys) + + if SciMLBase.isinplace(prob) + if SciMLBase.is_diagonal_noise(prob) + neqs = similar(vars, Any) + prob.g(neqs, vars, params, t) + else + neqs = similar(prob.noise_rate_prototype, Any) + prob.g(neqs, vars, params, t) + end + else + if SciMLBase.is_diagonal_noise(prob) + neqs = prob.g(vars, params, t) + else + neqs = prob.g(vars, params, t) + end + end + + @set! sys.noise_eqs = neqs + + return sys +end From b749d26bb24c8b90cfa014ceebe798bf4bc2404c Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Tue, 29 Apr 2025 16:05:33 +0530 Subject: [PATCH 156/185] feat: add `add_accumulations` --- src/ModelingToolkit.jl | 2 +- src/systems/diffeqs/basic_transformations.jl | 38 ++++++++++++++++++++ test/basic_transformations.jl | 22 ++++++++++++ 3 files changed, 61 insertions(+), 1 deletion(-) diff --git a/src/ModelingToolkit.jl b/src/ModelingToolkit.jl index 6e80a61bdc..cdfb9ba135 100644 --- a/src/ModelingToolkit.jl +++ b/src/ModelingToolkit.jl @@ -296,7 +296,7 @@ export isinput, isoutput, getbounds, hasbounds, getguess, hasguess, isdisturbanc hasunit, getunit, hasconnect, getconnect, hasmisc, getmisc, state_priority export ode_order_lowering, dae_order_lowering, liouville_transform, - change_independent_variable, substitute_component + change_independent_variable, substitute_component, add_accumulations export PDESystem export Differential, expand_derivatives, @derivatives export Equation, ConstrainedEquation diff --git a/src/systems/diffeqs/basic_transformations.jl b/src/systems/diffeqs/basic_transformations.jl index 55eb6cc31a..0ef22fa982 100644 --- a/src/systems/diffeqs/basic_transformations.jl +++ b/src/systems/diffeqs/basic_transformations.jl @@ -361,3 +361,41 @@ function Girsanov_transform(sys::System, u; θ0 = 1.0) @set! sys.observed = [weight ~ θ / θ0; obs] return sys end + +""" + $(TYPEDSIGNATURES) + +Add accumulation variables for `vars`. For every unknown `x` in `vars`, add +`D(accumulation_x) ~ x` as an equation. +""" +function add_accumulations(sys::System, vars = unknowns(sys)) + avars = [rename(v, Symbol(:accumulation_, getname(v))) for v in vars] + return add_accumulations(sys, avars .=> vars) +end + +""" + $(TYPEDSIGNATURES) + +Add accumulation variables for `vars`. `vars` is a vector of pairs in the form +of + +```julia +[cumulative_var1 => x + y, cumulative_var2 => x^2] +``` +Then, cumulative variables `cumulative_var1` and `cumulative_var2` that computes +the cumulative `x + y` and `x^2` would be added to `sys`. + +All accumulation variables have a default of zero. +""" +function add_accumulations(sys::System, vars::Vector{<:Pair}) + eqs = get_eqs(sys) + avars = map(first, vars) + if (ints = intersect(avars, unknowns(sys)); !isempty(ints)) + error("$ints already exist in the system!") + end + D = Differential(get_iv(sys)) + @set! sys.eqs = [eqs; Equation[D(a) ~ v[2] for (a, v) in zip(avars, vars)]] + @set! sys.unknowns = [get_unknowns(sys); avars] + @set! sys.defaults = merge(get_defaults(sys), Dict(a => 0.0 for a in avars)) + return sys +end diff --git a/test/basic_transformations.jl b/test/basic_transformations.jl index 7e83dd24e4..a7dc13de21 100644 --- a/test/basic_transformations.jl +++ b/test/basic_transformations.jl @@ -215,3 +215,25 @@ end M = System([D(x(t)) ~ x(t - 1)], t; name = :M) @test_throws "DDE" change_independent_variable(M, x(t)) end + +@testset "`add_accumulations`" begin + @parameters a + @variables x(t) y(t) z(t) + @named sys = System([D(x) ~ y, 0 ~ x + z, 0 ~ x - y], t, [z, y, x], []) + asys = add_accumulations(sys) + @variables accumulation_x(t) accumulation_y(t) accumulation_z(t) + eqs = [0 ~ x + z + 0 ~ x - y + D(accumulation_x) ~ x + D(accumulation_y) ~ y + D(accumulation_z) ~ z + D(x) ~ y] + @test issetequal(equations(asys), eqs) + @variables ac(t) + asys = add_accumulations(sys, [ac => (x + y)^2]) + eqs = [0 ~ x + z + 0 ~ x - y + D(ac) ~ (x + y)^2 + D(x) ~ y] + @test issetequal(equations(asys), eqs) +end From 3ed5385d3dc94baf5faacef99c8d3baeb9e68b3d Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Tue, 29 Apr 2025 16:26:49 +0530 Subject: [PATCH 157/185] fix: allow generating callbacks for `System` --- src/systems/callbacks.jl | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/systems/callbacks.jl b/src/systems/callbacks.jl index 7d542d9bd0..9c0141b702 100644 --- a/src/systems/callbacks.jl +++ b/src/systems/callbacks.jl @@ -711,7 +711,7 @@ function compile_affect(eqs::Vector{Equation}, cb, sys, dvs, ps; outputidxs = no end end -function generate_rootfinding_callback(sys::AbstractTimeDependentSystem, +function generate_rootfinding_callback(sys::AbstractSystem, dvs = unknowns(sys), ps = parameters(sys; initial_parameters = true); kwargs...) cbs = continuous_events(sys) isempty(cbs) && return nothing @@ -722,7 +722,7 @@ Generate a single rootfinding callback; this happens if there is only one equati generate_rootfinding_callback and thus we can produce a ContinuousCallback instead of a VectorContinuousCallback. """ function generate_single_rootfinding_callback( - eq, cb, sys::AbstractTimeDependentSystem, dvs = unknowns(sys), + eq, cb, sys::AbstractSystem, dvs = unknowns(sys), ps = parameters(sys; initial_parameters = true); kwargs...) if !isequal(eq.lhs, 0) eq = 0 ~ eq.lhs - eq.rhs @@ -765,7 +765,7 @@ function generate_single_rootfinding_callback( end function generate_vector_rootfinding_callback( - cbs, sys::AbstractTimeDependentSystem, dvs = unknowns(sys), + cbs, sys::AbstractSystem, dvs = unknowns(sys), ps = parameters(sys; initial_parameters = true); rootfind = SciMLBase.RightRootFind, reinitialization = SciMLBase.CheckInit(), kwargs...) eqs = map(cb -> flatten_equations(cb.eqs), cbs) @@ -866,7 +866,7 @@ end """ Compile a single continuous callback affect function(s). """ -function compile_affect_fn(cb, sys::AbstractTimeDependentSystem, dvs, ps, kwargs) +function compile_affect_fn(cb, sys::AbstractSystem, dvs, ps, kwargs) eq_aff = affects(cb) eq_neg_aff = affect_negs(cb) affect = compile_affect(eq_aff, cb, sys, dvs, ps; expression = Val{false}, kwargs...) @@ -890,8 +890,11 @@ function compile_affect_fn(cb, sys::AbstractTimeDependentSystem, dvs, ps, kwargs (affect = affect, affect_neg = affect_neg, initialize = initialize, finalize = finalize) end -function generate_rootfinding_callback(cbs, sys::AbstractTimeDependentSystem, +function generate_rootfinding_callback(cbs, sys::AbstractSystem, dvs = unknowns(sys), ps = parameters(sys; initial_parameters = true); kwargs...) + if !is_time_dependent(sys) + throw(MethodError(generate_rootfinding_callback, (cbs, sys, dvs, ps))) + end eqs = map(cb -> flatten_equations(cb.eqs), cbs) num_eqs = length.(eqs) total_eqs = sum(num_eqs) From 9f9aa1f144dd947885086b66e48b8d5063369588 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Tue, 29 Apr 2025 16:26:58 +0530 Subject: [PATCH 158/185] feat: add `preface` to `System` --- src/systems/system.jl | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/systems/system.jl b/src/systems/system.jl index e4386bc997..56420fd20b 100644 --- a/src/systems/system.jl +++ b/src/systems/system.jl @@ -48,6 +48,7 @@ struct System <: AbstractSystem index_cache::Union{Nothing, IndexCache} ignored_connections::Union{ Nothing, Tuple{Vector{IgnoredAnalysisPoint}, Vector{IgnoredAnalysisPoint}}} + preface::Any parent::Union{Nothing, System} initializesystem::Union{Nothing, System} is_initializesystem::Bool @@ -62,8 +63,9 @@ struct System <: AbstractSystem metadata = nothing, gui_metadata = nothing, is_dde = false, tstops = [], tearing_state = nothing, namespacing = true, complete = false, index_cache = nothing, ignored_connections = nothing, - parent = nothing, initializesystem = nothing, is_initializesystem = false, - isscheduled = false, schedule = nothing; checks::Union{Bool, Int} = true) + preface = nothing, parent = nothing, initializesystem = nothing, + is_initializesystem = false, isscheduled = false, schedule = nothing; + checks::Union{Bool, Int} = true) if is_initializesystem && iv !== nothing throw(ArgumentError(""" Expected initialization system to be time-independent. Found independent @@ -96,7 +98,7 @@ struct System <: AbstractSystem guesses, systems, initialization_eqs, continuous_events, discrete_events, connector_type, assertions, metadata, gui_metadata, is_dde, tstops, tearing_state, namespacing, complete, index_cache, ignored_connections, - parent, initializesystem, is_initializesystem, isscheduled, schedule) + preface, parent, initializesystem, is_initializesystem, isscheduled, schedule) end end @@ -114,7 +116,8 @@ function System(eqs::Vector{Equation}, iv, dvs, ps, brownians = []; metadata = nothing, gui_metadata = nothing, is_dde = nothing, tstops = [], tearing_state = nothing, ignored_connections = nothing, parent = nothing, description = "", name = nothing, discover_from_metadata = true, - initializesystem = nothing, is_initializesystem = false, checks = true) + initializesystem = nothing, is_initializesystem = false, preface = [], + checks = true) name === nothing && throw(NoNameError()) iv = unwrap(iv) @@ -181,7 +184,7 @@ function System(eqs::Vector{Equation}, iv, dvs, ps, brownians = []; costs, consolidate, dvs, ps, brownians, iv, observed, parameter_dependencies, var_to_name, name, description, defaults, guesses, systems, initialization_eqs, continuous_events, discrete_events, connector_type, assertions, metadata, gui_metadata, is_dde, - tstops, tearing_state, true, false, nothing, ignored_connections, parent, + tstops, tearing_state, true, false, nothing, ignored_connections, preface, parent, initializesystem, is_initializesystem; checks) end From ad26dfbea1611ff18f79e9d236e72ab678629d81 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Tue, 29 Apr 2025 16:27:10 +0530 Subject: [PATCH 159/185] test: fix `odesystem` tests --- test/odesystem.jl | 166 +++++++++++++--------------------------------- 1 file changed, 47 insertions(+), 119 deletions(-) diff --git a/test/odesystem.jl b/test/odesystem.jl index b2b2d519a3..50813bfc5f 100644 --- a/test/odesystem.jl +++ b/test/odesystem.jl @@ -92,29 +92,29 @@ f.f(du, u, p, 0.1) @test_throws ArgumentError f.f(u, p, 0.1) #check iip -f = eval(ODEFunctionExpr(de, [x, y, z], [σ, ρ, β])) -f2 = ODEFunction(de, [x, y, z], [σ, ρ, β]) +f = eval(ODEFunction(de; expression = Val{true})) +f2 = ODEFunction(de) @test SciMLBase.isinplace(f) === SciMLBase.isinplace(f2) @test SciMLBase.specialization(f) === SciMLBase.specialization(f2) for iip in (true, false) - f = eval(ODEFunctionExpr{iip}(de, [x, y, z], [σ, ρ, β])) - f2 = ODEFunction{iip}(de, [x, y, z], [σ, ρ, β]) + f = eval(ODEFunction{iip}(de; expression = Val{true})) + f2 = ODEFunction{iip}(de) @test SciMLBase.isinplace(f) === SciMLBase.isinplace(f2) === iip @test SciMLBase.specialization(f) === SciMLBase.specialization(f2) for specialize in (SciMLBase.AutoSpecialize, SciMLBase.FullSpecialize) - f = eval(ODEFunctionExpr{iip, specialize}(de, [x, y, z], [σ, ρ, β])) - f2 = ODEFunction{iip, specialize}(de, [x, y, z], [σ, ρ, β]) + f = eval(ODEFunction{iip, specialize}(de; expression = Val{true})) + f2 = ODEFunction{iip, specialize}(de) @test SciMLBase.isinplace(f) === SciMLBase.isinplace(f2) === iip @test SciMLBase.specialization(f) === SciMLBase.specialization(f2) === specialize end end #check sparsity -f = eval(ODEFunctionExpr(de, [x, y, z], [σ, ρ, β], sparsity = true)) +f = eval(ODEFunction(de, sparsity = true, expression = Val{true})) @test f.sparsity == ModelingToolkit.jacobian_sparsity(de) -f = eval(ODEFunctionExpr(de, [x, y, z], [σ, ρ, β], sparsity = false)) +f = eval(ODEFunction(de, sparsity = false, expression = Val{true})) @test isnothing(f.sparsity) eqs = [D(x) ~ σ * (y - x), @@ -137,9 +137,9 @@ tgrad_iip(du, u, p, t) eqs = [D(x) ~ σ(t - 1) * (y - x), D(y) ~ x * (ρ - z) - y, D(z) ~ x * y - β * z * κ] -@named de = System(eqs, t) +@named de = System(eqs, t, [x, y, z], [σ, ρ, β]) test_diffeq_inference("single internal iv-varying", de, t, (x, y, z), (σ, ρ, β)) -f = generate_rhs(de, [x, y, z], [σ, ρ, β], expression = Val{false}) +f = generate_rhs(de, expression = Val{false}, wrap_gfw = Val{true}) du = [0.0, 0.0, 0.0] f(du, [1.0, 2.0, 3.0], [x -> x + 7, 2, 3], 5.0) @test du ≈ [11, -3, -7] @@ -147,7 +147,7 @@ f(du, [1.0, 2.0, 3.0], [x -> x + 7, 2, 3], 5.0) eqs = [D(x) ~ x + 10σ(t - 1) + 100σ(t - 2) + 1000σ(t^2)] @named de = System(eqs, t) test_diffeq_inference("many internal iv-varying", de, t, (x,), (σ,)) -f = generate_rhs(de, [x], [σ], expression = Val{false}) +f = generate_rhs(de, [x], [σ], expression = Val{false}, wrap_gfw = Val{true}) du = [0.0] f(du, [1.0], [t -> t + 2], 5.0) @test du ≈ [27561] @@ -198,7 +198,7 @@ eqs = [D(x) ~ -A * x, @named de = System(eqs, t) @test begin local f, du - f = generate_rhs(de, [x, y], [A, B, C], expression = Val{false}) + f = generate_rhs(de, [x, y], [A, B, C], expression = Val{false}, wrap_gfw = Val{true}) du = [0.0, 0.0] f(du, [1.0, 2.0], [1, 2, 3], 0.0) du ≈ [-1, -1 / 3] @@ -294,7 +294,7 @@ sol_dpmap = solve(prob_dpmap, Rodas5()) sys = makecombinedsys() @unpack sys1, b = sys - prob = ODEProblem(sys, Pair[]) + prob = ODEProblem(sys, Pair[], (0.0, 1.0)) prob_new = SciMLBase.remake(prob, p = Dict(sys1.a => 3.0, b => 4.0), u0 = Dict(sys1.x => 1.0)) @test prob_new.p isa MTKParameters @@ -321,7 +321,7 @@ du0 = [D(y₁) => -0.04 D(y₂) => 0.04 D(y₃) => 0.0] prob4 = DAEProblem(sys, du0, u0, tspan, p2) -prob5 = eval(DAEProblemExpr(sys, du0, u0, tspan, p2)) +prob5 = eval(DAEProblem(sys, du0, u0, tspan, p2; expression = Val{true})) for prob in [prob4, prob5] local sol @test prob.differential_vars == [true, true, false] @@ -335,42 +335,27 @@ eqs = [D(x) ~ σ * (y - x), D(y) ~ x - β * y, x + z ~ y] @named sys = System(eqs, t) -@test all(isequal.(unknowns(sys), [x, y, z])) -@test all(isequal.(parameters(sys), [σ, β])) +@test issetequal(unknowns(sys), [x, y, z]) +@test issetequal(parameters(sys), [σ, β]) @test equations(sys) == eqs @test ModelingToolkit.isautonomous(sys) -# issue 701 -using ModelingToolkit -@parameters a -@variables x(t) -@named sys = System([D(x) ~ a], t) -@test issym(equations(sys)[1].rhs) +@testset "Issue#701: `collect_vars!` handles non-call symbolics" begin + @parameters a + @variables x(t) + @named sys = System([D(x) ~ a], t) + @test issym(equations(sys)[1].rhs) +end -# issue 708 -@parameters a -@variables x(t) y(t) z(t) -@named sys = System([D(x) ~ y, 0 ~ x + z, 0 ~ x - y], t, [z, y, x], []) -asys = add_accumulations(sys) -@variables accumulation_x(t) accumulation_y(t) accumulation_z(t) -eqs = [0 ~ x + z - 0 ~ x - y - D(accumulation_x) ~ x - D(accumulation_y) ~ y - D(accumulation_z) ~ z - D(x) ~ y] -@test ssort(equations(asys)) == ssort(eqs) -@variables ac(t) -asys = add_accumulations(sys, [ac => (x + y)^2]) -eqs = [0 ~ x + z - 0 ~ x - y - D(ac) ~ (x + y)^2 - D(x) ~ y] -@test ssort(equations(asys)) == ssort(eqs) - -sys2 = ode_order_lowering(sys) -M = ModelingToolkit.calculate_massmatrix(sys2) -@test M == Diagonal([1, 0, 0]) +@testset "Issue#708" begin + @parameters a + @variables x(t) y(t) z(t) + @named sys = System([D(x) ~ y, 0 ~ x + z, 0 ~ x - y], t, [z, y, x], []) + + sys2 = ode_order_lowering(sys) + M = ModelingToolkit.calculate_massmatrix(sys2) + @test M == Diagonal([1, 0, 0]) +end # issue #609 @variables x1(t) x2(t) @@ -403,7 +388,8 @@ eq = D(x) ~ r * x sys1 = makesys(:sys1) sys2 = makesys(:sys1) - @test_throws ArgumentError System([sys2.f ~ sys1.x, D(sys1.f) ~ 0], t, + @test_throws ModelingToolkit.NonUniqueSubsystemsError System( + [sys2.f ~ sys1.x, D(sys1.f) ~ 0], t, systems = [sys1, sys2], name = :foo) end issue808() @@ -435,14 +421,14 @@ eqs = [D(D(x)) ~ -b / M * D(x) - k / M * x] ps = [M, b, k] default_u0 = [D(x) => 0.0, x => 10.0] default_p = [M => 1.0, b => 1.0, k => 1.0] -@named sys = System(eqs, t, [x], ps; defaults = [default_u0; default_p], tspan) +@named sys = System(eqs, t, [x], ps; defaults = [default_u0; default_p]) sys = ode_order_lowering(sys) sys = complete(sys) -prob = ODEProblem(sys) +prob = ODEProblem(sys, nothing, tspan) sol = solve(prob, Tsit5()) @test sol.t[end] == tspan[end] @test sum(abs, sol.u[end]) < 1 -prob = ODEProblem{false}(sys; u0_constructor = x -> SVector(x...)) +prob = ODEProblem{false}(sys, nothing, tspan; u0_constructor = x -> SVector(x...)) @test prob.u0 isa SVector # check_eqs_u0 kwarg test @@ -453,25 +439,9 @@ sys = complete(sys) @test_throws ArgumentError ODEProblem(sys, [1.0, 1.0], (0.0, 1.0)) @test_nowarn ODEProblem(sys, [1.0, 1.0], (0.0, 1.0), check_length = false) -# check inputs -let - @parameters f k d - @variables x(t) ẋ(t) - δ = D - - eqs = [δ(x) ~ ẋ, δ(ẋ) ~ f - k * x - d * ẋ] - @named sys = System(eqs, t, [x, ẋ], [f, d, k]; controls = [f]) - - calculate_control_jacobian(sys) - - @test isequal(calculate_control_jacobian(sys), - reshape(Num[0, 1], 2, 1)) -end - -# issue 1109 -let +@testset "Issue#1109" begin @variables x(t)[1:3, 1:3] - @named sys = System(D.(x) .~ x, t) + @named sys = System(D(x) ~ x, t) @test_nowarn structural_simplify(sys) end @@ -740,51 +710,6 @@ let @test length(equations(structural_simplify(sys))) == 2 end -let - eq_to_lhs(eq) = eq.lhs - eq.rhs ~ 0 - eqs_to_lhs(eqs) = eq_to_lhs.(eqs) - - @parameters σ=10 ρ=28 β=8 / 3 sigma rho beta - @variables x(t)=1 y(t)=0 z(t)=0 x2(t)=1 y2(t)=0 z2(t)=0 u(t)[1:3] - - eqs = [D(x) ~ σ * (y - x), - D(y) ~ x * (ρ - z) - y, - D(z) ~ x * y - β * z] - - eqs2 = [ - D(y2) ~ x2 * (rho - z2) - y2, - D(x2) ~ sigma * (y2 - x2), - D(z2) ~ x2 * y2 - beta * z2 - ] - - # array u - eqs3 = [D(u[1]) ~ sigma * (u[2] - u[1]), - D(u[2]) ~ u[1] * (rho - u[3]) - u[2], - D(u[3]) ~ u[1] * u[2] - beta * u[3]] - eqs3 = eqs_to_lhs(eqs3) - - eqs4 = [ - D(y2) ~ x2 * (rho - z2) - y2, - D(x2) ~ sigma * (y2 - x2), - D(z2) ~ y2 - beta * z2 # missing x2 term - ] - - @named sys1 = System(eqs, t) - @named sys2 = System(eqs2, t) - @named sys3 = System(eqs3, t) - ssys3 = structural_simplify(sys3) - @named sys4 = System(eqs4, t) - - @test ModelingToolkit.isisomorphic(sys1, sys2) - @test !ModelingToolkit.isisomorphic(sys1, sys3) - @test ModelingToolkit.isisomorphic(sys1, ssys3) # I don't call structural_simplify in isisomorphic - @test !ModelingToolkit.isisomorphic(sys1, sys4) - - # 1281 - iv2 = only(independent_variables(sys2)) - @test isequal(only(independent_variables(convert_system(System, sys1, iv2))), iv2) -end - let vars = @variables sP(t) spP(t) spm(t) sph(t) pars = @parameters a b @@ -1564,14 +1489,16 @@ end @parameters p d @variables X(t)::Int64 eq = D(X) ~ p - d * X - @test_throws ArgumentError @mtkbuild osys = System([eq], t) + @test_throws ModelingToolkit.ContinuousOperatorDiscreteArgumentError @mtkbuild osys = System( + [eq], t) @variables Y(t)[1:3]::String eq = D(Y) ~ [p, p, p] - @test_throws ArgumentError @mtkbuild osys = System([eq], t) + @test_throws ModelingToolkit.ContinuousOperatorDiscreteArgumentError @mtkbuild osys = System( + [eq], t) @variables X(t)::Complex - eq = D(X) ~ p - d * X - @test_nowarn @named osys = System([eq], t) + eqs = D(X) ~ p - d * X + @test_nowarn @named osys = System(eqs, t) end # Test `isequal` @@ -1653,7 +1580,8 @@ end @test sol.t[end] == tspan[end] @test sum(abs, sol.u[end]) < 1 - prob = ODEProblem{false}(lowered_dae_sys; u0_constructor = x -> SVector(x...)) + prob = ODEProblem{false}( + lowered_dae_sys, nothing, tspan; u0_constructor = x -> SVector(x...)) @test prob.u0 isa SVector end @@ -1701,7 +1629,7 @@ end eqs = D(x(t)) ~ mat * x(t) cons = [x(3) ~ [2, 3, 3, 5, 4]] @mtkbuild ode = System(D(x(t)) ~ mat * x(t), t; constraints = cons) - @test length(constraints(ModelingToolkit.get_constraintsystem(ode))) == 5 + @test length(constraints(ode)) == 1 end @testset "`build_explicit_observed_function` with `expression = true` returns `Expr`" begin From c619111278d269912782f4b974773278a0cec15b Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Tue, 29 Apr 2025 16:39:32 +0530 Subject: [PATCH 160/185] refactor: store jumps as `Vector{JumpType}` --- src/problems/jumpproblem.jl | 6 +++--- src/systems/system.jl | 3 ++- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/problems/jumpproblem.jl b/src/problems/jumpproblem.jl index 4cc6c8d249..1cded1cf58 100644 --- a/src/problems/jumpproblem.jl +++ b/src/problems/jumpproblem.jl @@ -51,9 +51,9 @@ p = (prob.p isa DiffEqBase.NullParameters || prob.p === nothing) ? Num[] : prob.p majpmapper = JumpSysMajParamMapper(sys, p; jseqs = js, rateconsttype = invttype) - _majs = filter(x -> x isa MassActionJump, js) - _crjs = filter(x -> x isa ConstantRateJump, js) - vrjs = filter(x -> x isa VariableRateJump, js) + _majs = Vector{MassActionJump}(filter(x -> x isa MassActionJump, js)) + _crjs = Vector{ConstantRateJump}(filter(x -> x isa ConstantRateJump, js)) + vrjs = Vector{VariableRateJump}(filter(x -> x isa VariableRateJump, js)) majs = isempty(_majs) ? nothing : assemble_maj(_majs, unknowntoid, majpmapper) crjs = ConstantRateJump[assemble_crj(sys, j, unknowntoid; eval_expression, eval_module) for j in _crjs] diff --git a/src/systems/system.jl b/src/systems/system.jl index 56420fd20b..a7b11f4d06 100644 --- a/src/systems/system.jl +++ b/src/systems/system.jl @@ -17,7 +17,7 @@ struct System <: AbstractSystem # matrix - generic form # column matrix - scalar noise noise_eqs::Union{Nothing, AbstractVector, AbstractMatrix} - jumps::Vector{Any} + jumps::Vector{JumpType} constraints::Vector{Union{Equation, Inequality}} costs::Vector{<:Union{BasicSymbolic, Real}} consolidate::Any @@ -72,6 +72,7 @@ struct System <: AbstractSystem variable $iv. """)) end + jumps = Vector{JumpType}(jumps) if (checks == true || (checks & CheckComponents) > 0) && iv !== nothing check_independent_variables([iv]) check_variables(unknowns, iv) From 40a567046151720461be0a04a0adf9d22563e14a Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Tue, 29 Apr 2025 16:39:45 +0530 Subject: [PATCH 161/185] fix: validate units of jumps --- src/systems/system.jl | 3 +++ src/systems/unit_check.jl | 8 ++++++-- src/systems/validation.jl | 9 +++++++-- 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/src/systems/system.jl b/src/systems/system.jl index a7b11f4d06..6464dcc52e 100644 --- a/src/systems/system.jl +++ b/src/systems/system.jl @@ -91,6 +91,9 @@ struct System <: AbstractSystem else check_units(u, eqs, noise_eqs) end + if iv !== nothing + check_units(u, jumps, iv) + end isempty(constraints) || check_units(u, constraints) end new(tag, eqs, noise_eqs, jumps, constraints, costs, diff --git a/src/systems/unit_check.jl b/src/systems/unit_check.jl index 5035a22b5e..7eeb3f64d8 100644 --- a/src/systems/unit_check.jl +++ b/src/systems/unit_check.jl @@ -267,9 +267,13 @@ function validate(jump::MassActionJump, t::Symbolic; info::String = "") ["scaled_rates", "1/(t*reactants^$n))"]; info) end -function validate(jumps::ArrayPartition{<:Union{Any, Vector{<:JumpType}}}, t::Symbolic) +function validate(jumps::Vector{JumpType}, t::Symbolic) labels = ["in Mass Action Jumps,", "in Constant Rate Jumps,", "in Variable Rate Jumps,"] - all([validate(jumps.x[idx], t, info = labels[idx]) for idx in 1:3]) + majs = filter(x -> x isa MassActionJump, jumps) + crjs = filter(x -> x isa ConstantRateJump, jumps) + vrjs = filter(x -> x isa VariableRateJump, jumps) + splitjumps = [majs, crjs, vrjs] + all([validate(js, t; info) for (js, info) in zip(splitjumps, labels)]) end function validate(eq::Equation; info::String = "") diff --git a/src/systems/validation.jl b/src/systems/validation.jl index 84dd3b07e5..d416a02ea2 100644 --- a/src/systems/validation.jl +++ b/src/systems/validation.jl @@ -5,6 +5,7 @@ using ..ModelingToolkit: ValidationError, ModelingToolkit, Connection, instream, JumpType, VariableUnit, get_systems, Conditional, Comparison +using JumpProcesses: MassActionJump, ConstantRateJump, VariableRateJump using Symbolics: Symbolic, value, issym, isadd, ismul, ispow const MT = ModelingToolkit @@ -231,9 +232,13 @@ function validate(jump::MT.MassActionJump, t::Symbolic; info::String = "") ["scaled_rates", "1/(t*reactants^$n))"]; info) end -function validate(jumps::ArrayPartition{<:Union{Any, Vector{<:JumpType}}}, t::Symbolic) +function validate(jumps::Vector{JumpType}, t::Symbolic) labels = ["in Mass Action Jumps,", "in Constant Rate Jumps,", "in Variable Rate Jumps,"] - all([validate(jumps.x[idx], t, info = labels[idx]) for idx in 1:3]) + majs = filter(x -> x isa MassActionJump, jumps) + crjs = filter(x -> x isa ConstantRateJump, jumps) + vrjs = filter(x -> x isa VariableRateJump, jumps) + splitjumps = [majs, crjs, vrjs] + all([validate(js, t; info) for (js, info) in zip(splitjumps, labels)]) end function validate(eq::MT.Equation; info::String = "") From 54f321ebfc8aca86ab361c7ce9fcdb880b8417ac Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Tue, 29 Apr 2025 21:19:25 +0530 Subject: [PATCH 162/185] fix: respect scoping in `System` constructor variable discovery --- src/systems/system.jl | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/systems/system.jl b/src/systems/system.jl index 6464dcc52e..568626d19f 100644 --- a/src/systems/system.jl +++ b/src/systems/system.jl @@ -218,7 +218,11 @@ function System(eqs::Vector{Equation}, iv; kwargs...) equations. """)) end - push!(diffvars, var) + # this check ensures var is correctly scoped, since `collect_vars!` won't pick + # it up if it belongs to an ancestor system. + if var in othervars + push!(diffvars, var) + end push!(diffeqs, eq) else push!(othereqs, eq) From d58945c162e47adbbdf2446c7f2048afbbbb90e4 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Tue, 29 Apr 2025 21:19:43 +0530 Subject: [PATCH 163/185] fix: fix `flatten(::System)` --- src/systems/system.jl | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/systems/system.jl b/src/systems/system.jl index 568626d19f..72a12834ff 100644 --- a/src/systems/system.jl +++ b/src/systems/system.jl @@ -443,6 +443,10 @@ function flatten(sys::System, noeqs = false) else costs = [costs] end + # We don't include `ignored_connections` in the flattened system, because + # connection expansion inherently requires the hierarchy structure. If the system + # is being flattened, then we no longer want to expand connections (or have already + # done so) and thus don't care about `ignored_connections`. return System(noeqs ? Equation[] : equations(sys), get_iv(sys), unknowns(sys), parameters(sys; initial_parameters = true), brownians(sys); jumps = jumps(sys), constraints = constraints(sys), costs = costs, @@ -451,11 +455,10 @@ function flatten(sys::System, noeqs = false) guesses = guesses(sys), continuous_events = continuous_events(sys), discrete_events = discrete_events(sys), assertions = assertions(sys), is_dde = is_dde(sys), tstops = symbolic_tstops(sys), - ignored_connections = ignored_connections(sys), # without this, any defaults/guesses obtained from metadata that were # later removed by the user will be re-added. Right now, we just want to # retain `defaults(sys)` as-is. - discover_from_metadata = false, + discover_from_metadata = false, metadata = get_metadata(sys), description = description(sys), name = nameof(sys)) end From cc47cfd2dfc03a36fe916e2ae7464919848ecab2 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Tue, 29 Apr 2025 21:20:04 +0530 Subject: [PATCH 164/185] feat: export `jumps` --- src/ModelingToolkit.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ModelingToolkit.jl b/src/ModelingToolkit.jl index cdfb9ba135..30926c55f2 100644 --- a/src/ModelingToolkit.jl +++ b/src/ModelingToolkit.jl @@ -302,7 +302,7 @@ export Differential, expand_derivatives, @derivatives export Equation, ConstrainedEquation export Term, Sym export SymScope, LocalScope, ParentScope, GlobalScope -export independent_variable, equations, controls, observed, full_equations +export independent_variable, equations, controls, observed, full_equations, jumps export initialization_equations, guesses, defaults, parameter_dependencies, hierarchy export structural_simplify, expand_connections, linearize, linearization_function, LinearizationProblem From 63d4679b13e2b9dbb89b022a01890db70142379c Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Tue, 29 Apr 2025 21:20:29 +0530 Subject: [PATCH 165/185] fix: validate that `Sample` operates on unknowns --- src/discretedomain.jl | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/discretedomain.jl b/src/discretedomain.jl index 0befab61a2..28d9eb1d7f 100644 --- a/src/discretedomain.jl +++ b/src/discretedomain.jl @@ -142,6 +142,11 @@ function validate_operator(op::Sample, args, iv; context = nothing) if !is_variable_floatingpoint(arg) throw(ContinuousOperatorDiscreteArgumentError(op, arg, context)) end + if isparameter(arg) + throw(ArgumentError(""" + Expected argument of $op to be an unknown, found $arg which is a parameter. + """)) + end end """ From 90bb6d17bafcf861b3acd17684d9c52b7abf7413 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Tue, 29 Apr 2025 21:20:57 +0530 Subject: [PATCH 166/185] test: fix `test/structural_transformation/utils.jl` --- test/structural_transformation/utils.jl | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/test/structural_transformation/utils.jl b/test/structural_transformation/utils.jl index 9d560a2614..540cb8fdae 100644 --- a/test/structural_transformation/utils.jl +++ b/test/structural_transformation/utils.jl @@ -191,11 +191,6 @@ end end @testset "`map_variables_to_equations`" begin - @testset "Not supported for systems without `.tearing_state`" begin - @variables x - @mtkbuild sys = OptimizationSystem(x^2) - @test_throws ArgumentError map_variables_to_equations(sys) - end @testset "Requires simplified system" begin @variables x(t) y(t) @named sys = System([D(x) ~ x, y ~ 2x], t) @@ -299,7 +294,7 @@ end eqs = [D(x) ~ dx D(dx) ~ ddx dx ~ (k - x) / T] - return ODESystem(eqs, t, vars, params; systems, name) + return System(eqs, t, vars, params; systems, name) end @component function FilteredInputExplicit(; name, x0 = 0, T = 0.1) @@ -317,7 +312,7 @@ end D(dx) ~ ddx D(k[1]) ~ 1.0 dx ~ (k[1] - x) / T] - return ODESystem(eqs, t, vars, params; systems, name) + return System(eqs, t, vars, params; systems, name) end @component function FilteredInputErr(; name, x0 = 0, T = 0.1) @@ -335,7 +330,7 @@ end D(dx) ~ ddx dx ~ (k - x) / T D(k) ~ missing] - return ODESystem(eqs, t, vars, params; systems, name) + return System(eqs, t, vars, params; systems, name) end @named sys = FilteredInputErr() @@ -376,7 +371,7 @@ end eqs = [D(x) ~ dx D(dx) ~ ddx dx ~ (k(t) - x) / T] - return ODESystem(eqs, t, vars, params; systems, name) + return System(eqs, t, vars, params; systems, name) end @mtkbuild sys = FilteredInput2() From 9e5fbc732dc5d33e1038cf59dda2ca0af994a974 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Tue, 29 Apr 2025 21:21:27 +0530 Subject: [PATCH 167/185] test: simplify test for metadata retention in `complete` --- test/components.jl | 37 +++++-------------------------------- 1 file changed, 5 insertions(+), 32 deletions(-) diff --git a/test/components.jl b/test/components.jl index 1efdd8a22f..50250a7cac 100644 --- a/test/components.jl +++ b/test/components.jl @@ -373,36 +373,9 @@ end @testset "Issue#3275: Metadata retained on `complete`" begin @variables x(t) y(t) - @testset "ODESystem" begin - @named inner = System(D(x) ~ x, t) - @named outer = System(D(y) ~ y, t; systems = [inner], metadata = "test") - @test ModelingToolkit.get_metadata(outer) == "test" - sys = complete(outer) - @test ModelingToolkit.get_metadata(sys) == "test" - end - @testset "NonlinearSystem" begin - @named inner = System([0 ~ x^2 + 4x + 4], [x], []) - @named outer = System( - [0 ~ x^3 - y^3], [x, y], []; systems = [inner], metadata = "test") - @test ModelingToolkit.get_metadata(outer) == "test" - sys = complete(outer) - @test ModelingToolkit.get_metadata(sys) == "test" - end - k = ShiftIndex(t) - @testset "DiscreteSystem" begin - @named inner = System([x(k) ~ x(k - 1) + x(k - 2)], t, [x], []) - @named outer = System([y(k) ~ y(k - 1) + y(k - 2)], t, [x, y], - []; systems = [inner], metadata = "test") - @test ModelingToolkit.get_metadata(outer) == "test" - sys = complete(outer) - @test ModelingToolkit.get_metadata(sys) == "test" - end - @testset "OptimizationSystem" begin - @named inner = OptimizationSystem(x^2 + y^2 - 3, [x, y], []) - @named outer = OptimizationSystem( - x^3 - y, [x, y], []; systems = [inner], metadata = "test") - @test ModelingToolkit.get_metadata(outer) == "test" - sys = complete(outer) - @test ModelingToolkit.get_metadata(sys) == "test" - end + @named inner = System(D(x) ~ x, t) + @named outer = System(D(y) ~ y, t; systems = [inner], metadata = "test") + @test ModelingToolkit.get_metadata(outer) == "test" + sys = complete(outer) + @test ModelingToolkit.get_metadata(sys) == "test" end From ba1ed910c136d577b8cb24d8e7e96af30f3c254b Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Tue, 29 Apr 2025 21:22:11 +0530 Subject: [PATCH 168/185] test: improve readability of dependency graph tests --- test/dep_graphs.jl | 290 +++++++++++++++++++++------------------------ 1 file changed, 136 insertions(+), 154 deletions(-) diff --git a/test/dep_graphs.jl b/test/dep_graphs.jl index 3c7b88dd05..04e6bf159a 100644 --- a/test/dep_graphs.jl +++ b/test/dep_graphs.jl @@ -6,74 +6,7 @@ import ModelingToolkit: value ################################# # testing for Jumps / all dgs ################################# -@parameters k1 k2 -@variables S(t) I(t) R(t) -j₁ = MassActionJump(k1, [0 => 1], [S => 1]) -j₂ = MassActionJump(k1, [S => 1], [S => -1]) -j₃ = MassActionJump(k2, [S => 1, I => 1], [S => -1, I => 1]) -j₄ = MassActionJump(k2, [S => 2, R => 1], [R => -1]) -j₅ = ConstantRateJump(k1 * I, [R ~ R + 1]) -j₆ = VariableRateJump(k1 * k2 / (1 + t) * S, [S ~ S - 1, R ~ R + 1]) -eqs = [j₁, j₂, j₃, j₄, j₅, j₆] -@named js = JumpSystem(eqs, t, [S, I, R], [k1, k2]) -S = value(S) -I = value(I) -R = value(R) -k1 = value(k1) -k2 = value(k2) -# eq to vars they depend on -eq_sdeps = [Variable[], [S], [S, I], [S, R], [I], [S]] -eq_sidepsf = [Int[], [1], [1, 2], [1, 3], [2], [1]] -eq_sidepsb = [[2, 3, 4, 6], [3, 5], [4]] -deps = equation_dependencies(js) -@test all(i -> isequal(Set(eq_sdeps[i]), Set(deps[i])), 1:length(eqs)) -depsbg = asgraph(js) -@test depsbg.fadjlist == eq_sidepsf -@test depsbg.badjlist == eq_sidepsb - -# eq to params they depend on -eq_pdeps = [[k1], [k1], [k2], [k2], [k1], [k1, k2]] -eq_pidepsf = [[1], [1], [2], [2], [1], [1, 2]] -eq_pidepsb = [[1, 2, 5, 6], [3, 4, 6]] -deps = equation_dependencies(js, variables = parameters(js)) -@test all(i -> isequal(Set(eq_pdeps[i]), Set(deps[i])), 1:length(eqs)) -depsbg2 = asgraph(js, variables = parameters(js)) -@test depsbg2.fadjlist == eq_pidepsf -@test depsbg2.badjlist == eq_pidepsb - -# var to eqs that modify them -s_eqdepsf = [[1, 2, 3, 6], [3], [4, 5, 6]] -s_eqdepsb = [[1], [1], [1, 2], [3], [3], [1, 3]] -ne = 8 -bg = BipartiteGraph(ne, s_eqdepsf, s_eqdepsb) -deps2 = variable_dependencies(js) -@test isequal(bg, deps2) - -# eq to eqs that depend on them -eq_eqdeps = [[2, 3, 4, 6], [2, 3, 4, 6], [2, 3, 4, 5, 6], [4], [4], [2, 3, 4, 6]] -dg = SimpleDiGraph(6) -for (eqidx, eqdeps) in enumerate(eq_eqdeps) - for eqdepidx in eqdeps - add_edge!(dg, eqidx, eqdepidx) - end -end -dg3 = eqeq_dependencies(depsbg, deps2) -@test dg == dg3 - -# var to vars that depend on them -var_vardeps = [[1, 2, 3], [1, 2, 3], [3]] -ne = 7 -dg = SimpleDiGraph(3) -for (vidx, vdeps) in enumerate(var_vardeps) - for vdepidx in vdeps - add_edge!(dg, vidx, vdepidx) - end -end -dg4 = varvar_dependencies(depsbg, deps2) -@test dg == dg4 - -# testing when ignoring VariableRateJumps -let +@testset "JumpSystem" begin @parameters k1 k2 @variables S(t) I(t) R(t) j₁ = MassActionJump(k1, [0 => 1], [S => 1]) @@ -82,109 +15,158 @@ let j₄ = MassActionJump(k2, [S => 2, R => 1], [R => -1]) j₅ = ConstantRateJump(k1 * I, [R ~ R + 1]) j₆ = VariableRateJump(k1 * k2 / (1 + t) * S, [S ~ S - 1, R ~ R + 1]) - eqs = [j₁, j₂, j₃, j₄, j₅, j₆] - @named js = JumpSystem(eqs, t, [S, I, R], [k1, k2]) + alleqs = [j₁, j₂, j₃, j₄, j₅, j₆] + @named js = JumpSystem(alleqs, t, [S, I, R], [k1, k2]) S = value(S) I = value(I) R = value(R) k1 = value(k1) k2 = value(k2) - # eq to vars they depend on - eq_sdeps = [Variable[], [S], [S, I], [S, R], [I]] - eq_sidepsf = [Int[], [1], [1, 2], [1, 3], [2]] - eq_sidepsb = [[2, 3, 4], [3, 5], [4]] - - # filter out vrjs in making graphs - eqs = ArrayPartition(equations(js).x[1], equations(js).x[2]) - deps = equation_dependencies(js; eqs) - @test length(deps) == length(eq_sdeps) - @test all(i -> isequal(Set(eq_sdeps[i]), Set(deps[i])), 1:length(eqs)) - depsbg = asgraph(js; eqs) - @test depsbg.fadjlist == eq_sidepsf - @test depsbg.badjlist == eq_sidepsb - # eq to params they depend on - eq_pdeps = [[k1], [k1], [k2], [k2], [k1]] - eq_pidepsf = [[1], [1], [2], [2], [1]] - eq_pidepsb = [[1, 2, 5], [3, 4]] - deps = equation_dependencies(js; variables = parameters(js), eqs) - @test length(deps) == length(eq_pdeps) - @test all(i -> isequal(Set(eq_pdeps[i]), Set(deps[i])), 1:length(eqs)) - depsbg2 = asgraph(js; variables = parameters(js), eqs) - @test depsbg2.fadjlist == eq_pidepsf - @test depsbg2.badjlist == eq_pidepsb - - # var to eqs that modify them - s_eqdepsf = [[1, 2, 3], [3], [4, 5]] - s_eqdepsb = [[1], [1], [1, 2], [3], [3]] - ne = 6 - bg = BipartiteGraph(ne, s_eqdepsf, s_eqdepsb) - deps2 = variable_dependencies(js; eqs) - @test isequal(bg, deps2) - - # eq to eqs that depend on them - eq_eqdeps = [[2, 3, 4], [2, 3, 4], [2, 3, 4, 5], [4], [4], [2, 3, 4]] - dg = SimpleDiGraph(5) - for (eqidx, eqdeps) in enumerate(eq_eqdeps) - for eqdepidx in eqdeps - add_edge!(dg, eqidx, eqdepidx) + test_case_1 = (; + eqs = jumps(js), + # eq to vars they depend on + eq_sdeps = [Variable[], [S], [S, I], [S, R], [I], [S]], + eq_sidepsf = [Int[], [1], [1, 2], [1, 3], [2], [1]], + eq_sidepsb = [[2, 3, 4, 6], [3, 5], [4]], + # eq to params they depend on + eq_pdeps = [[k1], [k1], [k2], [k2], [k1], [k1, k2]], + eq_pidepsf = [[1], [1], [2], [2], [1], [1, 2]], + eq_pidepsb = [[1, 2, 5, 6], [3, 4, 6]], + # var to eqs that modify them + s_eqdepsf = [[1, 2, 3, 6], [3], [4, 5, 6]], + s_eqdepsb = [[1], [1], [1, 2], [3], [3], [1, 3]], + var_eq_ne = 8, + # eq to eqs that depend on them + eq_eqdeps = [[2, 3, 4, 6], [2, 3, 4, 6], [2, 3, 4, 5, 6], [4], [4], [2, 3, 4, 6]], + eq_eq_ne = 6, + # var to vars that depend on them + var_vardeps = [[1, 2, 3], [1, 2, 3], [3]], + var_var_ne = 3 + ) + # testing when ignoring VariableRateJumps + test_case_2 = (; + # filter out vrjs in making graphs + eqs = filter(x -> !(x isa VariableRateJump), jumps(js)), + # eq to vars they depend on + eq_sdeps = [Variable[], [S], [S, I], [S, R], [I]], + eq_sidepsf = [Int[], [1], [1, 2], [1, 3], [2]], + eq_sidepsb = [[2, 3, 4], [3, 5], [4]], + # eq to params they depend on + eq_pdeps = [[k1], [k1], [k2], [k2], [k1]], + eq_pidepsf = [[1], [1], [2], [2], [1]], + eq_pidepsb = [[1, 2, 5], [3, 4]], + # var to eqs that modify them + s_eqdepsf = [[1, 2, 3], [3], [4, 5]], + s_eqdepsb = [[1], [1], [1, 2], [3], [3]], + var_eq_ne = 6, + # eq to eqs that depend on them + eq_eqdeps = [[2, 3, 4], [2, 3, 4], [2, 3, 4, 5], [4], [4], [2, 3, 4]], + eq_eq_ne = 5, + # var to vars that depend on them + var_vardeps = [[1, 2, 3], [1, 2, 3], [3]], + var_var_ne = 3 + ) + + @testset "Case $i" for (i, test_case) in enumerate([test_case_1, test_case_2]) + (; # filter out vrjs in making graphs + eqs, # eq to vars they depend on + eq_sdeps, + eq_sidepsf, + eq_sidepsb, # eq to params they depend on + eq_pdeps, + eq_pidepsf, + eq_pidepsb, # var to eqs that modify them + s_eqdepsf, + s_eqdepsb, + var_eq_ne, # eq to eqs that depend on them + eq_eqdeps, + eq_eq_ne, # var to vars that depend on them + var_vardeps, + var_var_ne +) = test_case + deps = equation_dependencies(js; eqs) + @test length(deps) == length(eq_sdeps) + @test all([issetequal(a, b) for (a, b) in zip(eq_sdeps, deps)]) + # @test all(i -> ) + # @test all(i -> isequal(Set(eq_sdeps[i]), Set(deps[i])), 1:length(alleqs)) + depsbg = asgraph(js; eqs) + @test depsbg.fadjlist == eq_sidepsf + @test depsbg.badjlist == eq_sidepsb + + deps = equation_dependencies(js; variables = parameters(js), eqs) + @test length(deps) == length(eq_pdeps) + @test all([issetequal(a, b) for (a, b) in zip(eq_pdeps, deps)]) + depsbg2 = asgraph(js; variables = parameters(js), eqs) + @test depsbg2.fadjlist == eq_pidepsf + @test depsbg2.badjlist == eq_pidepsb + + bg = BipartiteGraph(var_eq_ne, s_eqdepsf, s_eqdepsb) + deps2 = variable_dependencies(js; eqs) + @test isequal(bg, deps2) + + dg = SimpleDiGraph(eq_eq_ne) + for (eqidx, eqdeps) in enumerate(eq_eqdeps) + for eqdepidx in eqdeps + add_edge!(dg, eqidx, eqdepidx) + end end - end - dg3 = eqeq_dependencies(depsbg, deps2) - @test dg == dg3 - - # var to vars that depend on them - var_vardeps = [[1, 2, 3], [1, 2, 3], [3]] - ne = 7 - dg = SimpleDiGraph(3) - for (vidx, vdeps) in enumerate(var_vardeps) - for vdepidx in vdeps - add_edge!(dg, vidx, vdepidx) + dg3 = eqeq_dependencies(depsbg, deps2) + @test dg == dg3 + + dg = SimpleDiGraph(var_var_ne) + for (vidx, vdeps) in enumerate(var_vardeps) + for vdepidx in vdeps + add_edge!(dg, vidx, vdepidx) + end end + dg4 = varvar_dependencies(depsbg, deps2) + @test dg == dg4 end - dg4 = varvar_dependencies(depsbg, deps2) - @test dg == dg4 end ##################################### # testing for ODE/SDEs ##################################### -@parameters k1 k2 -@variables S(t) I(t) R(t) -eqs = [D(S) ~ k1 - k1 * S - k2 * S * I - k1 * k2 / (1 + t) * S, - D(I) ~ k2 * S * I, - D(R) ~ -k2 * S^2 * R / 2 + k1 * I + k1 * k2 * S / (1 + t)] -noiseeqs = [S, I, R] -@named os = System(eqs, t, [S, I, R], [k1, k2]) -deps = equation_dependencies(os) -S = value(S); -I = value(I); -R = value(R); -k1 = value(k1); -k2 = value(k2); -eq_sdeps = [[S, I], [S, I], [S, I, R]] -@test all(i -> isequal(Set(eq_sdeps[i]), Set(deps[i])), 1:length(deps)) -@parameters k1 k2 -@variables S(t) I(t) R(t) -@named sdes = SDESystem(eqs, noiseeqs, t, [S, I, R], [k1, k2]) -deps = equation_dependencies(sdes) -@test all(i -> isequal(Set(eq_sdeps[i]), Set(deps[i])), 1:length(deps)) +@testset "ODEs, SDEs" begin + @parameters k1 k2 + @variables S(t) I(t) R(t) + eqs = [D(S) ~ k1 - k1 * S - k2 * S * I - k1 * k2 / (1 + t) * S + D(I) ~ k2 * S * I + D(R) ~ -k2 * S^2 * R / 2 + k1 * I + k1 * k2 * S / (1 + t)] + @named os = System(eqs, t, [S, I, R], [k1, k2]) + deps = equation_dependencies(os) + S = value(S) + I = value(I) + R = value(R) + k1 = value(k1) + k2 = value(k2) + eq_sdeps = [[S, I], [S, I], [S, I, R]] + @test all(i -> isequal(Set(eq_sdeps[i]), Set(deps[i])), 1:length(deps)) + + noiseeqs = [S, I, R] + @named sdes = SDESystem(eqs, noiseeqs, t, [S, I, R], [k1, k2]) + deps = equation_dependencies(sdes) + @test all(i -> isequal(Set(eq_sdeps[i]), Set(deps[i])), 1:length(deps)) -deps = variable_dependencies(os) -s_eqdeps = [[1], [2], [3]] -@test deps.fadjlist == s_eqdeps + deps = variable_dependencies(os) + s_eqdeps = [[1], [2], [3]] + @test deps.fadjlist == s_eqdeps +end ##################################### # testing for nonlin sys ##################################### -@variables x y z -@parameters σ ρ β - -eqs = [0 ~ σ * (y - x), - 0 ~ ρ - y, - 0 ~ y - β * z] -@named ns = System(eqs, [x, y, z], [σ, ρ, β]) -deps = equation_dependencies(ns) -eq_sdeps = [[x, y], [y], [y, z]] -@test all(i -> isequal(Set(deps[i]), Set(value.(eq_sdeps[i]))), 1:length(deps)) +@testset "Nonlinear" begin + @variables x y z + @parameters σ ρ β + + eqs = [0 ~ σ * (y - x), + 0 ~ ρ - y, + 0 ~ y - β * z] + @named ns = System(eqs, [x, y, z], [σ, ρ, β]) + deps = equation_dependencies(ns) + eq_sdeps = [[x, y], [y], [y, z]] + @test all(i -> isequal(Set(deps[i]), Set(value.(eq_sdeps[i]))), 1:length(deps)) +end From 198a80c08835d8f7e6a752b32f0b4e0eb9bb590a Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Tue, 29 Apr 2025 21:22:29 +0530 Subject: [PATCH 169/185] test: fix usage of `ODEProblemExpr` in lowering test --- test/lowering_solving.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/lowering_solving.jl b/test/lowering_solving.jl index 8c4db26287..280848f6ad 100644 --- a/test/lowering_solving.jl +++ b/test/lowering_solving.jl @@ -33,7 +33,7 @@ tspan = (0.0, 100.0) sys = complete(sys) prob = ODEProblem(sys, u0, tspan, p, jac = true) -probexpr = ODEProblemExpr(sys, u0, tspan, p, jac = true) +probexpr = ODEProblem(sys, u0, tspan, p; jac = true, expression = Val{true}) sol = solve(prob, Tsit5()) solexpr = solve(eval(prob), Tsit5()) @test all(x -> x == 0, Array(sol - solexpr)) From 3818e75f668b2b376715d8d3f968c37a7c12fd65 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Tue, 29 Apr 2025 21:22:44 +0530 Subject: [PATCH 170/185] test: remove test for specifying type of system in `@mtkmodel` --- test/model_parsing.jl | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/test/model_parsing.jl b/test/model_parsing.jl index e8464707de..62c19d2055 100644 --- a/test/model_parsing.jl +++ b/test/model_parsing.jl @@ -1011,18 +1011,3 @@ end @test any(isequal(u), vars) end end - -@testset "Specify the type of system" begin - @mtkmodel Float2Bool::DiscreteSystem begin - @variables begin - u(t)::Float64 - y(t)::Bool - end - @equations begin - y ~ u != 0 - end - end - - @named sys = Float2Bool() - @test typeof(sys) == DiscreteSystem -end From e8c5254ceba0aa7600369ad12fe020179ee36d58 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Tue, 29 Apr 2025 21:23:05 +0530 Subject: [PATCH 171/185] test: fix parameter dependencies test --- test/parameter_dependencies.jl | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/test/parameter_dependencies.jl b/test/parameter_dependencies.jl index 779b7c0b79..4edc2c0493 100644 --- a/test/parameter_dependencies.jl +++ b/test/parameter_dependencies.jl @@ -77,14 +77,14 @@ end t ) @named sys2 = System( - [], + Equation[], t; parameter_dependencies = [p2 => 2p1] ) sys = extend(sys2, sys1) @test !(p2 in Set(parameters(sys))) @test p2 in Set(full_parameters(sys)) - prob = ODEProblem(complete(sys)) + prob = ODEProblem(complete(sys), nothing, (0.0, 1.0)) get_dep = getu(prob, 2p2) @test get_dep(prob) == 4 end @@ -98,7 +98,7 @@ end t; parameter_dependencies = [p2 => 2p1] ) - prob = ODEProblem(complete(sys)) + prob = ODEProblem(complete(sys), nothing, (0.0, 1.0)) get_dep = getu(prob, 2p2) @test get_dep(prob) == 4 end @@ -130,9 +130,9 @@ end t; parameter_dependencies = [p2 => 2p1] ) - sys = complete(System([], t, systems = [sys1, sys2], name = :sys)) + sys = complete(System(Equation[], t, systems = [sys1, sys2], name = :sys)) - prob = ODEProblem(sys) + prob = ODEProblem(sys, [], (0.0, 1.0)) v1 = sys.sys2.p2 v2 = 2 * v1 @test is_observed(prob, v1) From 48db08f0c5c437d7bf0b936b7a5ab0dd1bd758f1 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Tue, 29 Apr 2025 21:23:14 +0530 Subject: [PATCH 172/185] test: fix symbolic events test --- test/symbolic_events.jl | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/test/symbolic_events.jl b/test/symbolic_events.jl index dc8f059bf9..1d9a45bfa8 100644 --- a/test/symbolic_events.jl +++ b/test/symbolic_events.jl @@ -2,7 +2,6 @@ using ModelingToolkit, OrdinaryDiffEq, StochasticDiffEq, JumpProcesses, Test using SciMLStructures: canonicalize, Discrete using ModelingToolkit: SymbolicContinuousCallback, SymbolicContinuousCallbacks, NULL_AFFECT, - get_callback, t_nounits as t, D_nounits as D using StableRNGs @@ -11,6 +10,10 @@ using SymbolicIndexingInterface using Setfield rng = StableRNG(12345) +function get_callback(prob) + prob.kwargs[:callback] +end + @variables x(t) = 0 eqs = [D(x) ~ 1] @@ -747,8 +750,7 @@ let rng = rng function testsol(jsys, u0, p, tspan; tstops = Float64[], paramtotest = nothing, N = 40000, kwargs...) jsys = complete(jsys) - dprob = DiscreteProblem(jsys, u0, tspan, p) - jprob = JumpProblem(jsys, dprob, Direct(); kwargs...) + jprob = JumpProblem(jsys, u0, tspan, p; aggregator = Direct(), kwargs...) sol = solve(jprob, SSAStepper(); tstops = tstops) @test (sol(1.000000000001)[1] - sol(0.99999999999)[1]) == 1 paramtotest === nothing || (@test sol.ps[paramtotest] == 1.0) @@ -1415,7 +1417,7 @@ end @named wd1 = weird1(0.021) @named wd2 = weird2(0.021) - sys1 = structural_simplify(System([], t; name = :parent, + sys1 = structural_simplify(System(Equation[], t; name = :parent, discrete_events = [0.01 => ModelingToolkit.ImperativeAffect( modified = (; θs = reduce(vcat, [[wd1.θ]])), ctx = [1]) do m, o, c, i @set! m.θs[1] = c[] += 1 From 294b3b1a3453967b2af0df7f1c33cc08b414e7b2 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Tue, 29 Apr 2025 21:23:30 +0530 Subject: [PATCH 173/185] fix: fix `calculate_jacobian` --- src/systems/codegen.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/systems/codegen.jl b/src/systems/codegen.jl index c169a098d5..53655d30c9 100644 --- a/src/systems/codegen.jl +++ b/src/systems/codegen.jl @@ -108,7 +108,7 @@ end function calculate_jacobian(sys::System; sparse = false, simplify = false, dvs = unknowns(sys)) obs = Dict(eq.lhs => eq.rhs for eq in observed(sys)) - rhs = map(eq -> fixpoint_sub(eq.rhs, obs), equations(sys)) + rhs = map(eq -> fixpoint_sub(eq.rhs - eq.lhs, obs), equations(sys)) if sparse jac = sparsejacobian(rhs, dvs; simplify) From 145d87bb8d234abefcea8b1babe44bf8c4c54fd2 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Tue, 29 Apr 2025 21:23:47 +0530 Subject: [PATCH 174/185] fix: respect `return_sparsity` in `generate_cost_hessian` --- src/systems/codegen.jl | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/systems/codegen.jl b/src/systems/codegen.jl index 53655d30c9..f8426b3593 100644 --- a/src/systems/codegen.jl +++ b/src/systems/codegen.jl @@ -386,8 +386,10 @@ function generate_cost_hessian( exprs = Symbolics.hessian(obj, dvs; simplify) end res = build_function_wrapper(sys, exprs, dvs, ps...; expression = Val{true}, kwargs...) - return maybe_compile_function( + fn = maybe_compile_function( expression, wrap_gfw, (2, 2, is_split(sys)), res; eval_expression, eval_module) + + return return_sparsity ? (fn, sparsity) : fn end function canonical_constraints(sys::System) From 5e43f70ed2e3addc333df4f537fc3b69634ffcca Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Tue, 29 Apr 2025 21:24:16 +0530 Subject: [PATCH 175/185] test: use `System` in `examples/*.jl` --- examples/electrical_components.jl | 20 ++++++++++---------- examples/rc_model.jl | 2 +- examples/serial_inductor.jl | 4 ++-- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/examples/electrical_components.jl b/examples/electrical_components.jl index 1f4f151c21..1f8e9e9a32 100644 --- a/examples/electrical_components.jl +++ b/examples/electrical_components.jl @@ -4,13 +4,13 @@ using ModelingToolkit: t_nounits as t, D_nounits as D @connector function Pin(; name) sts = @variables v(t) [guess = 1.0] i(t) [guess = 1.0, connect = Flow] - ODESystem(Equation[], t, sts, []; name = name) + System(Equation[], t, sts, []; name = name) end @component function Ground(; name) @named g = Pin() eqs = [g.v ~ 0] - compose(ODESystem(eqs, t, [], []; name = name), g) + compose(System(eqs, t, [], []; name = name), g) end @component function OnePort(; name) @@ -20,7 +20,7 @@ end eqs = [v ~ p.v - n.v 0 ~ p.i + n.i i ~ p.i] - compose(ODESystem(eqs, t, sts, []; name = name), p, n) + compose(System(eqs, t, sts, []; name = name), p, n) end @component function Resistor(; name, R = 1.0) @@ -30,7 +30,7 @@ end eqs = [ v ~ i * R ] - extend(ODESystem(eqs, t, [], ps; name = name), oneport) + extend(System(eqs, t, [], ps; name = name), oneport) end @component function Capacitor(; name, C = 1.0) @@ -40,7 +40,7 @@ end eqs = [ D(v) ~ i / C ] - extend(ODESystem(eqs, t, [], ps; name = name), oneport) + extend(System(eqs, t, [], ps; name = name), oneport) end @component function ConstantVoltage(; name, V = 1.0) @@ -50,7 +50,7 @@ end eqs = [ V ~ v ] - extend(ODESystem(eqs, t, [], ps; name = name), oneport) + extend(System(eqs, t, [], ps; name = name), oneport) end @component function Inductor(; name, L = 1.0) @@ -60,12 +60,12 @@ end eqs = [ D(i) ~ v / L ] - extend(ODESystem(eqs, t, [], ps; name = name), oneport) + extend(System(eqs, t, [], ps; name = name), oneport) end @connector function HeatPort(; name) @variables T(t) [guess = 293.15] Q_flow(t) [guess = 0.0, connect = Flow] - ODESystem(Equation[], t, [T, Q_flow], [], name = name) + System(Equation[], t, [T, Q_flow], [], name = name) end @component function HeatingResistor(; name, R = 1.0, TAmbient = 293.15, alpha = 1.0) @@ -79,7 +79,7 @@ end h.Q_flow ~ -v * p.i # -LossPower v ~ p.v - n.v 0 ~ p.i + n.i] - compose(ODESystem(eqs, t, [v, RTherm], [R, TAmbient, alpha], + compose(System(eqs, t, [v, RTherm], [R, TAmbient, alpha], name = name), p, n, h) end @@ -90,6 +90,6 @@ end eqs = [ D(h.T) ~ h.Q_flow / C ] - compose(ODESystem(eqs, t, [], [rho, V, cp], + compose(System(eqs, t, [], [rho, V, cp], name = name), h) end diff --git a/examples/rc_model.jl b/examples/rc_model.jl index 158436d980..6afe97a318 100644 --- a/examples/rc_model.jl +++ b/examples/rc_model.jl @@ -13,5 +13,5 @@ rc_eqs = [connect(source.p, resistor.p) connect(capacitor.n, source.n) connect(capacitor.n, ground.g)] -@named rc_model = ODESystem(rc_eqs, t) +@named rc_model = System(rc_eqs, t) rc_model = compose(rc_model, [resistor, capacitor, source, ground]) diff --git a/examples/serial_inductor.jl b/examples/serial_inductor.jl index 302df32c17..2816e2bb96 100644 --- a/examples/serial_inductor.jl +++ b/examples/serial_inductor.jl @@ -12,7 +12,7 @@ eqs = [connect(source.p, resistor.p) connect(source.n, inductor2.n) connect(inductor2.n, ground.g)] -@named ll_model = ODESystem(eqs, t) +@named ll_model = System(eqs, t) ll_model = compose(ll_model, [source, resistor, inductor1, inductor2, ground]) @named source = ConstantVoltage(V = 10.0) @@ -29,5 +29,5 @@ eqs = [connect(source.p, inductor1.p) connect(resistor2.n, inductor2.p) connect(source.n, inductor2.n) connect(inductor2.n, ground.g)] -@named ll2_model = ODESystem(eqs, t) +@named ll2_model = System(eqs, t) ll2_model = compose(ll2_model, [source, resistor1, resistor2, inductor1, inductor2, ground]) From 79b2b05ae0abbc96b1e3fdc285394bfd3338d453 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Tue, 29 Apr 2025 22:44:18 +0530 Subject: [PATCH 176/185] test: fix modelingtoolkitize test --- test/modelingtoolkitize.jl | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/test/modelingtoolkitize.jl b/test/modelingtoolkitize.jl index 04e6263c7c..0157a50acb 100644 --- a/test/modelingtoolkitize.jl +++ b/test/modelingtoolkitize.jl @@ -253,7 +253,6 @@ prob = ODEProblem(ode_prob_dict, u0, (0.0, 1.0), params) sys = modelingtoolkitize(prob) @test [ModelingToolkit.defaults(sys)[s] for s in unknowns(sys)] == u0 @test [ModelingToolkit.defaults(sys)[s] for s in parameters(sys)] == [10, 20] -@test ModelingToolkit.has_tspan(sys) @parameters sig=10 rho=28.0 beta=8 / 3 @variables x(t)=100 y(t)=1.0 z(t)=1 @@ -266,10 +265,9 @@ noiseeqs = [0.1 * x, 0.1 * y, 0.1 * z] -@named sys = SDESystem(eqs, noiseeqs, t, [x, y, z], [sig, rho, beta]; tspan = (0, 1000.0)) -prob = SDEProblem(complete(sys)) +@named sys = SDESystem(eqs, noiseeqs, t, [x, y, z], [sig, rho, beta]) +prob = SDEProblem(complete(sys), nothing, (0.0, 1.0)) sys = modelingtoolkitize(prob) -@test ModelingToolkit.has_tspan(sys) @testset "Explicit variable names" begin function fn(du, u, p::NamedTuple, t) From b3459624feb7b9219f38ac66e54b01cb3c4fd048 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Thu, 1 May 2025 13:56:50 +0530 Subject: [PATCH 177/185] test: remove outdated test We allow time-dependent parameter derivatives now --- test/odesystem.jl | 8 -------- 1 file changed, 8 deletions(-) diff --git a/test/odesystem.jl b/test/odesystem.jl index 50813bfc5f..316a155876 100644 --- a/test/odesystem.jl +++ b/test/odesystem.jl @@ -850,14 +850,6 @@ let @test string.(independent_variables(prob.f.sys)) == ["t"] end -let - @parameters P(t) Q(t) - ∂t = D - eqs = [∂t(Q) ~ 0.2P - ∂t(P) ~ -80.0sin(Q)] - @test_throws ArgumentError @named sys = System(eqs, t) -end - @parameters C L R @variables q(t) p(t) F(t) From 1ed449b38f3ddcfa809fb437f5380727de8bdcd3 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Thu, 1 May 2025 13:56:59 +0530 Subject: [PATCH 178/185] fixup! test: fix symbolic events test --- 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 1d9a45bfa8..3b8888d805 100644 --- a/test/symbolic_events.jl +++ b/test/symbolic_events.jl @@ -1423,7 +1423,7 @@ end @set! m.θs[1] = c[] += 1 end], systems = [wd1])) - sys2 = structural_simplify(System([], t; name = :parent, + sys2 = structural_simplify(System(Equation[], t; name = :parent, discrete_events = [0.01 => ModelingToolkit.ImperativeAffect( modified = (; θs = reduce(vcat, [[wd2.θ]])), ctx = [1]) do m, o, c, i @set! m.θs[1] = c[] += 1 From 61aae989cefb6d51eeaecb045a290d8acc2c559d Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Thu, 1 May 2025 15:55:30 +0530 Subject: [PATCH 179/185] refactor: remove old `modelingtoolkitize(::OptimizationProblem)` --- .../optimization/modelingtoolkitize.jl | 148 ------------------ 1 file changed, 148 deletions(-) delete mode 100644 src/systems/optimization/modelingtoolkitize.jl diff --git a/src/systems/optimization/modelingtoolkitize.jl b/src/systems/optimization/modelingtoolkitize.jl deleted file mode 100644 index 27dccc251a..0000000000 --- a/src/systems/optimization/modelingtoolkitize.jl +++ /dev/null @@ -1,148 +0,0 @@ -""" -$(TYPEDSIGNATURES) - -Generate `OptimizationSystem`, dependent variables, and parameters from an `OptimizationProblem`. -""" -function modelingtoolkitize(prob::DiffEqBase.OptimizationProblem; - u_names = nothing, p_names = nothing, kwargs...) - num_cons = isnothing(prob.lcons) ? 0 : length(prob.lcons) - if prob.p isa Tuple || prob.p isa NamedTuple - p = [x for x in prob.p] - else - p = prob.p - end - has_p = !(p isa Union{DiffEqBase.NullParameters, Nothing}) - if u_names !== nothing - varnames_length_check(prob.u0, u_names; is_unknowns = true) - _vars = [variable(name) for name in u_names] - elseif SciMLBase.has_sys(prob.f) - varnames = getname.(variable_symbols(prob.f.sys)) - varidxs = variable_index.((prob.f.sys,), varnames) - invpermute!(varnames, varidxs) - _vars = [variable(name) for name in varnames] - if prob.f.sys isa OptimizationSystem - for (i, sym) in enumerate(variable_symbols(prob.f.sys)) - if hasbounds(sym) - _vars[i] = Symbolics.setmetadata( - _vars[i], VariableBounds, getbounds(sym)) - end - end - end - else - _vars = [variable(:x, i) for i in eachindex(prob.u0)] - end - _vars = reshape(_vars, size(prob.u0)) - vars = ArrayInterface.restructure(prob.u0, _vars) - if prob.ub !== nothing # lb is also !== nothing - vars = map(vars, prob.lb, prob.ub) do sym, lb, ub - if iszero(lb) && iszero(ub) || isinf(lb) && lb < 0 && isinf(ub) && ub > 0 - sym - else - Symbolics.setmetadata(sym, VariableBounds, (lb, ub)) - end - end - end - params = if has_p - if p_names === nothing && SciMLBase.has_sys(prob.f) - p_names = Dict(parameter_index(prob.f.sys, sym) => sym - for sym in parameter_symbols(prob.f.sys)) - end - if p isa MTKParameters - old_to_new = Dict() - for sym in parameter_symbols(prob) - idx = parameter_index(prob, sym) - old_to_new[unwrap(sym)] = unwrap(p_names[idx]) - end - order = reorder_parameters(prob.f.sys) - for arr in order - for i in eachindex(arr) - arr[i] = old_to_new[arr[i]] - end - end - _params = order - else - _params = define_params(p, p_names) - end - p isa Number ? _params[1] : - (p isa Tuple || p isa NamedTuple || p isa AbstractDict || p isa MTKParameters ? - _params : - ArrayInterface.restructure(p, _params)) - else - [] - end - - if p isa MTKParameters - eqs = prob.f(vars, params...) - else - eqs = prob.f(vars, params) - end - - if DiffEqBase.isinplace(prob) && !isnothing(prob.f.cons) - lhs = Array{Num}(undef, num_cons) - if p isa MTKParameters - prob.f.cons(lhs, vars, params...) - else - prob.f.cons(lhs, vars, params) - end - cons = Union{Equation, Inequality}[] - - if !isnothing(prob.lcons) - for i in 1:num_cons - if !isinf(prob.lcons[i]) - if prob.lcons[i] != prob.ucons[i] - push!(cons, prob.lcons[i] ≲ lhs[i]) - else - push!(cons, lhs[i] ~ prob.ucons[i]) - end - end - end - end - - if !isnothing(prob.ucons) - for i in 1:num_cons - if !isinf(prob.ucons[i]) && prob.lcons[i] != prob.ucons[i] - push!(cons, lhs[i] ≲ prob.ucons[i]) - end - end - end - if (isnothing(prob.lcons) || all(isinf, prob.lcons)) && - (isnothing(prob.ucons) || all(isinf, prob.ucons)) - throw(ArgumentError("Constraints passed have no proper bounds defined. - Ensure you pass equal bounds (the scalar that the constraint should evaluate to) for equality constraints - or pass the lower and upper bounds for inequality constraints.")) - end - elseif !isnothing(prob.f.cons) - cons = p isa MTKParameters ? prob.f.cons(vars, params...) : - prob.f.cons(vars, params) - else - cons = [] - end - params = values(params) - params = if params isa Number || (params isa Array && ndims(params) == 0) - [params[1]] - elseif p isa MTKParameters - reduce(vcat, params) - else - vec(collect(params)) - end - - sts = vec(collect(vars)) - default_u0 = Dict(sts .=> vec(collect(prob.u0))) - default_p = if has_p - if prob.p isa AbstractDict - Dict(v => prob.p[k] for (k, v) in pairs(_params)) - elseif prob.p isa MTKParameters - Dict(params .=> reduce(vcat, prob.p)) - else - Dict(params .=> vec(collect(prob.p))) - end - else - Dict() - end - de = OptimizationSystem(eqs, sts, params; - name = gensym(:MTKizedOpt), - constraints = cons, - defaults = merge(default_u0, default_p), - kwargs...) - de -end From 41505780cebd76cf0738f6074588417872f77a1d Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Thu, 1 May 2025 15:55:40 +0530 Subject: [PATCH 180/185] feat: add `modelingtoolkitize(::OptimizationProblem)` --- src/ModelingToolkit.jl | 3 +- src/modelingtoolkitize/optimizationproblem.jl | 86 +++++++++++++++++++ 2 files changed, 87 insertions(+), 2 deletions(-) create mode 100644 src/modelingtoolkitize/optimizationproblem.jl diff --git a/src/ModelingToolkit.jl b/src/ModelingToolkit.jl index 30926c55f2..9a784cd040 100644 --- a/src/ModelingToolkit.jl +++ b/src/ModelingToolkit.jl @@ -181,8 +181,7 @@ include("problems/optimizationproblem.jl") include("modelingtoolkitize/common.jl") include("modelingtoolkitize/odeproblem.jl") include("modelingtoolkitize/sdeproblem.jl") - -include("systems/optimization/modelingtoolkitize.jl") +include("modelingtoolkitize/optimizationproblem.jl") include("systems/nonlinear/homotopy_continuation.jl") include("systems/nonlinear/modelingtoolkitize.jl") diff --git a/src/modelingtoolkitize/optimizationproblem.jl b/src/modelingtoolkitize/optimizationproblem.jl new file mode 100644 index 0000000000..8ed30f4c5a --- /dev/null +++ b/src/modelingtoolkitize/optimizationproblem.jl @@ -0,0 +1,86 @@ +""" + $(TYPEDSIGNATURES) + +Convert an `OptimizationProblem` to a `ModelingToolkit.System`. + +# Keyword arguments + +- `u_names`: An array of names of the same size as `prob.u0` to use as the names of the + unknowns of the system. The names should be given as `Symbol`s. +- `p_names`: A collection of names to use for parameters of the system. The collection + should have keys corresponding to indexes of `prob.p`. For example, if `prob.p` is an + associative container like `NamedTuple`, then `p_names` should map keys of `prob.p` to + the name that the corresponding parameter should have in the returned system. The names + should be given as `Symbol`s. + +All other keyword arguments are forwarded to the created `System`. +""" +function modelingtoolkitize( + prob::OptimizationProblem; u_names = nothing, p_names = nothing, + kwargs...) + num_cons = isnothing(prob.lcons) ? 0 : length(prob.lcons) + p = prob.p + has_p = !(p isa Union{DiffEqBase.NullParameters, Nothing}) + + vars = construct_vars(prob, nothing, u_names) + params = construct_params(prob, nothing, p_names) + + objective = prob.f(vars, params) + + if prob.f.cons === nothing + cons = [] + else + if DiffEqBase.isinplace(prob.f) + lhs = Array{Num}(undef, num_cons) + prob.f.cons(lhs, vars, params) + else + lhs = prob.f.cons(vars, params) + end + cons = Union{Equation, Inequality}[] + + if !isnothing(prob.lcons) + for i in 1:num_cons + if !isinf(prob.lcons[i]) + if prob.lcons[i] != prob.ucons[i] + push!(cons, prob.lcons[i] ≲ lhs[i]) + else + push!(cons, lhs[i] ~ prob.ucons[i]) + end + end + end + end + + if !isnothing(prob.ucons) + for i in 1:num_cons + if !isinf(prob.ucons[i]) && prob.lcons[i] != prob.ucons[i] + push!(cons, lhs[i] ≲ prob.ucons[i]) + end + end + end + + if (isnothing(prob.lcons) || all(isinf, prob.lcons)) && + (isnothing(prob.ucons) || all(isinf, prob.ucons)) + throw(ArgumentError("Constraints passed have no proper bounds defined. + Ensure you pass equal bounds (the scalar that the constraint should evaluate to) for equality constraints + or pass the lower and upper bounds for inequality constraints.")) + end + end + + # turn `params` into a list of symbolic variables as opposed to + # a parameter object containing symbolic variables. + _params = params + params = vec(collect(values(params))) + + defaults = defaults_from_u0_p(prob, vars, _params, params) + # In case initials crept in, specifically from when we constructed parameters + # using prob.f.sys + filter!(x -> !iscall(x) || !(operation(x) isa Initial), params) + filter!(x -> !iscall(x[1]) || !(operation(x[1]) isa Initial), defaults) + + sts = vec(collect(vars)) + sys = OptimizationSystem(objective, sts, params; + defaults, + constraints = cons, + name = gensym(:MTKizedOpt), + kwargs...) +end From a2917424a2e6a611b4c415e884a6bccee03ab47d Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Thu, 1 May 2025 16:00:59 +0530 Subject: [PATCH 181/185] feat: support time-independent variable declaration in mtkize utils --- src/modelingtoolkitize/common.jl | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/modelingtoolkitize/common.jl b/src/modelingtoolkitize/common.jl index d2f494b1f1..f38da52b39 100644 --- a/src/modelingtoolkitize/common.jl +++ b/src/modelingtoolkitize/common.jl @@ -39,6 +39,10 @@ function define_vars(u, t) [_defvaridx(:x, i)(t) for i in eachindex(u)] end +function define_vars(u, ::Nothing) + [variable(:x, i) for i in eachindex(u)] +end + """ $(TYPEDSIGNATURES) @@ -50,13 +54,21 @@ function construct_vars(prob, t, u_names = nothing) if u_names !== nothing # explicitly provided names varnames_length_check(state_values(prob), u_names; is_unknowns = true) - _vars = [_defvar(name)(t) for name in u_names] + if t === nothing + _vars = [variable(name) for name in u_names] + else + _vars = [_defvar(name)(t) for name in u_names] + end elseif SciMLBase.has_sys(prob.f) # get names from the system varnames = getname.(variable_symbols(prob.f.sys)) varidxs = variable_index.((prob.f.sys,), varnames) invpermute!(varnames, varidxs) - _vars = [_defvar(name)(t) for name in varnames] + if t === nothing + _vars = [variable(name) for name in varnames] + else + _vars = [_defvar(name)(t) for name in varnames] + end else # auto-generate names _vars = define_vars(state_values(prob), t) From 3d01527710f04037613ceb22a139a2e03fef45c2 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Thu, 1 May 2025 16:10:26 +0530 Subject: [PATCH 182/185] feat: support passing `t::Nothing` to `trace_rhs` --- src/modelingtoolkitize/common.jl | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/modelingtoolkitize/common.jl b/src/modelingtoolkitize/common.jl index f38da52b39..9817d993e3 100644 --- a/src/modelingtoolkitize/common.jl +++ b/src/modelingtoolkitize/common.jl @@ -321,18 +321,22 @@ Given a problem `prob`, the symbolic unknowns and params and the independent var trace through `prob.f` and return the resultant expression. """ function trace_rhs(prob, vars, params, t) + args = (vars, params) + if t !== nothing + args = (args..., t) + end # trace prob.f to get equation RHS if SciMLBase.isinplace(prob.f) rhs = ArrayInterface.restructure(prob.u0, similar(vars, Num)) fill!(rhs, 0) if prob.f isa SciMLBase.AbstractSciMLFunction && prob.f.f isa FunctionWrappersWrappers.FunctionWrappersWrapper - prob.f.f.fw[1].obj[](rhs, vars, params, t) + prob.f.f.fw[1].obj[](rhs, args...) else - prob.f(rhs, vars, params, t) + prob.f(rhs, args...) end else - rhs = prob.f(vars, params, t) + rhs = prob.f(args...) end return rhs end From a8eff55aeaba994d68ecef9d432b8d67806721c3 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Thu, 1 May 2025 16:10:57 +0530 Subject: [PATCH 183/185] refactor: remove old `modelingtoolkitize(::NonlinearProblem)` --- src/ModelingToolkit.jl | 1 - src/systems/nonlinear/modelingtoolkitize.jl | 85 --------------------- 2 files changed, 86 deletions(-) delete mode 100644 src/systems/nonlinear/modelingtoolkitize.jl diff --git a/src/ModelingToolkit.jl b/src/ModelingToolkit.jl index 9a784cd040..3fa1783308 100644 --- a/src/ModelingToolkit.jl +++ b/src/ModelingToolkit.jl @@ -184,7 +184,6 @@ include("modelingtoolkitize/sdeproblem.jl") include("modelingtoolkitize/optimizationproblem.jl") include("systems/nonlinear/homotopy_continuation.jl") -include("systems/nonlinear/modelingtoolkitize.jl") include("systems/nonlinear/initializesystem.jl") include("systems/diffeqs/first_order_transform.jl") include("systems/diffeqs/basic_transformations.jl") diff --git a/src/systems/nonlinear/modelingtoolkitize.jl b/src/systems/nonlinear/modelingtoolkitize.jl deleted file mode 100644 index 4ce3769ef7..0000000000 --- a/src/systems/nonlinear/modelingtoolkitize.jl +++ /dev/null @@ -1,85 +0,0 @@ -""" -$(TYPEDSIGNATURES) - -Generate `System`, dependent variables, and parameters from an `NonlinearProblem`. -""" -function modelingtoolkitize( - prob::Union{NonlinearProblem, NonlinearLeastSquaresProblem}; - u_names = nothing, p_names = nothing, kwargs...) - p = prob.p - has_p = !(p isa Union{DiffEqBase.NullParameters, Nothing}) - - if u_names !== nothing - varnames_length_check(prob.u0, u_names; is_unknowns = true) - _vars = [variable(name) for name in u_names] - elseif SciMLBase.has_sys(prob.f) - varnames = getname.(variable_symbols(prob.f.sys)) - varidxs = variable_index.((prob.f.sys,), varnames) - invpermute!(varnames, varidxs) - _vars = [variable(name) for name in varnames] - else - _vars = [variable(:x, i) for i in eachindex(prob.u0)] - end - _vars = reshape(_vars, size(prob.u0)) - - vars = prob.u0 isa Number ? _vars : ArrayInterface.restructure(prob.u0, _vars) - params = if has_p - if p_names === nothing && SciMLBase.has_sys(prob.f) - p_names = Dict(parameter_index(prob.f.sys, sym) => sym - for sym in parameter_symbols(prob.f.sys)) - end - _params = define_params(p, p_names) - p isa Number ? _params[1] : - (p isa Tuple || p isa NamedTuple || p isa AbstractDict || p isa MTKParameters ? - _params : - ArrayInterface.restructure(p, _params)) - else - [] - end - - if DiffEqBase.isinplace(prob) - if prob isa NonlinearLeastSquaresProblem - rhs = ArrayInterface.restructure( - prob.f.resid_prototype, similar(prob.f.resid_prototype, Num)) - prob.f(rhs, vars, params) - eqs = vcat([0.0 ~ rhs[i] for i in 1:length(prob.f.resid_prototype)]...) - else - rhs = ArrayInterface.restructure(prob.u0, similar(vars, Num)) - prob.f(rhs, vars, params) - eqs = vcat([0.0 ~ rhs[i] for i in 1:length(rhs)]...) - end - - else - rhs = prob.f(vars, params) - out_def = prob.f(prob.u0, prob.p) - eqs = vcat([0.0 ~ rhs[i] for i in 1:length(out_def)]...) - end - - sts = vec(collect(vars)) - _params = params - params = values(params) - params = if params isa Number || (params isa Array && ndims(params) == 0) - [params[1]] - else - vec(collect(params)) - end - default_u0 = Dict(sts .=> vec(collect(prob.u0))) - default_p = if has_p - if prob.p isa AbstractDict - Dict(v => prob.p[k] for (k, v) in pairs(_params)) - elseif prob.p isa MTKParameters - Dict(params .=> reduce(vcat, prob.p)) - else - Dict(params .=> vec(collect(prob.p))) - end - else - Dict() - end - - de = System(eqs, sts, params, - defaults = merge(default_u0, default_p); - name = gensym(:MTKizedNonlinProb), - kwargs...) - - de -end From 07a2f0067474da89d472af34ca5b0b4b1cf86fb3 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Thu, 1 May 2025 16:11:07 +0530 Subject: [PATCH 184/185] feat: add `modelingtoolkitize(::NonlinearProblem)` --- src/ModelingToolkit.jl | 1 + src/modelingtoolkitize/nonlinearproblem.jl | 48 ++++++++++++++++++++++ 2 files changed, 49 insertions(+) create mode 100644 src/modelingtoolkitize/nonlinearproblem.jl diff --git a/src/ModelingToolkit.jl b/src/ModelingToolkit.jl index 3fa1783308..a9cf5653f2 100644 --- a/src/ModelingToolkit.jl +++ b/src/ModelingToolkit.jl @@ -182,6 +182,7 @@ include("modelingtoolkitize/common.jl") include("modelingtoolkitize/odeproblem.jl") include("modelingtoolkitize/sdeproblem.jl") include("modelingtoolkitize/optimizationproblem.jl") +include("modelingtoolkitize/nonlinearproblem.jl") include("systems/nonlinear/homotopy_continuation.jl") include("systems/nonlinear/initializesystem.jl") diff --git a/src/modelingtoolkitize/nonlinearproblem.jl b/src/modelingtoolkitize/nonlinearproblem.jl new file mode 100644 index 0000000000..cca41fe74e --- /dev/null +++ b/src/modelingtoolkitize/nonlinearproblem.jl @@ -0,0 +1,48 @@ +""" + $(TYPEDSIGNATURES) + +Convert a `NonlinearProblem` or `NonlinearLeastSquaresProblem` to a +`ModelingToolkit.System`. + +# Keyword arguments + +- `u_names`: An array of names of the same size as `prob.u0` to use as the names of the + unknowns of the system. The names should be given as `Symbol`s. +- `p_names`: A collection of names to use for parameters of the system. The collection + should have keys corresponding to indexes of `prob.p`. For example, if `prob.p` is an + associative container like `NamedTuple`, then `p_names` should map keys of `prob.p` to + the name that the corresponding parameter should have in the returned system. The names + should be given as `Symbol`s. + +All other keyword arguments are forwarded to the created `System`. +""" +function modelingtoolkitize( + prob::Union{NonlinearProblem, NonlinearLeastSquaresProblem}; + u_names = nothing, p_names = nothing, kwargs...) + p = prob.p + has_p = !(p isa Union{DiffEqBase.NullParameters, Nothing}) + + vars = construct_vars(prob, nothing, u_names) + params = construct_params(prob, nothing, p_names) + + rhs = trace_rhs(prob, vars, params, nothing) + eqs = vcat([0 ~ rhs[i] for i in eachindex(prob.u0)]...) + + sts = vec(collect(vars)) + + # turn `params` into a list of symbolic variables as opposed to + # a parameter object containing symbolic variables. + _params = params + params = vec(collect(values(params))) + + defaults = defaults_from_u0_p(prob, vars, _params, params) + # In case initials crept in, specifically from when we constructed parameters + # using prob.f.sys + filter!(x -> !iscall(x) || !(operation(x) isa Initial), params) + filter!(x -> !iscall(x[1]) || !(operation(x[1]) isa Initial), defaults) + + return System(eqs, sts, params; + defaults, + name = gensym(:MTKizedNonlin), + kwargs...) +end From fe373a7aa2c9fdb2278dacdd4cf79e75c515370a Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Thu, 1 May 2025 16:12:05 +0530 Subject: [PATCH 185/185] refactor: remove old `modelingtoolkitize(::ODEProblem)` and `::SDEProblem` --- src/systems/diffeqs/modelingtoolkitize.jl | 303 ---------------------- 1 file changed, 303 deletions(-) delete mode 100644 src/systems/diffeqs/modelingtoolkitize.jl diff --git a/src/systems/diffeqs/modelingtoolkitize.jl b/src/systems/diffeqs/modelingtoolkitize.jl deleted file mode 100644 index cfa3ac43dd..0000000000 --- a/src/systems/diffeqs/modelingtoolkitize.jl +++ /dev/null @@ -1,303 +0,0 @@ -""" -$(TYPEDSIGNATURES) - -Generate `System`, dependent variables, and parameters from an `ODEProblem`. -""" -function modelingtoolkitize( - prob::DiffEqBase.ODEProblem; u_names = nothing, p_names = nothing, kwargs...) - prob.f isa DiffEqBase.AbstractParameterizedFunction && - return prob.f.sys - t = t_nounits - p = prob.p - has_p = !(p isa Union{DiffEqBase.NullParameters, Nothing}) - - if u_names !== nothing - varnames_length_check(prob.u0, u_names; is_unknowns = true) - _vars = [_defvar(name)(t) for name in u_names] - elseif SciMLBase.has_sys(prob.f) - varnames = getname.(variable_symbols(prob.f.sys)) - varidxs = variable_index.((prob.f.sys,), varnames) - invpermute!(varnames, varidxs) - _vars = [_defvar(name)(t) for name in varnames] - else - _vars = define_vars(prob.u0, t) - end - - vars = prob.u0 isa Number ? _vars : ArrayInterface.restructure(prob.u0, _vars) - params = if has_p - if p_names === nothing && SciMLBase.has_sys(prob.f) - p_names = Dict(parameter_index(prob.f.sys, sym) => sym - for sym in parameter_symbols(prob.f.sys)) - end - _params = define_params(p, p_names) - p isa Number ? _params[1] : - (p isa Tuple || p isa NamedTuple || p isa AbstractDict || p isa MTKParameters ? - _params : - ArrayInterface.restructure(p, _params)) - else - [] - end - - var_set = Set(vars) - - D = D_nounits - mm = prob.f.mass_matrix - - if mm === I - lhs = map(v -> D(v), vars) - else - lhs = map(mm * vars) do v - if iszero(v) - 0 - elseif v in var_set - D(v) - else - error("Non-permutation mass matrix is not supported.") - end - end - end - - if DiffEqBase.isinplace(prob) - rhs = ArrayInterface.restructure(prob.u0, similar(vars, Num)) - fill!(rhs, 0) - if prob.f isa ODEFunction && - prob.f.f isa FunctionWrappersWrappers.FunctionWrappersWrapper - prob.f.f.fw[1].obj[](rhs, vars, params, t) - else - prob.f(rhs, vars, params, t) - end - else - rhs = prob.f(vars, params, t) - end - - eqs = vcat([lhs[i] ~ rhs[i] for i in eachindex(prob.u0)]...) - - sts = vec(collect(vars)) - - _params = params - params = values(params) - params = if params isa Number || (params isa Array && ndims(params) == 0) - [params[1]] - else - vec(collect(params)) - end - default_u0 = Dict(sts .=> vec(collect(prob.u0))) - default_p = if has_p - if prob.p isa AbstractDict - Dict(v => prob.p[k] for (k, v) in pairs(_params)) - elseif prob.p isa MTKParameters - Dict(params .=> reduce(vcat, prob.p)) - else - Dict(params .=> vec(collect(prob.p))) - end - else - Dict() - end - filter!(x -> !iscall(x) || !(operation(x) isa Initial), params) - filter!(x -> !iscall(x[1]) || !(operation(x[1]) isa Initial), default_p) - de = System(eqs, t, sts, params, - defaults = merge(default_u0, default_p); - name = gensym(:MTKizedODE), - tspan = prob.tspan, - kwargs...) - - de -end - -_defvaridx(x, i) = variable(x, i, T = SymbolicUtils.FnType{Tuple, Real}) -_defvar(x) = variable(x, T = SymbolicUtils.FnType{Tuple, Real}) - -function define_vars(u, t) - [_defvaridx(:x, i)(t) for i in eachindex(u)] -end - -function define_vars(u::NTuple{<:Number}, t) - tuple((_defvaridx(:x, i)(ModelingToolkit.value(t)) for i in eachindex(u))...) -end - -function define_vars(u::NamedTuple, t) - NamedTuple(x => _defvar(x)(ModelingToolkit.value(t)) for x in keys(u)) -end - -const PARAMETERS_NOT_SUPPORTED_MESSAGE = """ - The chosen parameter type is currently not supported by `modelingtoolkitize`. The - current supported types are: - - - AbstractArrays - - AbstractDicts - - LabelledArrays (SLArray, LArray) - - Flat tuples (tuples of numbers) - - Flat named tuples (namedtuples of numbers) - """ - -struct ModelingtoolkitizeParametersNotSupportedError <: Exception - type::Any -end - -function Base.showerror(io::IO, e::ModelingtoolkitizeParametersNotSupportedError) - println(io, PARAMETERS_NOT_SUPPORTED_MESSAGE) - print(io, "Parameter type: ") - println(io, e.type) -end - -function varnames_length_check(vars, names; is_unknowns = false) - if length(names) != length(vars) - throw(ArgumentError(""" - Number of $(is_unknowns ? "unknowns" : "parameters") ($(length(vars))) \ - does not match number of names ($(length(names))). - """)) - end -end - -function define_params(p, _ = nothing) - throw(ModelingtoolkitizeParametersNotSupportedError(typeof(p))) -end - -function define_params(p::AbstractArray, names = nothing) - if names === nothing - [toparam(variable(:α, i)) for i in eachindex(p)] - else - varnames_length_check(p, names) - [toparam(variable(names[i])) for i in eachindex(p)] - end -end - -function define_params(p::Number, names = nothing) - if names === nothing - [toparam(variable(:α))] - elseif names isa Union{AbstractArray, AbstractDict} - varnames_length_check(p, names) - [toparam(variable(names[i])) for i in eachindex(p)] - else - [toparam(variable(names))] - end -end - -function define_params(p::AbstractDict, names = nothing) - if names === nothing - OrderedDict(k => toparam(variable(:α, i)) for (i, k) in zip(1:length(p), keys(p))) - else - varnames_length_check(p, names) - OrderedDict(k => toparam(variable(names[k])) for k in keys(p)) - end -end - -function define_params(p::Tuple, names = nothing) - if names === nothing - tuple((toparam(variable(:α, i)) for i in eachindex(p))...) - else - varnames_length_check(p, names) - tuple((toparam(variable(names[i])) for i in eachindex(p))...) - end -end - -function define_params(p::NamedTuple, names = nothing) - if names === nothing - NamedTuple(x => toparam(variable(x)) for x in keys(p)) - else - varnames_length_check(p, names) - NamedTuple(x => toparam(variable(names[x])) for x in keys(p)) - end -end - -function define_params(p::MTKParameters, names = nothing) - if names === nothing - bufs = (p...,) - i = 1 - ps = [] - for buf in bufs - for _ in buf - push!( - ps, - toparam(variable(:α, i)) - ) - end - end - return identity.(ps) - else - new_p = as_any_buffer(p) - for (k, v) in names - new_p[k] = v - end - return reduce(vcat, new_p; init = []) - end -end - -""" -$(TYPEDSIGNATURES) - -Generate `System`, dependent variables, and parameters from an `SDEProblem`. -""" -function modelingtoolkitize(prob::DiffEqBase.SDEProblem; kwargs...) - prob.f isa DiffEqBase.AbstractParameterizedFunction && - return (prob.f.sys, prob.f.sys.unknowns, prob.f.sys.ps) - @independent_variables t - p = prob.p - has_p = !(p isa Union{DiffEqBase.NullParameters, Nothing}) - - _vars = define_vars(prob.u0, t) - - vars = prob.u0 isa Number ? _vars : ArrayInterface.restructure(prob.u0, _vars) - params = if has_p - _params = define_params(p) - p isa MTKParameters ? _params : - p isa Number ? _params[1] : - (p isa Tuple || p isa NamedTuple ? _params : - ArrayInterface.restructure(p, _params)) - else - [] - end - - D = Differential(t) - - rhs = [D(var) for var in vars] - - if DiffEqBase.isinplace(prob) - lhs = similar(vars, Any) - - prob.f(lhs, vars, params, t) - - if DiffEqBase.is_diagonal_noise(prob) - neqs = similar(vars, Any) - prob.g(neqs, vars, params, t) - else - neqs = similar(vars, Any, size(prob.noise_rate_prototype)) - prob.g(neqs, vars, params, t) - end - else - lhs = prob.f(vars, params, t) - if DiffEqBase.is_diagonal_noise(prob) - neqs = prob.g(vars, params, t) - else - neqs = prob.g(vars, params, t) - end - end - deqs = vcat([rhs[i] ~ lhs[i] for i in eachindex(prob.u0)]...) - - params = if ndims(params) == 0 - [params[1]] - else - Vector(vec(params)) - end - sts = Vector(vec(vars)) - default_u0 = Dict(sts .=> vec(collect(prob.u0))) - default_p = if has_p - if prob.p isa AbstractDict - Dict(v => prob.p[k] for (k, v) in pairs(_params)) - elseif prob.p isa MTKParameters - Dict(params .=> reduce(vcat, prob.p)) - else - Dict(params .=> vec(collect(prob.p))) - end - else - Dict() - end - - de = System(deqs, t, sts, params; noise_eqs = neqs, - name = gensym(:MTKizedSDE), - tspan = prob.tspan, - defaults = merge(default_u0, default_p), - kwargs...) - - de -end