diff --git a/src/inputoutput.jl b/src/inputoutput.jl index 19603b76cd..97376a2a44 100644 --- a/src/inputoutput.jl +++ b/src/inputoutput.jl @@ -319,7 +319,7 @@ function inputs_to_parameters!(state::TransformationState, inputsyms) @set! sys.ps = [ps; new_parameters] @set! state.sys = sys - @set! state.fullvars = new_fullvars + @set! state.fullvars = Vector{BasicSymbolic}(new_fullvars) @set! state.structure = structure return state end diff --git a/src/structural_transformation/StructuralTransformations.jl b/src/structural_transformation/StructuralTransformations.jl index 2ba469e26a..15f1f7d2db 100644 --- a/src/structural_transformation/StructuralTransformations.jl +++ b/src/structural_transformation/StructuralTransformations.jl @@ -40,6 +40,7 @@ using ModelingToolkit: algeqs, EquationsView, dervars_range, diffvars_range, algvars_range, DiffGraph, complete!, get_fullvars, system_subset +using SymbolicIndexingInterface: symbolic_type, ArraySymbolic using ModelingToolkit.DiffEqBase using ModelingToolkit.StaticArrays diff --git a/src/structural_transformation/utils.jl b/src/structural_transformation/utils.jl index 2bf316cfa6..e1c8a74eca 100644 --- a/src/structural_transformation/utils.jl +++ b/src/structural_transformation/utils.jl @@ -228,9 +228,15 @@ function find_eq_solvables!(state::TearingState, ieq, to_rm = Int[], coeffs = no all_int_vars = false if !allow_symbolic if allow_parameter - all( - x -> ModelingToolkit.isparameter(x), - vars(a)) || continue + # if any of the variables in `a` are present in fullvars (taking into account arrays) + if any( + v -> any(isequal(v), fullvars) || + symbolic_type(v) == ArraySymbolic() && + Symbolics.shape(v) != Symbolics.Unknown() && + any(x -> any(isequal(x), fullvars), collect(v)), + vars(a)) + continue + end else continue end diff --git a/src/systems/abstractsystem.jl b/src/systems/abstractsystem.jl index 1a90faad92..248768b8d4 100644 --- a/src/systems/abstractsystem.jl +++ b/src/systems/abstractsystem.jl @@ -648,14 +648,14 @@ function (f::Initial)(x) iscall(x) && operation(x) isa Initial && return x result = if symbolic_type(x) == ArraySymbolic() # create an array for `Initial(array)` - Symbolics.array_term(f, toparam(x)) + Symbolics.array_term(f, x) elseif iscall(x) && operation(x) == getindex # instead of `Initial(x[1])` create `Initial(x)[1]` # which allows parameter indexing to handle this case automatically. arr = arguments(x)[1] - term(getindex, f(toparam(arr)), arguments(x)[2:end]...) + term(getindex, f(arr), arguments(x)[2:end]...) else - term(f, toparam(x)) + term(f, x) end # the result should be a parameter result = toparam(result) diff --git a/src/systems/analysis_points.jl b/src/systems/analysis_points.jl index 27a0204cb8..2ce6356aa7 100644 --- a/src/systems/analysis_points.jl +++ b/src/systems/analysis_points.jl @@ -950,7 +950,7 @@ function linearization_function(sys::AbstractSystem, if output isa AnalysisPoint sys, (output_var,) = apply_transformation(AddVariable(output), sys) sys, (input_var,) = apply_transformation(GetInput(output), sys) - push!(get_eqs(sys), output_var ~ input_var) + @set! sys.eqs = [get_eqs(sys); output_var ~ input_var] else output_var = output end diff --git a/src/systems/diffeqs/basic_transformations.jl b/src/systems/diffeqs/basic_transformations.jl index 4bf3b40177..6046e5d4be 100644 --- a/src/systems/diffeqs/basic_transformations.jl +++ b/src/systems/diffeqs/basic_transformations.jl @@ -482,7 +482,7 @@ function noise_to_brownians(sys::System; names::Union{Symbol, Vector{Symbol}} = """)) end brownvars = map(names) do name - only(@brownian $name) + unwrap(only(@brownian $name)) end terms = if ndims(neqs) == 1 diff --git a/src/systems/nonlinear/initializesystem.jl b/src/systems/nonlinear/initializesystem.jl index 0263749957..a13ac9d6f3 100644 --- a/src/systems/nonlinear/initializesystem.jl +++ b/src/systems/nonlinear/initializesystem.jl @@ -214,7 +214,15 @@ function generate_initializesystem_timeindependent(sys::AbstractSystem; initialization_eqs = filter(initialization_eqs) do eq empty!(vs) vars!(vs, eq; op = Initial) - non_params = filter(!isparameter, vs) + allpars = full_parameters(sys) + for p in allpars + if symbolic_type(p) == ArraySymbolic() && + Symbolics.shape(p) != Symbolics.Unknown() + append!(allpars, Symbolics.scalarize(p)) + end + end + allpars = Set(allpars) + non_params = filter(!in(allpars), vs) # error if non-parameters are present in the initialization equations if !isempty(non_params) throw(UnknownsInTimeIndependentInitializationError(eq, non_params)) diff --git a/src/systems/system.jl b/src/systems/system.jl index 940ca39350..c84187feaa 100644 --- a/src/systems/system.jl +++ b/src/systems/system.jl @@ -206,7 +206,7 @@ function System(eqs::Vector{Equation}, iv; kwargs...) diffeqs = Equation[] othereqs = Equation[] for eq in eqs - if !(eq.lhs isa Union{Symbolic, Number}) + if !(eq.lhs isa Union{Symbolic, Number, AbstractArray}) push!(othereqs, eq) continue end diff --git a/src/systems/systemstructure.jl b/src/systems/systemstructure.jl index c1b2c337ac..e8d81e0820 100644 --- a/src/systems/systemstructure.jl +++ b/src/systems/systemstructure.jl @@ -204,7 +204,7 @@ mutable struct TearingState{T <: AbstractSystem} <: AbstractTearingState{T} """The system of equations.""" sys::T """The set of variables of the system.""" - fullvars::Vector + fullvars::Vector{BasicSymbolic} structure::SystemStructure extra_eqs::Vector param_derivative_map::Dict{BasicSymbolic, Any} @@ -254,128 +254,164 @@ function Base.push!(ev::EquationsView, eq) push!(ev.ts.extra_eqs, eq) end -function is_time_dependent_parameter(p, iv) - return iv !== nothing && isparameter(p) && iscall(p) && - (operation(p) === getindex && is_time_dependent_parameter(arguments(p)[1], iv) || +function is_time_dependent_parameter(p, allps, iv) + return iv !== nothing && p in allps && iscall(p) && + (operation(p) === getindex && + is_time_dependent_parameter(arguments(p)[1], allps, iv) || (args = arguments(p); length(args)) == 1 && isequal(only(args), iv)) end +function symbolic_contains(var, set) + var in set || + symbolic_type(var) == ArraySymbolic() && + Symbolics.shape(var) != Symbolics.Unknown() && + all(x -> x in set, Symbolics.scalarize(var)) +end + function TearingState(sys; quick_cancel = false, check = true, sort_eqs = true) + # flatten system sys = flatten(sys) ivs = independent_variables(sys) iv = length(ivs) == 1 ? ivs[1] : nothing - # scalarize array equations, without scalarizing arguments to registered functions - eqs = flatten_equations(copy(equations(sys))) + # flatten array equations + eqs = flatten_equations(equations(sys)) neqs = length(eqs) - dervaridxs = OrderedSet{Int}() - var2idx = Dict{Any, Int}() - symbolic_incidence = [] - fullvars = [] param_derivative_map = Dict{BasicSymbolic, Any}() - var_counter = Ref(0) + # * Scalarize unknowns + dvs = Set{BasicSymbolic}() + fullvars = BasicSymbolic[] + for x in unknowns(sys) + push!(dvs, x) + xx = Symbolics.scalarize(x) + if xx isa AbstractArray + union!(dvs, xx) + end + end + ps = Set{Symbolic}() + for x in full_parameters(sys) + push!(ps, x) + if symbolic_type(x) == ArraySymbolic() && Symbolics.shape(x) != Symbolics.Unknown() + xx = Symbolics.scalarize(x) + union!(ps, xx) + end + end + browns = Set{BasicSymbolic}() + for x in brownians(sys) + push!(browns, x) + xx = Symbolics.scalarize(x) + if xx isa AbstractArray + union!(browns, xx) + end + end + var2idx = Dict{BasicSymbolic, Int}() var_types = VariableType[] - addvar! = let fullvars = fullvars, var_counter = var_counter, var_types = var_types - var -> get!(var2idx, var) do + addvar! = let fullvars = fullvars, dvs = dvs, var2idx = var2idx, var_types = var_types + (var, vtype) -> get!(var2idx, var) do + push!(dvs, var) push!(fullvars, var) - push!(var_types, getvariabletype(var)) - var_counter[] += 1 + push!(var_types, vtype) + return length(fullvars) end end - vars = OrderedSet() - varsvec = [] + # build symbolic incidence + symbolic_incidence = Vector{BasicSymbolic}[] + varsbuf = Set() eqs_to_retain = trues(length(eqs)) - for (i, eq′) in enumerate(eqs) - if eq′.lhs isa Connection - check ? error("$(nameof(sys)) has unexpanded `connect` statements") : - return nothing - end - if iscall(eq′.lhs) && (op = operation(eq′.lhs)) isa Differential && - isequal(op.x, iv) && is_time_dependent_parameter(only(arguments(eq′.lhs)), iv) + for (i, eq) in enumerate(eqs) + if iscall(eq.lhs) && (op = operation(eq.lhs)) isa Differential && + isequal(op.x, iv) && is_time_dependent_parameter(only(arguments(eq.lhs)), ps, iv) # parameter derivatives are opted out by specifying `D(p) ~ missing`, but # we want to store `nothing` in the map because that means `fast_substitute` # will ignore the rule. We will this identify the presence of `eq′.lhs` in # the differentiated expression and error. - param_derivative_map[eq′.lhs] = coalesce(eq′.rhs, nothing) + param_derivative_map[eq.lhs] = coalesce(eq.rhs, nothing) eqs_to_retain[i] = false # change the equation if the RHS is `missing` so the rest of this loop works - eq′ = eq′.lhs ~ coalesce(eq′.rhs, 0.0) + eq = 0.0 ~ coalesce(eq.rhs, 0.0) end - if _iszero(eq′.lhs) - rhs = quick_cancel ? quick_cancel_expr(eq′.rhs) : eq′.rhs - eq = eq′ - else - lhs = quick_cancel ? quick_cancel_expr(eq′.lhs) : eq′.lhs - rhs = quick_cancel ? quick_cancel_expr(eq′.rhs) : eq′.rhs + rhs = quick_cancel ? quick_cancel_expr(eq.rhs) : eq.rhs + if !_iszero(eq.lhs) + lhs = quick_cancel ? quick_cancel_expr(eq.lhs) : eq.lhs eq = 0 ~ rhs - lhs end - vars!(vars, eq.rhs, op = Symbolics.Operator) - for v in vars - _var, _ = var_from_nested_derivative(v) - any(isequal(_var), ivs) && continue - if isparameter(_var) || - (iscall(_var) && isparameter(operation(_var))) - if is_time_dependent_parameter(_var, iv) && - !haskey(param_derivative_map, Differential(iv)(_var)) + empty!(varsbuf) + vars!(varsbuf, eq; op = Symbolics.Operator) + incidence = Set{BasicSymbolic}() + isalgeq = true + for v in varsbuf + # additionally track brownians in fullvars + if v in browns + addvar!(v, BROWNIAN) + push!(incidence, v) + end + + # TODO: Can we handle this without `isparameter`? + if symbolic_contains(v, ps) || + getmetadata(v, SymScope, LocalScope()) isa GlobalScope && isparameter(v) + if is_time_dependent_parameter(v, ps, iv) && + !haskey(param_derivative_map, Differential(iv)(v)) # Parameter derivatives default to zero - they stay constant # between callbacks - param_derivative_map[Differential(iv)(_var)] = 0.0 + param_derivative_map[Differential(iv)(v)] = 0.0 end continue end - v = scalarize(v) - if v isa AbstractArray - append!(varsvec, v) - else - push!(varsvec, v) - end - end - isalgeq = true - unknownvars = [] - for var in varsvec - ModelingToolkit.isdelay(var, iv) && continue - set_incidence = true - @label ANOTHER_VAR - _var, _ = var_from_nested_derivative(var) - any(isequal(_var), ivs) && continue - if isparameter(_var) || - (iscall(_var) && isparameter(operation(_var))) - continue - end - varidx = addvar!(var) - set_incidence && push!(unknownvars, var) - - dvar = var - idx = varidx - while isdifferential(dvar) - if !(idx in dervaridxs) - push!(dervaridxs, idx) + + isequal(v, iv) && continue + isdelay(v, iv) && continue + + if !symbolic_contains(v, dvs) + isvalid = iscall(v) && operation(v) isa Union{Shift, Sample, Hold} + v′ = v + while !isvalid && iscall(v′) && operation(v′) isa Union{Differential, Shift} + v′ = arguments(v′)[1] + if v′ in dvs || getmetadata(v′, SymScope, LocalScope()) isa GlobalScope + isvalid = true + break + end + end + if !isvalid + throw(ArgumentError("$v is present in the system but $v′ is not an unknown.")) + end + + addvar!(v, VARIABLE) + if iscall(v) && operation(v) isa Symbolics.Operator && !isdifferential(v) && + (it = input_timedomain(v)) !== nothing + v′ = only(arguments(v)) + addvar!(setmetadata(v′, VariableTimeDomain, it), VARIABLE) end - isalgeq = false - dvar = arguments(dvar)[1] - idx = addvar!(dvar) end - dvar = var - idx = varidx + isalgeq &= !isdifferential(v) - if iscall(var) && operation(var) isa Symbolics.Operator && - !isdifferential(var) && (it = input_timedomain(var)) !== nothing - set_incidence = false - var = only(arguments(var)) - var = setmetadata(var, VariableTimeDomain, it) - @goto ANOTHER_VAR + if symbolic_type(v) == ArraySymbolic() + vv = collect(v) + union!(incidence, vv) + map(vv) do vi + addvar!(vi, VARIABLE) + end + else + push!(incidence, v) + addvar!(v, VARIABLE) end end - push!(symbolic_incidence, copy(unknownvars)) - empty!(unknownvars) - empty!(vars) - empty!(varsvec) + if isalgeq eqs[i] = eq else eqs[i] = eqs[i].lhs ~ rhs end + push!(symbolic_incidence, collect(incidence)) + end + + dervaridxs = OrderedSet{Int}() + for (i, v) in enumerate(fullvars) + while isdifferential(v) + push!(dervaridxs, i) + v = arguments(v)[1] + i = addvar!(v, VARIABLE) + end end eqs = eqs[eqs_to_retain] neqs = length(eqs) @@ -389,6 +425,7 @@ function TearingState(sys; quick_cancel = false, check = true, sort_eqs = true) symbolic_incidence = symbolic_incidence[sortidxs] end + # Handle shifts - find lowest shift and add intermediates with derivative edges ### Handle discrete variables lowest_shift = Dict() for var in fullvars @@ -422,12 +459,13 @@ function TearingState(sys; quick_cancel = false, check = true, sort_eqs = true) for s in (steps - 1):-1:(lshift + 1) sf = Shift(tt, s) dvar = sf(v) - idx = addvar!(dvar) + idx = addvar!(dvar, VARIABLE) if !(idx in dervaridxs) push!(dervaridxs, idx) end end end + # sort `fullvars` such that the mass matrix is as diagonal as possible. dervaridxs = collect(dervaridxs) sorted_fullvars = OrderedSet(fullvars[dervaridxs]) @@ -451,6 +489,7 @@ function TearingState(sys; quick_cancel = false, check = true, sort_eqs = true) var2idx = Dict(fullvars .=> eachindex(fullvars)) dervaridxs = 1:length(dervaridxs) + # build `var_to_diff` nvars = length(fullvars) diffvars = [] var_to_diff = DiffGraph(nvars, true) @@ -462,6 +501,7 @@ function TearingState(sys; quick_cancel = false, check = true, sort_eqs = true) var_to_diff[diffvaridx] = dervaridx end + # build incidence graph graph = BipartiteGraph(neqs, nvars, Val(false)) for (ie, vars) in enumerate(symbolic_incidence), v in vars jv = var2idx[v] @@ -731,3 +771,21 @@ function _structural_simplify!(state::TearingState; simplify = false, ModelingToolkit.invalidate_cache!(sys) end + +struct DifferentiatedVariableNotUnknownError <: Exception + differentiated::Any + undifferentiated::Any +end + +function Base.showerror(io::IO, err::DifferentiatedVariableNotUnknownError) + undiff = err.undifferentiated + diff = err.differentiated + print(io, + "Variable $undiff occurs differentiated as $diff but is not an unknown of the system.") + scope = getmetadata(undiff, SymScope, LocalScope()) + depth = expected_scope_depth(scope) + if depth > 0 + print(io, + "\nVariable $undiff expects $depth more levels in the hierarchy to be an unknown.") + end +end diff --git a/src/utils.jl b/src/utils.jl index e1e8c50af4..13ebb28b6a 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -124,8 +124,6 @@ function check_parameters(ps, iv) for p in ps isequal(iv, p) && throw(ArgumentError("Independent variable $iv not allowed in parameters.")) - isparameter(p) || - throw(ArgumentError("$p is not a parameter.")) end end @@ -153,8 +151,6 @@ function check_variables(dvs, iv) throw(ArgumentError("Independent variable $iv not allowed in dependent variables.")) (is_delay_var(iv, dv) || occursin(iv, dv)) || throw(ArgumentError("Variable $dv is not a function of independent variable $iv.")) - isparameter(dv) && - throw(ArgumentError("$dv is not an unknown. It is a parameter.")) end end diff --git a/test/components.jl b/test/components.jl index 6102762d01..5160aad6da 100644 --- a/test/components.jl +++ b/test/components.jl @@ -197,7 +197,7 @@ end connect(resistor.heat_port, heat_capacitor.port)] compose(System(rc_eqs, t, name = Symbol(name, i)), - [resistor, capacitor, source, ground, heat_capacitor]) + [resistor, capacitor, source, ground, shape, heat_capacitor]) end V = 2.0 @named shape = Constant(k = V) diff --git a/test/dde.jl b/test/dde.jl index a5a0f6ee0d..cac207caa3 100644 --- a/test/dde.jl +++ b/test/dde.jl @@ -98,7 +98,7 @@ prob_sa = SDDEProblem( function oscillator(; name, k = 1.0, τ = 0.01) @parameters k=k τ=τ - @variables x(..)=0.1 y(t)=0.1 jcn(t)=0.0 delx(t) + @variables x(..)=0.1 y(t)=0.1 jcn(t) delx(t) eqs = [D(x(t)) ~ y, D(y) ~ -k * x(t - τ) + jcn, delx ~ x(t - τ)] diff --git a/test/dq_units.jl b/test/dq_units.jl index f0dc2dbe23..5a635144f5 100644 --- a/test/dq_units.jl +++ b/test/dq_units.jl @@ -129,7 +129,7 @@ sys_simple = structural_simplify(sys) eqs = [L ~ v * t, V ~ L^3] -@named sys = System(eqs, [V, L], [t, r]) +@named sys = System(eqs, [V, L], [t, r, v]) sys_simple = structural_simplify(sys) #Jump System diff --git a/test/error_handling.jl b/test/error_handling.jl index d6c0fa8caa..6b416d9a6e 100644 --- a/test/error_handling.jl +++ b/test/error_handling.jl @@ -27,7 +27,7 @@ function OverdefinedConstantVoltage(; name, V = 1.0, I = 1.0) # Overdefine p.i and n.i n.i ~ I p.i ~ I] - System(eqs, t, [], [V], systems = [p, n], defaults = Dict(V => val, I => val2), + System(eqs, t, [], [V, I], systems = [p, n], defaults = Dict(V => val, I => val2), name = name) end diff --git a/test/nonlinearsystem.jl b/test/nonlinearsystem.jl index a4f72c00de..0609c495bc 100644 --- a/test/nonlinearsystem.jl +++ b/test/nonlinearsystem.jl @@ -108,7 +108,7 @@ lorenz2 = lorenz(:lorenz2) lorenz2.y ~ s * h lorenz1.F ~ lorenz2.u lorenz2.F ~ lorenz1.u], - [s, a], [], + [s, a], [h], systems = [lorenz1, lorenz2]) @test_nowarn alias_elimination(connected) @@ -296,9 +296,9 @@ sys = structural_simplify(ns; conservative = true) # system that contains a chain of observed variables when simplified @variables x y z eqs = [0 ~ x^2 + 2z + y, z ~ y, y ~ x] # analytical solution x = y = z = 0 or -3 - @mtkbuild ns = System(eqs) # solve for y with observed chain z -> x -> y - @test isequal(expand.(calculate_jacobian(ns)), [3 // 2 + y;;]) - @test isequal(calculate_hessian(ns), [[1;;]]) + @mtkbuild ns = System(eqs) # solve for y with observed chain z -> y -> x + @test isequal(expand.(calculate_jacobian(ns)), [-3 // 2 - x;;]) + @test isequal(calculate_hessian(ns), [[-1;;]]) prob = NonlinearProblem(ns, unknowns(ns) .=> -4.0) # give guess < -3 to reach -3 sol = solve(prob, NewtonRaphson()) @test sol[x] ≈ sol[y] ≈ sol[z] ≈ -3 diff --git a/test/odesystem.jl b/test/odesystem.jl index bfad3af0df..abd563b14e 100644 --- a/test/odesystem.jl +++ b/test/odesystem.jl @@ -1390,7 +1390,7 @@ end @parameters c(t) @mtkbuild sys = System([D(x) ~ c * cos(x), obs ~ c], t, - [x], + [x, obs], [c]; discrete_events = [SymbolicDiscreteCallback( 1.0 => [c ~ Pre(c) + 1], discrete_parameters = [c])]) diff --git a/test/sdesystem.jl b/test/sdesystem.jl index 5bdc033498..a3e0ff00d0 100644 --- a/test/sdesystem.jl +++ b/test/sdesystem.jl @@ -792,12 +792,12 @@ end [input = true] end ps = @parameters a = 2 - @brownian η + browns = @brownian η eqs = [D(x) ~ -a * x + (input + 1) * η input ~ 0.0] - sys = System(eqs, t, sts, ps; name = :name) + sys = System(eqs, t, sts, ps, browns; name = :name) sys = structural_simplify(sys) @test ModelingToolkit.get_noise_eqs(sys) ≈ [1.0] prob = SDEProblem(sys, [], (0.0, 1.0), []) diff --git a/test/structural_transformation/tearing.jl b/test/structural_transformation/tearing.jl index f6c4fe5c44..5bac18a253 100644 --- a/test/structural_transformation/tearing.jl +++ b/test/structural_transformation/tearing.jl @@ -58,11 +58,9 @@ graph2vars(graph) = map(is -> Set(map(i -> int2var[i], is)), graph.fadjlist) Set([u4]) Set([u5])] -state = TearingState(tearing(sys)) -let sss = state.structure - @unpack graph = sss - @test graph2vars(graph) == [Set([u1, u2, u5])] -end +newsys = tearing(sys) +@test length(equations(newsys)) == 1 +@test issetequal(ModelingToolkit.vars(equations(newsys)), [u1, u4, u5]) # Before: # u1 u2 u3 u4 u5 diff --git a/test/structural_transformation/utils.jl b/test/structural_transformation/utils.jl index 540cb8fdae..c4f686935a 100644 --- a/test/structural_transformation/utils.jl +++ b/test/structural_transformation/utils.jl @@ -6,6 +6,7 @@ using UnPack using ModelingToolkit: t_nounits as t, D_nounits as D, default_toterm using Symbolics: unwrap using DataInterpolations +using OrdinaryDiffEq, NonlinearSolve, StochasticDiffEq const ST = StructuralTransformations # Define some variables @@ -386,3 +387,52 @@ end @test D(sys.k(t)) in vs end end + +@testset "Don't rely on metadata" begin + @testset "ODESystem" begin + @variables x(t) p + @parameters y(t) q + @mtkbuild sys = System([D(x) ~ x * q, x^2 + y^2 ~ p], t, [x, y], + [p, q]; initialization_eqs = [p + q ~ 3], + defaults = [p => missing], guesses = [p => 1.0, y => 1.0]) + @test length(equations(sys)) == 2 + @test length(parameters(sys)) == 2 + prob = ODEProblem(sys, [x => 1.0], (0.0, 1.0), [q => 2.0]) + integ = init(prob, Rodas5P(); abstol = 1e-10, reltol = 1e-8) + @test integ.ps[p]≈1.0 atol=1e-6 + @test integ[y]≈0.0 atol=1e-5 + end + + @testset "NonlinearSystem" begin + @variables x p + @parameters y q + @mtkbuild sys = System([0 ~ p * x + y, x^3 + y^3 ~ q], [x, y], + [p, q]; initialization_eqs = [p ~ q + 1], + guesses = [p => 1.0], defaults = [p => missing]) + @test length(equations(sys)) == length(unknowns(sys)) == 1 + @test length(observed(sys)) == 1 + @test observed(sys)[1].lhs in Set([x, y]) + @test length(parameters(sys)) == 2 + prob = NonlinearProblem(sys, [x => 1.0, y => 1.0], [q => 1.0]) + integ = init(prob, NewtonRaphson()) + @test prob.ps[p] ≈ 2.0 + end + + @testset "SDESystem" begin + @variables x(t) p a + @parameters y(t) q b + @brownian c + @mtkbuild sys = System([D(x) ~ x + q * a, D(y) ~ y + p * b + c], t, [x, y], + [p, q], [a, b, c]; initialization_eqs = [p + q ~ 4], + guesses = [p => 1.0], defaults = [p => missing]) + @test length(equations(sys)) == 2 + @test issetequal(unknowns(sys), [x, y]) + @test issetequal(parameters(sys), [p, q]) + @test isempty(brownians(sys)) + neqs = ModelingToolkit.get_noise_eqs(sys) + @test issetequal(sum.(eachrow(neqs)), [q, 1 + p]) + prob = SDEProblem(sys, [x => 1.0, y => 1.0], (0.0, 1.0), [q => 1.0]) + integ = init(prob, ImplicitEM()) + @test integ.ps[p] ≈ 3.0 + end +end diff --git a/test/units.jl b/test/units.jl index b7d141f347..f15145ffa9 100644 --- a/test/units.jl +++ b/test/units.jl @@ -156,7 +156,7 @@ sys_simple = structural_simplify(sys) eqs = [L ~ v * t, V ~ L^3] -@named sys = System(eqs, [V, L], [t, r]) +@named sys = System(eqs, [V, L], [v, t, r]) sys_simple = structural_simplify(sys) #Jump System