diff --git a/.gitignore b/.gitignore index 3401a5a4b3..6d305ad348 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,5 @@ Manifest.toml .vscode .vscode/* +docs/src/assets/Project.toml +docs/src/assets/Manifest.toml diff --git a/Project.toml b/Project.toml index 3834488ae3..ca73a90799 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "ModelingToolkit" uuid = "961ee093-0014-501f-94e3-6117800e7a78" authors = ["Yingbo Ma ", "Chris Rackauckas and contributors"] -version = "8.71.2" +version = "8.72.0" [deps] AbstractTrees = "1520ce14-60c1-5f80-bbc7-55ef81b5835c" diff --git a/docs/pages.jl b/docs/pages.jl index 0b6d0555cd..f6f5d7af50 100644 --- a/docs/pages.jl +++ b/docs/pages.jl @@ -5,6 +5,7 @@ pages = [ "tutorials/nonlinear.md", "tutorials/optimization.md", "tutorials/modelingtoolkitize.md", + "tutorials/programmatically_generating.md", "tutorials/stochastic_diffeq.md", "tutorials/parameter_identifiability.md", "tutorials/domain_connections.md"], diff --git a/docs/src/tutorials/acausal_components.md b/docs/src/tutorials/acausal_components.md index c2a469eea3..cbee878ec6 100644 --- a/docs/src/tutorials/acausal_components.md +++ b/docs/src/tutorials/acausal_components.md @@ -24,8 +24,8 @@ using ModelingToolkit, Plots, DifferentialEquations @variables t @connector Pin begin - v(t) = 1.0 - i(t) = 1.0, [connect = Flow] + v(t) + i(t), [connect = Flow] end @mtkmodel Ground begin @@ -43,8 +43,8 @@ end n = Pin() end @variables begin - v(t) = 1.0 - i(t) = 1.0 + v(t) + i(t) end @equations begin v ~ p.v - n.v @@ -56,7 +56,7 @@ end @mtkmodel Resistor begin @extend v, i = oneport = OnePort() @parameters begin - R = 1.0 + R = 1.0 # Sets the default resistance end @equations begin v ~ i * R @@ -100,13 +100,11 @@ end end end -@named rc_model = RCModel(resistor.R = 2.0) -rc_model = complete(rc_model) -sys = structural_simplify(rc_model) +@mtkbuild rc_model = RCModel(resistor.R = 2.0) u0 = [ rc_model.capacitor.v => 0.0, ] -prob = ODAEProblem(sys, u0, (0, 10.0)) +prob = ODAEProblem(rc_model, u0, (0, 10.0)) sol = solve(prob, Tsit5()) plot(sol) ``` @@ -131,8 +129,8 @@ default, variables are equal in a connection. ```@example acausal @connector Pin begin - v(t) = 1.0 - i(t) = 1.0, [connect = Flow] + v(t) + i(t), [connect = Flow] end ``` @@ -175,8 +173,8 @@ pin. n = Pin() end @variables begin - v(t) = 1.0 - i(t) = 1.0 + v(t) + i(t) end @equations begin v ~ p.v - n.v @@ -276,7 +274,7 @@ We can create a RCModel component with `@named`. And using `subcomponent_name.pa the parameters or defaults values of variables of subcomponents. ```@example acausal -@named rc_model = RCModel(resistor.R = 2.0) +@mtkbuild rc_model = RCModel(resistor.R = 2.0) ``` This model is acausal because we have not specified anything about the causality of the model. We have @@ -300,26 +298,15 @@ and the parameters are: parameters(rc_model) ``` -## Simplifying and Solving this System - -This system could be solved directly as a DAE using [one of the DAE solvers -from DifferentialEquations.jl](https://docs.sciml.ai/DiffEqDocs/stable/solvers/dae_solve/). -However, let's take a second to symbolically simplify the system before doing the -solve. Although we can use ODE solvers that handle mass matrices to solve the -above system directly, we want to run the `structural_simplify` function first, -as it eliminates many unnecessary variables to build the leanest numerical -representation of the system. Let's see what it does here: +The observed equations are: ```@example acausal -sys = structural_simplify(rc_model) -equations(sys) +observed(rc_model) ``` -```@example acausal -states(sys) -``` +## Solving this System -After structural simplification, we are left with a system of only two equations +We are left with a system of only two equations with two state variables. One of the equations is a differential equation, while the other is an algebraic equation. We can then give the values for the initial conditions of our states, and solve the system by converting it to @@ -331,12 +318,12 @@ This is done as follows: u0 = [rc_model.capacitor.v => 0.0 rc_model.capacitor.p.i => 0.0] -prob = ODEProblem(sys, u0, (0, 10.0)) +prob = ODEProblem(rc_model, u0, (0, 10.0)) sol = solve(prob, Rodas4()) plot(sol) ``` -Since we have run `structural_simplify`, MTK can numerically solve all the +MTK can numerically solve all the unreduced algebraic equations using the `ODAEProblem` (note the letter `A`): @@ -344,7 +331,7 @@ letter `A`): u0 = [ rc_model.capacitor.v => 0.0, ] -prob = ODAEProblem(sys, u0, (0, 10.0)) +prob = ODAEProblem(rc_model, u0, (0, 10.0)) sol = solve(prob, Rodas4()) plot(sol) ``` @@ -357,7 +344,7 @@ like `structural_simplify` simply change state variables into observables which defined by `observed` equations. ```@example acausal -observed(sys) +observed(rc_model) ``` These are explicit algebraic equations which can then be used to reconstruct diff --git a/docs/src/tutorials/ode_modeling.md b/docs/src/tutorials/ode_modeling.md index dd5875da7c..2782e6357f 100644 --- a/docs/src/tutorials/ode_modeling.md +++ b/docs/src/tutorials/ode_modeling.md @@ -1,8 +1,17 @@ # Getting Started with ModelingToolkit.jl -This is an introductory tutorial for ModelingToolkit (MTK). -Some examples of Ordinary Differential Equations (ODE) are used to -illustrate the basic user-facing functionality. +This is an introductory tutorial for ModelingToolkit (MTK). We will demonstrate +the basics of the package by demonstrating how to define and simulate simple +Ordinary Differential Equation (ODE) systems. + +## Installing ModelingToolkit + +To install ModelingToolkit, use the Julia package manager. This can be done as follows: + +```julia +using Pkg +Pkg.add("ModelingToolkit") +``` ## Copy-Pastable Simplified Example @@ -22,21 +31,14 @@ D = Differential(t) @variables begin x(t) # dependent variables end - @structural_parameters begin - h = 1 - end @equations begin - D(x) ~ (h - x) / τ + D(x) ~ (1 - x) / τ end end using DifferentialEquations: solve - -@named fol = FOL() -fol = complete(fol) - +@mtkbuild fol = FOL() prob = ODEProblem(fol, [fol.x => 0.0], (0.0, 10.0), [fol.τ => 3.0]) -# parameter `τ` can be assigned a value, but structural parameter `h` cannot'. sol = solve(prob) using Plots @@ -58,7 +60,7 @@ Here, ``t`` is the independent variable (time), ``x(t)`` is the (scalar) state variable, ``f(t)`` is an external forcing function, and ``\tau`` is a parameter. In MTK, this system can be modelled as follows. For simplicity, we -first set the forcing function to a time-independent value ``h``. And the +first set the forcing function to a time-independent value ``1``. And the independent variable ``t`` is automatically added by ``@mtkmodel``. ```@example ode2 @@ -74,32 +76,17 @@ D = Differential(t) @variables begin x(t) # dependent variables end - @structural_parameters begin - h = 1 - end @equations begin - D(x) ~ (h - x) / τ + D(x) ~ (1 - x) / τ end end -@named fol_incomplete = FOL() -fol = complete(fol_incomplete) +@mtkbuild fol = FOL() ``` Note that equations in MTK use the tilde character (`~`) as equality sign. -`@named` creates an instance of `FOL` named as `fol`. Before creating an -ODEProblem with `fol` run `complete`. Once the system is complete, it will no -longer namespace its subsystems or variables. This is necessary to correctly pass -the intial values of states and parameters to the ODEProblem. - -```julia -julia> fol_incomplete.x -fol_incomplete₊x(t) - -julia> fol.x -x(t) -``` +`@mtkbuild` creates an instance of `FOL` named as `fol`. After construction of the ODE, you can solve it using [DifferentialEquations.jl](https://docs.sciml.ai/DiffEqDocs/stable/): @@ -115,22 +102,6 @@ The initial state and the parameter values are specified using a mapping from the actual symbolic elements to their values, represented as an array of `Pair`s, which are constructed using the `=>` operator. -## Non-DSL way of defining an ODESystem - -Using `@mtkmodel` is the preferred way of defining ODEs with MTK. However, let us -look at how we can define the same system without `@mtkmodel`. This is useful for -defining PDESystem etc. - -```@example first-mtkmodel -@variables t x(t) # independent and dependent variables -@parameters τ # parameters -@constants h = 1 # constants -D = Differential(t) # define an operator for the differentiation w.r.t. time - -# your first ODE, consisting of a single equation, indicated by ~ -@named fol_model = ODESystem(D(x) ~ (h - x) / τ) -``` - ## Algebraic relations and structural simplification You could separate the calculation of the right-hand side, by introducing an @@ -147,46 +118,40 @@ using ModelingToolkit x(t) # dependent variables RHS(t) end - @structural_parameters begin - h = 1 - end begin D = Differential(t) end @equations begin - RHS ~ (h - x) / τ + RHS ~ (1 - x) / τ D(x) ~ RHS end end -@named fol_separate = FOL() +@mtkbuild fol = FOL() ``` -To directly solve this system, you would have to create a Differential-Algebraic -Equation (DAE) problem, since besides the differential equation, there is an -additional algebraic equation now. However, this DAE system can obviously be -transformed into the single ODE we used in the first example above. MTK achieves -this by structural simplification: +You can look at the equations by using the command `equations`: ```@example ode2 -fol_simplified = structural_simplify(complete(fol_separate)) -equations(fol_simplified) +equations(fol) ``` +Notice that there is only one equation in this system, `Differential(t)(x(t)) ~ RHS(t)`. +The other equation was removed from the system and was transformed into an `observed` +variable. Observed equations are variables which can be computed on-demand but are not +necessary for the solution of the system, and thus MTK tracks it separately. One can +check the observed equations via the `observed` function: + ```@example ode2 -equations(fol_simplified) == equations(fol) +observed(fol) ``` -You can extract the equations from a system using `equations` (and, in the same -way, `states` and `parameters`). The simplified equation is exactly the same -as the original one, so the simulation performance will also be the same. -However, there is one difference. MTK does keep track of the eliminated -algebraic variables as "observables" (see -[Observables and Variable Elimination](@ref)). -That means, MTK still knows how to calculate them out of the information available +For more information on this process, see [Observables and Variable Elimination](@ref). + +MTK still knows how to calculate them out of the information available in a simulation result. The intermediate variable `RHS` therefore can be plotted -along with the state variable. Note that this has to be requested explicitly, -through: +along with the state variable. Note that this has to be requested explicitly +like as follows: ```@example ode2 prob = ODEProblem(fol_simplified, @@ -197,16 +162,26 @@ sol = solve(prob) plot(sol, vars = [fol_simplified.x, fol_simplified.RHS]) ``` -By default, `structural_simplify` also replaces symbolic `constants` with -their default values. This allows additional simplifications not possible -when using `parameters` (e.g., solution of linear equations by dividing out -the constant's value, which cannot be done for parameters, since they may -be zero). +## Named Indexing of Solutions + +Note that the indexing of the solution similarly works via the names, and so to get +the time series for `x`, one would do: + +```@example ode2 +sol[fol.x] +``` + +or to get the second value in the time series for `x`: + +```@example ode2 +sol[fol.x, 2] +``` -Note that the indexing of the solution similarly works via the names, and so -`sol[x]` gives the time-series for `x`, `sol[x,2:10]` gives the 2nd through 10th -values of `x` matching `sol.t`, etc. Note that this works even for variables -which have been eliminated, and thus `sol[RHS]` retrieves the values of `RHS`. +Similarly, the time series for `RHS` can be retrieved using the same indexing: + +```@example ode2 +sol[fol.RHS] +``` ## Specifying a time-variable forcing function @@ -222,9 +197,6 @@ Obviously, one could use an explicit, symbolic function of time: x(t) # dependent variables f(t) end - @structural_parameters begin - h = 1 - end begin D = Differential(t) end @@ -445,14 +417,9 @@ using the structural information. For more information, see the Here are some notes that may be helpful during your initial steps with MTK: - - Sometimes, the symbolic engine within MTK cannot correctly identify the - independent variable (e.g. time) out of all variables. In such a case, you - usually get an error that some variable(s) is "missing from variable map". In - most cases, it is then sufficient to specify the independent variable as second - argument to `ODESystem`, e.g. `ODESystem(eqs, t)`. - - A completely macro-free usage of MTK is possible and is discussed in a - separate tutorial. This is for package developers, since the macros are only - essential for automatic symbolic naming for modelers. + - The `@mtkmodel` macro is for high-level usage of MTK. However, in many cases you + may need to programmatically generate `ODESystem`s. If that's the case, check out + the [Programmatically Generating and Scripting ODESystems Tutorial](@ref programmatically). - Vector-valued parameters and variables are possible. A cleaner, more consistent treatment of these is still a work in progress, however. Once finished, this introductory tutorial will also cover this feature. diff --git a/docs/src/tutorials/programmatically_generating.md b/docs/src/tutorials/programmatically_generating.md new file mode 100644 index 0000000000..ca11243c84 --- /dev/null +++ b/docs/src/tutorials/programmatically_generating.md @@ -0,0 +1,106 @@ +# [Programmatically Generating and Scripting ODESystems](@id programmatically) + +In the following tutorial we will discuss how to programmatically generate `ODESystem`s. +This is for cases where one is writing functions that generating `ODESystem`s, for example +if implementing a reader which parses some file format to generate an `ODESystem` (for example, +SBML), or for writing functions that transform an `ODESystem` (for example, if you write a +function that log-transforms a variable in an `ODESystem`). + +## The Representation of a ModelingToolkit System + +ModelingToolkit is built on [Symbolics.jl](https://symbolics.juliasymbolics.org/dev/), +a symbolic Computer Algebra System (CAS) developed in Julia. As such, all CAS functionality +is available on ModelingToolkit systems, such as symbolic differentiation, Groebner basis +calculations, and whatever else you can think of. Under the hood, all ModelingToolkit +variables and expressions are Symbolics.jl variables and expressions. Thus when scripting +a ModelingToolkit system, one simply needs to generate Symbolics.jl variables and equations +as demonstrated in the Symbolics.jl documentation. This looks like: + +```@example scripting +using Symbolics +@variables t x(t) y(t) # Define variables +D = Differential(t) # Define a differential operator +eqs = [D(x) ~ y + D(y) ~ x] # Define an array of equations +``` + +## The Non-DSL (non-`@mtkmodel`) Way of Defining an ODESystem + +Using `@mtkmodel` is the preferred way of defining ODEs with MTK. However, let us +look at how we can define the same system without `@mtkmodel`. This is useful for +defining PDESystem etc. + +```@example scripting +using ModelingToolkit + +@variables t x(t) # independent and dependent variables +@parameters τ # parameters +@constants h = 1 # constants +D = Differential(t) # define an operator for the differentiation w.r.t. time +eqs = [D(x) ~ (h - x) / τ] # create an array of equations + +# your first ODE, consisting of a single equation, indicated by ~ +@named fol_model = ODESystem(eqs, t) + +# Perform the standard transformations and mark the model complete +# Note: Complete models cannot be subsystems of other models! +fol_model = complete(structural_simplify(fol_model)) +``` + +As you can see, generating an ODESystem is as simple as creating the array of equations +and passing it to the `ODESystem` constructor. + +## Understanding the Difference Between the Julia Variable and the Symbolic Variable + +In the most basic usage of ModelingToolkit and Symbolics, the name of the Julia variable +and the symbolic variable are the same. For example, when we do: + +```@example scripting +@variables a +``` + +the name of the symbolic variable is `a` and same with the Julia variable. However, we can +de-couple these by setting `a` to a new symbolic variable, for example: + +```@example scripting +b = only(@variables(a)) +``` + +Now the Julia variable `b` refers to the variable named `a`. However, the downside of this current +approach is that it requires that the user writing the script knows the name `a` that they want to +place to the variable. But what if for example we needed to get the variable's name from a file? + +To do this, one can interpolate a symbol into the `@variables` macro using `$`. For example: + +```@example scripting +a = :c +b = only(@variables($a)) +``` + +In this example, `@variables($a)` created a variable named `c`, and set this variable to `b`. + +Variables are not the only thing with names. For example, when you build a system, it knows its name +that name is used in the namespacing. In the standard usage, again the Julia variable and the +symbolic name are made the same via: + +```@example scripting +@named fol_model = ODESystem(eqs, t) +``` + +However, one can decouple these two properties by noting that `@named` is simply shorthand for the +following: + +```@example scripting +fol_model = ODESystem(eqs, t; name = :fol_model) +``` + +Thus if we had read a name from a file and wish to populate an `ODESystem` with said name, we could do: + +```@example scripting +namesym = :name_from_file +fol_model = ODESystem(eqs, t; name = namesym) +``` + +## Warning About Mutation + +Be advsied that it's never a good idea to mutate an `ODESystem`, or any `AbstractSystem`. diff --git a/src/ModelingToolkit.jl b/src/ModelingToolkit.jl index 1a6e0356a5..de63a67837 100644 --- a/src/ModelingToolkit.jl +++ b/src/ModelingToolkit.jl @@ -201,7 +201,7 @@ export JumpProblem, DiscreteProblem export NonlinearSystem, OptimizationSystem, ConstraintsSystem export alias_elimination, flatten export connect, domain_connect, @connector, Connection, Flow, Stream, instream -export @component, @mtkmodel +export @component, @mtkmodel, @mtkbuild export isinput, isoutput, getbounds, hasbounds, isdisturbance, istunable, getdist, hasdist, tunable_parameters, isirreducible, getdescription, hasdescription, isbinaryvar, isintegervar diff --git a/src/systems/abstractsystem.jl b/src/systems/abstractsystem.jl index ab68cc9e7e..37f4a49e23 100644 --- a/src/systems/abstractsystem.jl +++ b/src/systems/abstractsystem.jl @@ -230,7 +230,8 @@ for prop in [:eqs :gui_metadata :discrete_subsystems :unknown_states - :split_idxs] + :split_idxs + :parent] fname1 = Symbol(:get_, prop) fname2 = Symbol(:has_, prop) @eval begin @@ -307,6 +308,9 @@ function Base.propertynames(sys::AbstractSystem; private = false) end function Base.getproperty(sys::AbstractSystem, name::Symbol; namespace = !iscomplete(sys)) + if has_parent(sys) && (parent = get_parent(sys); parent !== nothing) + sys = parent + end wrap(getvar(sys, name; namespace = namespace)) end function getvar(sys::AbstractSystem, name::Symbol; namespace = !iscomplete(sys)) @@ -1180,6 +1184,15 @@ macro component(expr) esc(component_post_processing(expr, false)) end +macro mtkbuild(expr) + named_expr = ModelingToolkit.named_expr(expr) + name = named_expr.args[1] + esc(quote + $named_expr + $name = $structural_simplify($name) + end) +end + """ $(SIGNATURES) diff --git a/src/systems/diffeqs/odesystem.jl b/src/systems/diffeqs/odesystem.jl index 3b828702c2..9259953dfc 100644 --- a/src/systems/diffeqs/odesystem.jl +++ b/src/systems/diffeqs/odesystem.jl @@ -143,6 +143,10 @@ struct ODESystem <: AbstractODESystem split_idxs: a vector of vectors of indices for the split parameters. """ split_idxs::Union{Nothing, Vector{Vector{Int}}} + """ + parent: the hierarchical parent system before simplification. + """ + parent::Any function ODESystem(tag, deqs, iv, dvs, ps, tspan, var_to_name, ctrls, observed, tgrad, jac, ctrl_jac, Wfact, Wfact_t, name, systems, defaults, @@ -151,7 +155,7 @@ struct ODESystem <: AbstractODESystem tearing_state = nothing, substitutions = nothing, complete = false, discrete_subsystems = nothing, unknown_states = nothing, - split_idxs = nothing; checks::Union{Bool, Int} = true) + split_idxs = nothing, parent = nothing; checks::Union{Bool, Int} = true) if checks == true || (checks & CheckComponents) > 0 check_variables(dvs, iv) check_parameters(ps, iv) @@ -165,7 +169,7 @@ struct ODESystem <: AbstractODESystem ctrl_jac, Wfact, Wfact_t, name, systems, defaults, torn_matching, connector_type, preface, cevents, devents, metadata, gui_metadata, tearing_state, substitutions, complete, discrete_subsystems, - unknown_states, split_idxs) + unknown_states, split_idxs, parent) end end diff --git a/src/systems/diffeqs/sdesystem.jl b/src/systems/diffeqs/sdesystem.jl index 30d0e10666..8c42f7ea0d 100644 --- a/src/systems/diffeqs/sdesystem.jl +++ b/src/systems/diffeqs/sdesystem.jl @@ -115,13 +115,17 @@ struct SDESystem <: AbstractODESystem complete: if a model `sys` is complete, then `sys.x` no longer performs namespacing. """ complete::Bool + """ + parent: the hierarchical parent system before simplification. + """ + parent::Any function SDESystem(tag, deqs, neqs, iv, dvs, ps, tspan, var_to_name, ctrls, observed, tgrad, jac, ctrl_jac, Wfact, Wfact_t, name, systems, defaults, connector_type, cevents, devents, metadata = nothing, gui_metadata = nothing, - complete = false; + complete = false, parent = nothing; checks::Union{Bool, Int} = true) if checks == true || (checks & CheckComponents) > 0 check_variables(dvs, iv) @@ -135,7 +139,7 @@ struct SDESystem <: AbstractODESystem new(tag, deqs, neqs, iv, dvs, ps, tspan, var_to_name, ctrls, observed, tgrad, jac, ctrl_jac, Wfact, Wfact_t, name, systems, defaults, connector_type, cevents, devents, - metadata, gui_metadata, complete) + metadata, gui_metadata, complete, parent) end end diff --git a/src/systems/discrete_system/discrete_system.jl b/src/systems/discrete_system/discrete_system.jl index c0307c586b..ecd0cae8c6 100644 --- a/src/systems/discrete_system/discrete_system.jl +++ b/src/systems/discrete_system/discrete_system.jl @@ -86,6 +86,10 @@ struct DiscreteSystem <: AbstractTimeDependentSystem complete: if a model `sys` is complete, then `sys.x` no longer performs namespacing. """ complete::Bool + """ + parent: the hierarchical parent system before simplification. + """ + parent::Any function DiscreteSystem(tag, discreteEqs, iv, dvs, ps, tspan, var_to_name, ctrls, observed, @@ -93,7 +97,7 @@ struct DiscreteSystem <: AbstractTimeDependentSystem systems, defaults, preface, connector_type, metadata = nothing, gui_metadata = nothing, tearing_state = nothing, substitutions = nothing, - complete = false; checks::Union{Bool, Int} = true) + complete = false, parent = nothing; checks::Union{Bool, Int} = true) if checks == true || (checks & CheckComponents) > 0 check_variables(dvs, iv) check_parameters(ps, iv) @@ -105,7 +109,7 @@ struct DiscreteSystem <: AbstractTimeDependentSystem systems, defaults, preface, connector_type, metadata, gui_metadata, - tearing_state, substitutions, complete) + tearing_state, substitutions, complete, parent) end end diff --git a/src/systems/nonlinear/nonlinearsystem.jl b/src/systems/nonlinear/nonlinearsystem.jl index d33a198710..192089b7da 100644 --- a/src/systems/nonlinear/nonlinearsystem.jl +++ b/src/systems/nonlinear/nonlinearsystem.jl @@ -75,18 +75,23 @@ struct NonlinearSystem <: AbstractTimeIndependentSystem complete: if a model `sys` is complete, then `sys.x` no longer performs namespacing. """ complete::Bool + """ + parent: the hierarchical parent system before simplification. + """ + parent::Any function NonlinearSystem(tag, eqs, states, ps, var_to_name, observed, jac, name, systems, defaults, connector_type, metadata = nothing, gui_metadata = nothing, tearing_state = nothing, substitutions = nothing, - complete = false; checks::Union{Bool, Int} = true) + complete = false, parent = nothing; checks::Union{Bool, Int} = true) if checks == true || (checks & CheckUnits) > 0 all_dimensionless([states; ps]) || check_units(eqs) end new(tag, eqs, states, ps, var_to_name, observed, jac, name, systems, defaults, - connector_type, metadata, gui_metadata, tearing_state, substitutions, complete) + connector_type, metadata, gui_metadata, tearing_state, substitutions, complete, + parent) end end diff --git a/src/systems/optimization/optimizationsystem.jl b/src/systems/optimization/optimizationsystem.jl index 2d935aa030..7a9b10cd49 100644 --- a/src/systems/optimization/optimizationsystem.jl +++ b/src/systems/optimization/optimizationsystem.jl @@ -56,10 +56,14 @@ struct OptimizationSystem <: AbstractOptimizationSystem complete: if a model `sys` is complete, then `sys.x` no longer performs namespacing. """ complete::Bool + """ + parent: the hierarchical parent system before simplification. + """ + parent::Any function OptimizationSystem(tag, op, states, ps, var_to_name, observed, constraints, name, systems, defaults, metadata = nothing, - gui_metadata = nothing, complete = false; + gui_metadata = nothing, complete = false, parent = nothing; checks::Union{Bool, Int} = true) if checks == true || (checks & CheckUnits) > 0 unwrap(op) isa Symbolic && check_units(op) @@ -67,7 +71,8 @@ struct OptimizationSystem <: AbstractOptimizationSystem all_dimensionless([states; ps]) || check_units(constraints) end new(tag, op, states, ps, var_to_name, observed, - constraints, name, systems, defaults, metadata, gui_metadata, complete) + constraints, name, systems, defaults, metadata, gui_metadata, complete, + parent) end end diff --git a/src/systems/systems.jl b/src/systems/systems.jl index 7aaaabb917..bdf8f31808 100644 --- a/src/systems/systems.jl +++ b/src/systems/systems.jl @@ -17,6 +17,23 @@ This will convert all `inputs` to parameters and allow them to be unconnected, i simplification will allow models where `n_states = n_equations - n_inputs`. """ function structural_simplify(sys::AbstractSystem, io = nothing; simplify = false, + kwargs...) + newsys′ = __structural_simplify(sys, io; simplify, kwargs...) + if newsys′ isa Tuple + @assert length(newsys′) == 2 + newsys = newsys′[1] + else + newsys = newsys′ + end + @set! newsys.parent = complete(sys) + newsys = complete(newsys) + if newsys′ isa Tuple + return newsys, newsys′[2] + else + return newsys + end +end +function __structural_simplify(sys::AbstractSystem, io = nothing; simplify = false, kwargs...) sys = expand_connections(sys) sys isa DiscreteSystem && return sys diff --git a/test/model_parsing.jl b/test/model_parsing.jl index 3f380df100..c64bfa7b76 100644 --- a/test/model_parsing.jl +++ b/test/model_parsing.jl @@ -1,6 +1,6 @@ using ModelingToolkit, Test using ModelingToolkit: get_gui_metadata, - VariableDescription, getdefault, RegularConnector, get_ps + VariableDescription, getdefault, RegularConnector, get_ps, getname using URIs: URI using Distributions using Unitful @@ -146,7 +146,11 @@ l15 0" stroke="black" stroke-width="1" stroke-linejoin="bevel" fill="none">