Model building reference
This page lists functionality and utilities related to building hierarchical models. It is recommended to read the page on the System before this.
Common definitions of t and D
ModelingToolkit provides common definitions for the independent variable t (time) and the derivative with respect to it D.
Missing docstring for ModelingToolkit.t_nounits. Check Documenter's build log for details.
Missing docstring for ModelingToolkit.D_nounits. Check Documenter's build log for details.
Missing docstring for ModelingToolkit.t. Check Documenter's build log for details.
Missing docstring for ModelingToolkit.D. Check Documenter's build log for details.
Missing docstring for ModelingToolkit.t_unitful. Check Documenter's build log for details.
Missing docstring for ModelingToolkit.D_unitful. Check Documenter's build log for details.
Users are recommended to use the appropriate common definition in their models. The required definitions can be imported with convenient aliased names. For example:
using ModelingToolkit: t_nounits as t, D_nounits as DAllows using t and D to refer to t_nounits and D_nounits respectively.
Hierarchical model composition
The System data structure can represent a tree-like hierarchy of systems for building models from composable blocks. The ModelingToolkit.get_systems function can be used for querying the subsystems of a system. The @component macro should be used when writing building blocks for model composition.
ModelingToolkit.@component — MacroMark a system constructor function as building a component. For example,
@component function AddOne(; name)
@variables in(t) out(t)
eqs = [out ~ in + 1]
return System(eqs, t, [in, out], []; name)
endModelingToolkit systems are either components or connectors. Components define dynamics of the model. Connectors are used to connect components together. See the Model building reference section of the documentation for more information.
See also: @connector.
Every constructor function should build either a component or a connector. Components define the dynamics of the system. Connectors are used to connect components together and propagate information between them. See also @connector.
Scoping of variables
When building hierarchical systems, is is often necessary to pass variables from a parent system to the subsystems. If done naively, this will result in the child system assuming it "owns" the variables passed to it and any occurrences of those variables in the child system will be namespaced. To prevent this, ModelingToolkit has the concept of variable scope. The scope allows specifying which system a variable belongs to relative to the system in which it is used.
ModelingToolkit.LocalScope — Typestruct LocalScope <: SymScopeThe default scope of a variable. It belongs to the system whose equations it is involved in and is namespaced by every level of the hierarchy.
ModelingToolkit.ParentScope — Typestruct ParentScope <: SymScopeDenotes that the variable does not belong to the system whose equations it is involved in. It is not namespaced by this system. In the immediate parent of this system, the scope of this variable is given by parent.
Fields
parent::SymScope
ModelingToolkit.GlobalScope — Typestruct GlobalScope <: SymScopeDenotes that a variable belongs to the root system in the hierarchy, regardless of which equations of subsystems in the hierarchy it is involved in. Variables with this scope are never namespaced and only added to the unknowns/parameters of a system when calling complete or mtkcompile.
Note that the scopes must be applied to individual variables and not expressions. For example, ParentScope(x + y) is incorrect. Instead, ParentScope(x) + ParentScope(y) is the correct usage. Applying the same scope (more generally, the same function) to all variables in an expression is a common task, and ModelingToolkit exposes a utility for the same:
ModelingToolkit.apply_to_variables — Functionapply_to_variables(f, ex) -> Any
Apply function f to each variable in expression ex. f should be a function that takes a variable and returns the replacement to use. A "variable" in this context refers to a symbolic quantity created directly from a variable creation macro such as Symbolics.@variables, @independent_variables, @parameters, @constants or @brownians.
It is still tedious to manually use apply_to_variables on any symbolic expression passed to a subsystem. The @named macro automatically wraps all symbolic arguments in ParentScope and uses the identifier being assigned as the name of the system.
ModelingToolkit.@named — Macro@named y = foo(x)
@named y[1:10] = foo(x)
@named begin
y[1:10] = foo(x)
z = foo(x)
end # returns `[y; z]`
@named y 1:10 i -> foo(x*i) # This is not recommendedPass the LHS name to the model. When it's calling anything that's not an AbstractSystem, it wraps all keyword arguments in default_to_parentscope so that namespacing works intuitively when passing a symbolic default into a component.
Examples:
julia> using ModelingToolkit
julia> foo(i; name) = (; i, name)
foo (generic function with 1 method)
julia> x = 41
41
julia> @named y = foo(x)
(i = 41, name = :y)
julia> @named y[1:3] = foo(x)
3-element Vector{NamedTuple{(:i, :name), Tuple{Int64, Symbol}}}:
(i = 41, name = :y_1)
(i = 41, name = :y_2)
(i = 41, name = :y_3)Exploring the tree structure
The System type implements the AbstractTrees interface. This can be used to explore the hierarchical structure.
ModelingToolkit.hierarchy — Functionhierarchy(sys::AbstractSystem; describe = false, bold = describe, kwargs...)Print a tree of a system's hierarchy of subsystems.
Keyword arguments
describe: Whether to also print the description of each subsystem, if present.bold: Whether to print the name of the system in bold font.
Connection semantics
ModelingToolkit implements connection semantics similar to those in the Modelica specification. We do not support the concept of inner and outer elements or expandable connectors. Connectors in ModelingToolkit are systems with the appropriate metadata added via the @connector macro.
ModelingToolkit.connect — Functionconnect(
sys1::ModelingToolkit.AbstractSystem,
sys2::ModelingToolkit.AbstractSystem,
syss::ModelingToolkit.AbstractSystem...
) -> Equation
Connect multiple connectors created via @connector. All connected connectors must be unique.
connect(
var1::Union{Num, SymbolicUtils.BasicSymbolic, Symbolics.Arr},
var2::Union{Num, SymbolicUtils.BasicSymbolic, Symbolics.Arr},
vars::Union{Num, SymbolicUtils.BasicSymbolic, Symbolics.Arr}...
) -> Equation
Connect multiple causal variables. The first variable must be an output, and all subsequent variables must be inputs. The statement connect(var1, var2, var3, ...) expands to:
var1 ~ var2
var1 ~ var3
# ...connect(output_connector, ap_name::Symbol, input_connector; verbose = true)
connect(output_connector, ap::AnalysisPoint, input_connector; verbose = true)Connect output_connector and input_connector with an AnalysisPoint inbetween. The incoming connection output_connector is expected to be an output connector (for example, ModelingToolkitStandardLibrary.Blocks.RealOutput), and vice versa.
PLEASE NOTE: The connection is assumed to be causal, meaning that
@named P = FirstOrder(k = 1, T = 1)
@named C = Gain(; k = -1)
connect(C.output, :plant_input, P.input)is correct, whereas
connect(P.input, :plant_input, C.output)typically is not (unless the model is an inverse model).
Arguments
output_connector: An output connectorinput_connector: An input connectorap: An explicitly createdAnalysisPointap_name: If a name is given, anAnalysisPointwith the given name will be created automatically.
Keyword arguments
verbose: Warn if an input is connected to an output (reverse causality). Silence this warning if you are analyzing an inverse model.
ModelingToolkit.domain_connect — Functiondomain_connect(
sys1::ModelingToolkit.AbstractSystem,
sys2::ModelingToolkit.AbstractSystem,
syss::ModelingToolkit.AbstractSystem...
) -> Equation
Adds a domain only connection equation, through and across state equations are not generated.
ModelingToolkit.@connector — MacroMark a system constructor function as building a connector. For example,
@connector function ElectricalPin(; name, v = nothing, i = nothing)
@variables begin
v(t) = v, [description = "Potential at the pin [V]"]
i(t) = i, [connect = Flow, description = "Current flowing into the pin [A]"]
end
return System(Equation[], t, [v, i], []; name)
endSince connectors only declare variables, the equivalent shorthand syntax can also be used:
@connector Pin begin
v(t), [description = "Potential at the pin [V]"]
i(t), [connect = Flow, description = "Current flowing into the pin [A]"]
endModelingToolkit systems are either components or connectors. Components define dynamics of the model. Connectors are used to connect components together. See the Model building reference section of the documentation for more information.
See also: @component.
Connections can be expanded using expand_connections.
ModelingToolkit.expand_connections — Functionexpand_connections(
sys::ModelingToolkit.AbstractSystem;
tol
) -> Any
Given a hierarchical system with connect equations, expand the connection equations and return the new system. tol is the tolerance for handling the singularities in stream connection equations that happen when a flow variable approaches zero.
Similar to the stream and flow keyword arguments in the specification, ModelingToolkit allows specifying how variables in a connector behave in a connection.
ModelingToolkit.Equality — Typestruct Equality <: ModelingToolkit.AbstractConnectTypeFlag which is meant to be passed to the connect metadata of a variable to affect how it behaves when the connector it is in is part of a connect equation. Equality is the default value and such variables when connected are made equal. For example, electric potential is equated at a junction.
For more information, refer to the Connection semantics section of the docs.
See also: connect, @connector, Flow, Stream.
ModelingToolkit.Flow — Typestruct Flow <: ModelingToolkit.AbstractConnectTypeFlag which is meant to be passed to the connect metadata of a variable to affect how it behaves when the connector it is in is part of a connect equation. Flow denotes that the sum of marked variable in all connectors in the connection set must sum to zero. For example, electric current sums to zero at a junction (assuming appropriate signs are used for current flowing in and out of the function).
For more information, refer to the Connection semantics section of the docs.
See also: connect, @connector, Equality, Stream.
ModelingToolkit.Stream — Typestruct Stream <: ModelingToolkit.AbstractConnectTypeFlag which is meant to be passed to the connect metadata of a variable to affect how it behaves when the connector it is in is part of a connect equation. Stream denotes that the variable is part of a special stream connector.
For more information, refer to the Connection semantics section of the docs.
See also: connect, @connector, Equality, Flow.
These are specified using the connect metadata. ModelingToolkit also supports instream. Refer to the Modelica specification on Stream connectors for more information.
ModelingToolkit.instream — Functioninstream(a) -> SymbolicUtils.BasicSymbolic
instream is used when modeling stream connections. It is only allowed to be used on Stream variables.
Refer to the Connection semantics section of the docs for more information.
System composition utilities
ModelingToolkit.extend — Functionextend(
sys::ModelingToolkit.AbstractSystem,
basesys::ModelingToolkit.AbstractSystem;
name,
description,
gui_metadata
) -> Any
Extend basesys with sys. This can be thought of as the merge operation on systems. Values in sys take priority over duplicates in basesys (for example, defaults).
By default, the resulting system inherits sys's name and description.
The & operator can also be used for this purpose. sys & basesys is equivalent to extend(sys, basesys).
See also compose.
extend(
sys,
basesys::Array{T<:ModelingToolkit.AbstractSystem, 1}
) -> Any
Extend sys with all systems in basesys in order.
ModelingToolkit.compose — Functioncompose(sys, systems; name)
Compose multiple systems together. This adds all of systems as subsystems of sys. The resulting system inherits the name of sys by default.
The ∘ operator can also be used for this purpose. sys ∘ basesys is equivalent to compose(sys, basesys).
See also extend.
compose(syss...; name) -> Any
Syntactic sugar for adding all systems in syss as the subsystems of first(syss).
ModelingToolkit.substitute_component — Functionsubstitute_component(
sys::ModelingToolkit.AbstractSystem,
rule::Pair{T<:ModelingToolkit.AbstractSystem, T<:ModelingToolkit.AbstractSystem}
) -> Any
Given a hierarchical system sys and a rule lhs => rhs, replace the subsystem lhs in sys by rhs. The lhs must be the namespaced version of a subsystem of sys (e.g. obtained via sys.inner.component). The rhs must be valid as per the following conditions:
rhsmust not be namespaced.- The name of
rhsmust be the same as the unnamespaced name oflhs. - Neither one of
lhsorrhscan be marked as complete. - Both
lhsandrhsmust share the same independent variable. rhsmust contain at least all of the unknowns and parameters present inlhs.- Corresponding unknowns in
rhsmust share the same connection and causality (input/output) metadata as their counterparts inlhs. - For each subsystem of
lhs, there must be an identically named subsystem ofrhs. These two corresponding subsystems must satisfy conditions 3, 4, 5, 6, 7. If the subsystem oflhsis a connector, the corresponding subsystem ofrhsmust also be a connector of the same type.
sys also cannot be marked as complete.
Flattening systems
The hierarchical structure can be flattened. This operation is performed during simplification.
ModelingToolkit.flatten — Functionflatten(sys::System) -> System
flatten(sys::System, noeqs) -> System
Flatten the hierarchical structure of a system, collecting all equations, unknowns, etc. into one top-level system after namespacing appropriately.
System simplification
Systems can be simplified to reformulate them in a way that enables it to be solved numerically, and also perform other optimizations. This is done via the mtkcompile function. Connection expansion and flattening are preprocessing steps of simplification.
ModelingToolkit.mtkcompile — Functionmtkcompile(
sys;
additional_passes,
simplify,
split,
allow_symbolic,
allow_parameter,
conservative,
fully_determined,
inputs,
outputs,
disturbance_inputs,
kwargs...
)
Compile the given system into a form that ModelingToolkit can generate code for. Also performs a variety of symbolic-numeric enhancements. For ODEs, this includes processes such as order reduction, index reduction, alias elimination and tearing. A subset of the unknowns of the system may be eliminated as observables, eliminating the need for the numerical solver to solve for these variables.
Does not rely on metadata to identify variables/parameters/brownians. Instead, queries the system for which symbolic quantites belong to which category. Any variables not present in the equations of the system will be removed in this process.
Keyword Arguments
- When
simplify=true, thesimplifyfunction will be applied during the tearing process. allow_symbolic=false,allow_parameter=true, andconservative=falselimit the coefficient types during tearing. In particular,conservative=truelimits tearing to only solve for trivial linear systems where the coefficient has the absolute value of $1$.fully_determined=truecontrols whether or not an error will be thrown if the number of equations don't match the number of inputs, outputs, and equations.inputs,outputsanddisturbance_inputsare passed as keyword arguments.All inputsget converted to parameters and are allowed to be unconnected, allowing models wheren_unknowns = n_equations - n_inputs.sort_eqs=truecontrols whether equations are sorted lexicographically before simplification or not.
ModelingToolkit.@mtkcompile — MacroMacro shorthand for building and compiling a system in one step.
@mtkcompile sys = Constructor(args...; kwargs....)Is shorthand for
@named sys = Constructor(args...; kwargs...)
sys = mtkcompile(sys)It is also possible (though not always advisable) to build numerical problems from systems without passing them through mtkcompile. To do this, the system must first be marked as "complete" via the complete function. This process is used to indicate that a system will not be modified further and allows ModelingToolkit to perform any necessary preprocessing to it. mtkcompile calls complete internally.
ModelingToolkit.complete — Functioncomplete(
sys::ModelingToolkit.AbstractSystem;
split,
flatten,
add_initial_parameters
) -> Any
Mark a system as completed. A completed system is a system which is done being defined/modified and is ready for structural analysis or other transformations. This allows for analyses and optimizations to be performed which require knowing the global structure of the system.
One property to note is that if a system is complete, the system will no longer namespace its subsystems or variables, i.e. isequal(complete(sys).v.i, v.i).
This namespacing functionality can also be toggled independently of complete using toggle_namespacing.
Exploring the results of simplification
Similar to how full_equations returns the equations of a system with all variables eliminated during mtkcompile substituted, we can perform this substitution on an arbitrary expression.
ModelingToolkit.substitute_observed — Functionsubstitute_observed(
sys::ModelingToolkit.AbstractSystem,
expr;
simplify
) -> Any
Recursively substitute the observed equations of sys into expr. If simplify, call Symbolics.simplify on the resultant expression.
ModelingToolkit.empty_substitutions — Functionempty_substitutions(sys) -> Any
Check if any variables were eliminated from the system as part of mtkcompile.
ModelingToolkit.get_substitutions — Functionget_substitutions(sys) -> Dict
Get a dictionary mapping variables eliminated from the system during mtkcompile to the expressions used to calculate them.
Experimental simplification
ModelingToolkit may have a variety of experimental simplification passes. These are not enabled by default, but can be used by passing to the additional_passes keyword argument of mtkcompile.
ModelingToolkit.IfLifting — FunctionIf lifting converts (nested) if statements into a series of continuous events + a logically equivalent if statement + parameters.
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
Event handling
Time-dependent systems may have several events. These are used to trigger discontinuities in the model. They compile to standard callbacks from DiffEqCallbacks.jl.
ModelingToolkit.SymbolicContinuousCallback — TypeSymbolicContinuousCallback(eqs::Vector{Equation}, affect = nothing, iv = nothing;
affect_neg = affect, initialize = nothing, finalize = nothing, rootfind = SciMLBase.LeftRootFind, alg_eqs = Equation[])A ContinuousCallback specified symbolically. Takes a vector of equations eq as well as the positive-edge affect and negative-edge affect_neg that apply when any of eq are satisfied. By default affect_neg = affect; to only get rising edges specify affect_neg = nothing.
Assume without loss of generality that the equation is of the form c(u,p,t) ~ 0; we denote the integrator state as i.u. For compactness, we define prev_sign = sign(c(u[t-1], p[t-1], t-1)) and cur_sign = sign(c(u[t], p[t], t)). A condition edge will be detected and the callback will be invoked iff prev_sign * cur_sign <= 0. The positive edge affect will be triggered iff an edge is detected and if prev_sign < 0; similarly, affect_neg will be triggered iff an edge is detected and prev_sign > 0.
Inter-sample condition activation is not guaranteed; for example if we use the dirac delta function as c to insert a sharp discontinuity between integrator steps (which in this example would not normally be identified by adaptivity) then the condition is not guaranteed to be triggered.
Once detected the integrator will "wind back" through a root-finding process to identify the point when the condition became active; the method used is specified by rootfind from SciMLBase.RootfindOpt. If we denote the time when the condition becomes active as tc, the value in the integrator after windback will be:
u[tc-epsilon], p[tc-epsilon], tcifLeftRootFindis used,u[tc+epsilon], p[tc+epsilon], tcifRightRootFindis used,- or
u[t], p[t], tifNoRootFindis used.
For example, if we want to detect when an unknown variable x satisfies x > 0 using the condition x ~ 0 on a positive edge (that is, D(x) > 0), then left root finding will get us x=-epsilon, right root finding x=epsilon and no root finding will produce whatever the next step of the integrator was after it passed through 0.
Multiple callbacks in the same system with different rootfind operations will be grouped by their rootfind value into separate VectorContinuousCallbacks in the enumeration order of SciMLBase.RootfindOpt. This may cause some callbacks to not fire if several become active at the same instant. See the SciMLBase documentation for more information on the semantic rules.
Affects (i.e. affect and affect_neg) can be specified as either:
- A list of equations that should be applied when the callback is triggered (e.g.
x ~ 3, y ~ 7) which must be of the formunknown ~ observed valuewhere eachunknownappears only once. Equations will be applied in the order that they appear in the vector; parameters and state updates will become immediately visible to following equations. - A tuple
(f!, unknowns, read_parameters, modified_parameters, ctx), where:f!is a function with signature(integ, u, p, ctx)that is called with the integrator, a state index vectoruderived fromunknowns, a parameter index vectorpderived fromread_parameters, and thectxthat was given at construction time. Note thatctxis aliased between instances.unknownsis a vector of symbolic unknown variables and optionally their aliases (e.g. if the model was defined with@variables x(t)then a valid value forunknownswould be[x]). A variable can be aliased with a pairx => :y. The indices of theseunknownswill be passed tof!inuin a named tuple; in the earlier example, if we pass[x]asunknownsthenf!can accessxasinteg.u[u.x]. If no alias is specified the name of the index will be the symbol version of the variable name.read_parametersis a vector of the parameters that are used byf!. Their indices are passed tofinpsimilarly to the indices ofunknownspassed inu.modified_parametersis a vector of the parameters that are modified byf!. Note that a parameter will not appear inpif it only appears inmodified_parameters; it must appear in bothparametersandmodified_parametersif it is used in the affect definition.ctxis a user-defined context object passed tof!when invoked. This value is aliased for each problem.
- A
ImperativeAffect; refer to its documentation for details.
reinitializealg is used to set how the system will be reinitialized after the callback.
- Symbolic affects have reinitialization built in. In this case the algorithm will default to SciMLBase.NoInit(), and should not be provided.
- Functional and imperative affects will default to SciMLBase.CheckInit(), which will error if the system is not properly reinitialized after the callback. If your system is a DAE, pass in an algorithm like SciMLBase.BrownBasicFullInit() to properly re-initialize.
Initial and final affects can also be specified identically to positive and negative edge affects. Initialization affects will run as soon as the solver starts, while finalization affects will be executed after termination.
ModelingToolkit.SymbolicDiscreteCallback — TypeSymbolicDiscreteCallback(conditions::Vector{Equation}, affect = nothing, iv = nothing;
initialize = nothing, finalize = nothing, alg_eqs = Equation[])A callback that triggers at the first timestep that the conditions are satisfied.
The condition can be one of:
- Δt::Real - periodic events with period Δt
- ts::Vector{Real} - events trigger at these preset times given by
ts - eqs::Vector{Symbolic} - events trigger when the condition evaluates to true
Arguments:
- iv: The independent variable of the system. This must be specified if the independent variable appears in one of the equations explicitly, as in x ~ t + 1.
- alg_eqs: Algebraic equations of the system that must be satisfied after the callback occurs.
The affect functions for the above callbacks can be symbolic or user-defined functions. Symbolic affects are handled using equations as described in the Events section of the documentation. User-defined functions can be used via ImperativeAffect.
ModelingToolkit.ImperativeAffect — TypeImperativeAffect(f::Function; modified::NamedTuple, observed::NamedTuple, ctx)ImperativeAffect is a helper for writing affect functions that will compute observed values and ensure that modified values are correctly written back into the system. The affect function f needs to have the signature
f(modified::NamedTuple, observed::NamedTuple, ctx, integrator)::NamedTupleThe function f will be called with observed and modifiedNamedTuples that are derived from their respective NamedTuple definitions. Each declarationNamedTuple should map an expression to a symbol; for example if we pass observed=(; x = a + b) this will alias the result of executing a+b in the system as x so the value of a + b will be accessible as observed.x in f. modified currently restricts symbolic expressions to only bare variables, so only tuples of the form (; x = y) or (; x) (which aliases x as itself) are allowed.
The argument NamedTuples (for instance (;x=y)) will be populated with the declared values on function entry; if we require (;x=y) in observed and y=2, for example, then the NamedTuple (;x=2) will be passed as observed to the affect function f.
The NamedTuple returned from f includes the values to be written back to the system after f returns. For example, if we want to update the value of x to be the result of x + y we could write
ImperativeAffect(observed=(; x_plus_y = x + y), modified=(; x)) do m, o
@set! m.x = o.x_plus_y
endWhere we use Setfield to copy the tuple m with a new value for x, then return the modified value of m. All values updated by the tuple must have names originally declared in modified; a runtime error will be produced if a value is written that does not appear in modified. The user can dynamically decide not to write a value back by not including it in the returned tuple, in which case the associated field will not be updated.
Modelingtoolkitize
ModelingToolkit can take some numerical problems created non-symbolically and build a symbolic representation from them.
ModelingToolkit.modelingtoolkitize — Functionmodelingtoolkitize(
prob::ODEProblem;
u_names,
p_names,
return_symbolic_u0_p,
kwargs...
) -> Any
Convert an ODEProblem to a ModelingToolkit.System.
Keyword arguments
u_names: An array of names of the same size asprob.u0to use as the names of the unknowns of the system. The names should be given asSymbols.p_names: A collection of names to use for parameters of the system. The collection should have keys corresponding to indexes ofprob.p. For example, ifprob.pis an associative container likeNamedTuple, thenp_namesshould map keys ofprob.pto the name that the corresponding parameter should have in the returned system. The names should be given asSymbols.- INTERNAL
return_symbolic_u0_p: Also return the symbolic state and parameter objects.
All other keyword arguments are forwarded to the created System.
modelingtoolkitize(
prob::SDEProblem;
u_names,
p_names,
kwargs...
) -> Any
Convert an SDEProblem to a ModelingToolkit.System.
Keyword arguments
u_names: an array of names of the same size asprob.u0to use as the names of the unknowns of the system. The names should be given asSymbols.p_names: a collection of names to use for parameters of the system. The collection should have keys corresponding to indexes ofprob.p. For example, ifprob.pis an associative container likeNamedTuple, thenp_namesshould map keys ofprob.pto the name that the corresponding parameter should have in the returned system. The names should be given asSymbols.
All other keyword arguments are forwarded to the created System.
modelingtoolkitize(
prob::OptimizationProblem;
u_names,
p_names,
kwargs...
) -> System
Convert an OptimizationProblem to a ModelingToolkit.System.
Keyword arguments
u_names: An array of names of the same size asprob.u0to use as the names of the unknowns of the system. The names should be given asSymbols.p_names: A collection of names to use for parameters of the system. The collection should have keys corresponding to indexes ofprob.p. For example, ifprob.pis an associative container likeNamedTuple, thenp_namesshould map keys ofprob.pto the name that the corresponding parameter should have in the returned system. The names should be given asSymbols.
All other keyword arguments are forwarded to the created System.
modelingtoolkitize(
prob::Union{SciMLBase.NonlinearLeastSquaresProblem, NonlinearProblem};
u_names,
p_names,
kwargs...
) -> System
Convert a NonlinearProblem or NonlinearLeastSquaresProblem to a ModelingToolkit.System.
Keyword arguments
u_names: An array of names of the same size asprob.u0to use as the names of the unknowns of the system. The names should be given asSymbols.p_names: A collection of names to use for parameters of the system. The collection should have keys corresponding to indexes ofprob.p. For example, ifprob.pis an associative container likeNamedTuple, thenp_namesshould map keys ofprob.pto the name that the corresponding parameter should have in the returned system. The names should be given asSymbols.
All other keyword arguments are forwarded to the created System.
Using FMUs
ModelingToolkit is capable of importing FMUs as black-box symbolic models. Currently only a subset of FMU features are supported. This functionality requires importing FMI.jl.
ModelingToolkit.FMIComponent — FunctionFMIComponent(
::Val{Ver};
fmu,
tolerance,
communication_step_size,
reinitializealg,
type,
name
)
A component that wraps an FMU loaded via FMI.jl. The FMI version (2 or 3) should be provided as a Val to the function. Supports Model Exchange and CoSimulation FMUs. All inputs, continuous variables and outputs must be FMI.fmi2Real or FMI.fmi3Float64. Does not support events or discrete variables in the FMU. Does not support automatic differentiation. Parameters of the FMU will have defaults corresponding to their initial values in the FMU specification. All other variables will not have a default. Hierarchical names in the FMU of the form namespace.variable are transformed into symbolic variables with the name namespace__variable.
Keyword Arguments
fmu: The FMU loaded viaFMI.loadFMU.tolerance: The tolerance to provide to the FMU. Not used for v3 FMUs since it is not supported by FMI.jl.communication_step_size: The periodic interval at which communication with CoSimulation FMUs will occur. Must be provided for CoSimulation FMU components.reinitializealg: The DAE initialization algorithm to use for the callback managing the FMU. For CoSimulation FMUs whose states/outputs are used in algebraic equations of the system, this needs to be an algorithm that will solve for the new algebraic variables. For example,OrdinaryDiffEqCore.BrownFullBasicInit().type: Either:MEor:CSdepending on whetherfmuis a Model Exchange or CoSimulation FMU respectively.name: The name of the system.
Model transformations
ModelingToolkit exposes a variety of transformations that can be applied to models to aid in symbolic analysis.
ModelingToolkit.liouville_transform — Functionliouville_transform(sys::System; kwargs...) -> System
Generates the Liouville transformed set of ODEs, which is the original ODE system with a new variable trJ appended, corresponding to the -tr(Jacobian). This variable is used for properties like uncertainty propagation from a given initial distribution density.
For example, if $u'=p*u$ and p follows a probability distribution $f(p)$, then the probability density of a future value with a given choice of $p$ is computed by setting the initial trJ = f(p), and the final value of trJ is the probability of $u(t)$.
Example:
using ModelingToolkit, OrdinaryDiffEq
@independent_variables t
@parameters α β γ δ
@variables x(t) y(t)
D = Differential(t)
eqs = [D(x) ~ α*x - β*x*y, D(y) ~ -δ*y + γ*x*y]
@named sys = System(eqs, t)
sys2 = liouville_transform(sys)
sys2 = complete(sys2)
u0 = [x => 1.0, y => 1.0, sys2.trJ => 1.0]
prob = ODEProblem(sys2, u0, tspan, p)
sol = solve(prob, Tsit5())Where sol[3,:] is the evolution of trJ over time.
Sources:
Probabilistic Robustness Analysis of F-16 Controller Performance: An Optimal Transport Approach
Abhishek Halder, Kooktae Lee, and Raktim Bhattacharya https://abhishekhalder.bitbucket.io/F16ACC2013Final.pdf
ModelingToolkit.fractional_to_ordinary — FunctionGenerates the system of ODEs to find solution to FDEs.
Example:
@independent_variables t
@variables x(t)
D = Differential(t)
tspan = (0., 1.)
α = 0.5
eqs = (9*gamma(1 + α)/4) - (3*t^(4 - α/2)*gamma(5 + α/2)/gamma(5 - α/2))
eqs += (gamma(9)*t^(8 - α)/gamma(9 - α)) + (3/2*t^(α/2)-t^4)^3 - x^(3/2)
sys = fractional_to_ordinary(eqs, x, α, 10^-7, 1)
prob = ODEProblem(sys, [], tspan)
sol = solve(prob, radau5(), abstol = 1e-10, reltol = 1e-10)ModelingToolkit.linear_fractional_to_ordinary — FunctionGenerates the system of ODEs to find solution to FDEs.
Example:
@independent_variables t
@variables x_0(t)
D = Differential(t)
tspan = (0., 5000.)
function expect(t)
return sqrt(2) * sin(t + pi/4)
end
sys = linear_fractional_to_ordinary([3, 2.5, 2, 1, .5, 0], [1, 1, 1, 4, 1, 4], 6*cos(t), 10^-5, 5000; initials=[1, 1, -1])
prob = ODEProblem(sys, [], tspan)
sol = solve(prob, radau5(), abstol = 1e-5, reltol = 1e-5)ModelingToolkit.change_of_variables — Functionchange_of_variables(
sys::System,
iv,
forward_subs,
backward_subs;
simplify,
t0,
isSDE
) -> Any
Generates the set of ODEs after change of variables.
Example:
using ModelingToolkit, OrdinaryDiffEq, Test
# Change of variables: z = log(x)
# (this implies that x = exp(z) is automatically non-negative)
@independent_variables t
@parameters α
@variables x(t)
D = Differential(t)
eqs = [D(x) ~ α*x]
tspan = (0., 1.)
def = [x => 1.0, α => -0.5]
@mtkcompile sys = System(eqs, t;defaults=def)
prob = ODEProblem(sys, [], tspan)
sol = solve(prob, Tsit5())
@variables z(t)
forward_subs = [log(x) => z]
backward_subs = [x => exp(z)]
new_sys = change_of_variables(sys, t, forward_subs, backward_subs)
@test equations(new_sys)[1] == (D(z) ~ α)
new_prob = ODEProblem(new_sys, [], tspan)
new_sol = solve(new_prob, Tsit5())
@test isapprox(new_sol[x][end], sol[x][end], atol=1e-4)ModelingToolkit.stochastic_integral_transform — Functionstochastic_integral_transform(
sys::System,
correction_factor
) -> Any
Choose correction_factor=-1//2 (1//2) to convert Ito -> Stratonovich (Stratonovich->Ito).
ModelingToolkit.Girsanov_transform — FunctionGirsanov_transform(sys::System, u; θ0) -> Any
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 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.
Reference: Kloeden, P. E., Platen, E., & Schurz, H. (2012). Numerical solution of SDE through computer experiments. Springer Science & Business Media.
Example
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 = System(eqs,t,[x],[α,β]; noise_eqs = noiseeqs)
# 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)ModelingToolkit.change_independent_variable — Functionchange_independent_variable(
sys::System, iv, eqs = [];
add_old_diff = false, simplify = true, fold = false
)Transform the independent variable (e.g. $t$) of the ODE system sys to a dependent variable iv (e.g. $u(t)$). The transformation is well-defined when the mapping between the new and old independent variables are one-to-one. This is satisfied if one is a strictly increasing function of the other (e.g. $du(t)/dt > 0$ or $du(t)/dt < 0$).
Any extra equations eqs involving the new and old independent variables will be taken into account in the transformation.
Keyword arguments
add_old_diff: Whether to add a differential equation for the old independent variable in terms of the new one using the inverse function rule $dt/du = 1/(du/dt)$.simplify: Whether expanded derivative expressions are simplified. This can give a tidier transformation.fold: Whether internal substitutions will evaluate numerical expressions.
Usage before structural simplification
The variable change must take place before structural simplification. In following calls to mtkcompile, consider passing allow_symbolic = true to avoid undesired constraint equations between between dummy variables.
Usage with non-autonomous systems
If sys is non-autonomous (i.e. $t$ appears explicitly in its equations), consider passing an algebraic equation relating the new and old independent variables (e.g. $t = f(u(t))$). Otherwise the transformed system can be underdetermined. If an algebraic relation is not known, consider using add_old_diff instead.
Usage with hierarchical systems
It is recommended that iv is a non-namespaced variable in sys. This means it can belong to the top-level system or be a variable in a subsystem declared with GlobalScope.
Example
Consider a free fall with constant horizontal velocity. Physics naturally describes position as a function of time. By changing the independent variable, it can be reformulated for vertical position as a function of horizontal position:
julia> @variables x(t) y(t);
julia> @named M = System([D(D(y)) ~ -9.81, D(D(x)) ~ 0.0], t);
julia> M = change_independent_variable(M, x);
julia> M = mtkcompile(M; allow_symbolic = true);
julia> unknowns(M)
3-element Vector{SymbolicUtils.BasicSymbolic{Real}}:
xˍt(x)
y(x)
yˍx(x)ModelingToolkit.add_accumulations — Functionadd_accumulations(sys::System) -> Any
add_accumulations(sys::System, vars) -> Any
Add accumulation variables for vars. For every unknown x in vars, add D(accumulation_x) ~ x as an equation.
add_accumulations(sys::System, vars::Vector{<:Pair}) -> Any
Add accumulation variables for vars. vars is a vector of pairs in the form of
[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.
ModelingToolkit.noise_to_brownians — Functionnoise_to_brownians(sys::System; names) -> Any
Given a system with noise in the form of noise equation (get_noise_eqs(sys) !== nothing) return an equivalent system which represents the noise using brownian variables.
Keyword Arguments
names: The name(s) to use for the brownian variables. If this is aSymbol, variables with the given name and successive numeric_isuffixes will be used. If aVector, this must have appropriate length for the noise equations of the system. The corresponding number of brownian variables are created with the given names.
ModelingToolkit.convert_system_indepvar — Functionconvert_system_indepvar(sys::System, t; name) -> Any
Function which takes a system sys and an independent variable t and changes the independent variable of sys to t. This is different from change_independent_variable since this function only does a symbolic substitution of the independent variable. sys must not be a reduced system (observed(sys) must be empty). If sys is time-independent, this can be used to turn it into a time-dependent system.
Keyword arguments
name: The name of the returned system.
ModelingToolkit.subset_tunables — Functionsubset_tunables(sys, new_tunables) -> Any
Change the tunable parameters of a system to a new set of tunables.
The new tunable parameters must be a subset of the current tunables as discovered by tunable_parameters. The remaining parameters will be set as constants in the system.
ModelingToolkit.respecialize — Functionrespecialize(sys::ModelingToolkit.AbstractSystem) -> Any
Shorthand for respecialize(sys, []; all = true)
respecialize(
sys::ModelingToolkit.AbstractSystem,
mapping;
all
) -> Any
Specialize nonnumeric parameters in sys by changing their symtype to a concrete type. mapping is an iterable, where each element can be a parameter or a pair mapping a parameter to a value. If the element is a parameter, it must have a default. Each specified parameter is updated to have the symtype of the value associated with it (either in mapping or in the defaults). This operation can only be performed on nonnumeric, non-array parameters. The defaults of respecialized parameters are set to the associated values.
This operation can only be performed on completed systems.
Keyword arguments
all: Specialize all nonnumeric parameters in the system. This will error if any such parameter does not have a default.
Hybrid systems
Hybrid systems are dynamical systems involving one or more discrete-time subsystems. These discrete time systems follow clock semantics - they are synchronous systems and the relevant variables are only defined at points where the clock ticks.
While ModelingToolkit is unable to simplify, compile and solve such systems on its own, it has the ability to represent them. Compilation strategies can be implemented independently on top of mtkcompile using the additional_passes functionality.
ModelingToolkit.Sample — Typestruct Sample <: Symbolics.OperatorRepresents a sample operator. A discrete-time signal is created by sampling a continuous-time signal.
Constructors
Sample(clock::Union{TimeDomain, InferredTimeDomain} = InferredDiscrete())Sample(dt::Real)
Sample(x::Num), with a single argument, is shorthand for Sample()(x).
Fields
clock
Examples
julia> using Symbolics
julia> t = ModelingToolkit.t_nounits
julia> Δ = Sample(0.01)
(::Sample) (generic function with 2 methods)ModelingToolkit.Hold — Typestruct Hold <: Symbolics.OperatorRepresents a hold operator. A continuous-time signal is produced by holding a discrete-time signal x with zero-order hold.
cont_x = Hold()(disc_x)ModelingToolkit.SampleTime — Typefunction SampleTime()SampleTime() can be used in the equations of a hybrid system to represent time sampled at the inferred clock for that equation.
ModelingToolkit uses the clock definition in SciMLBase
Missing docstring for SciMLBase.TimeDomain. Check Documenter's build log for details.
Missing docstring for SciMLBase.SolverStepClock. Check Documenter's build log for details.
Missing docstring for SciMLBase.Continuous. Check Documenter's build log for details.
State machines
While ModelingToolkit has the capability to represent state machines, it lacks the ability to compile and simulate them.
ModelingToolkit.transition — Functiontransition(from, to, cond; immediate::Bool = true, reset::Bool = true, synchronize::Bool = false, priority::Int = 1)Create a transition from state from to state to that is enabled when transitioncondition cond evaluates to true.
Arguments:
from: The source state of the transition.to: The target state of the transition.cond: A transition condition that evaluates to a Bool, such asticksInState() >= 2.immediate: Iftrue, the transition will fire at the same tick as it becomes true, iffalse, the actions of the state are evaluated first, and the transition fires during the next tick.reset: If true, the destination statetois reset to its initial condition when the transition fires.synchronize: If true, the transition will only fire if all sub-state machines in the source state are in their final (terminal) state. A final state is one that has no outgoing transitions.priority: If a state has more than one outgoing transition, all outgoing transitions must have a unique priority. The transitions are evaluated in priority order, i.e., the transition with priority 1 is evaluated first.
ModelingToolkit.activeState — FunctionactiveState(state)When used in a finite state machine, this operator returns true if the queried state is active and false otherwise.
ModelingToolkit.entry — Functionentry()
entry(state)When used in a finite-state machine, this operator returns true at the first tick when the state is active, and false otherwise.
When used to query the entry of the enclosing state, the method without arguments is used, when used to query the entry of another state, the state is passed as an argument.
This can be used to perform a unique action when entering a state.
ModelingToolkit.ticksInState — FunctionticksInState()
ticksInState(state)Get the number of ticks spent in a state in a finite state machine.
When used to query the number of ticks spent in the enclosing state, the method without arguments is used, i.e.,
@mtkmodel FSM begin
...
@equations begin
var(k+1) ~ ticksInState() >= 2 ? 0.0 : var(k)
end
endIf used to query the number of ticks in another state, the state is passed as an argument.
This operator can be used in both equations and transition conditions.
See also timeInState and entry
ModelingToolkit.timeInState — FunctiontimeInState()
timeInState(state)Get the time (in seconds) spent in a state in a finite state machine.
When used to query the time spent in the enclosing state, the method without arguments is used, i.e.,
@mtkmodel FSM begin
...
@equations begin
var(k+1) ~ timeInState() >= 2 ? 0.0 : var(k)
end
endIf used to query the residence time of another state, the state is passed as an argument.
This operator can be used in both equations and transition conditions.
See also ticksInState and entry