Symbolic Nonlinear System Definition and Acceleration via ModelingToolkit

ModelingToolkit.jl is a symbolic-numeric modeling system for the Julia SciML ecosystem. It adds a high-level interactive interface for the numerical solvers which can make it easy to symbolically modify and generate equations to be solved. The basic form of using ModelingToolkit looks as follows:

import ModelingToolkit as MTK
import NonlinearSolve as NLS

MTK.@variables x y z
MTK.@parameters σ ρ β

# Define a nonlinear system
eqs = [0 ~ σ * (y - x), 0 ~ x * (ρ - z) - y, 0 ~ x * y - β * z]
MTK.@mtkbuild ns = MTK.NonlinearSystem(eqs, [x, y, z], [σ, ρ, β])

u0 = [x => 1.0, y => 0.0, z => 0.0]

ps = [σ => 10.0
      ρ => 26.0
      β => 8 / 3]

prob = NLS.NonlinearProblem(ns, u0, ps)
sol = NLS.solve(prob, NLS.NewtonRaphson())

Symbolic Derivations of Extra Functions

As a symbolic system, ModelingToolkit can be used to represent the equations and derive new forms. For example, let's look at the equations:

MTK.equations(ns)

We can ask it what the Jacobian of our system is via calculate_jacobian:

MTK.calculate_jacobian(ns)

We can tell MTK to generate a computable form of this analytical Jacobian via jac = true to help the solver use efficient forms:

prob = NLS.NonlinearProblem(ns, u0, ps, jac = true)
sol = NLS.solve(prob, NLS.NewtonRaphson())

Symbolic Simplification of Nonlinear Systems via Tearing

One of the major reasons for using ModelingToolkit is to allow structural simplification of the systems. It's very easy to write down a mathematical model that, in theory, could be solved more simply. Let's take a look at a quick system:

MTK.@variables u1 u2 u3 u4 u5
eqs = [0 ~ u1 - sin(u5), 0 ~ u2 - cos(u1), 0 ~ u3 - hypot(u1, u2),
    0 ~ u4 - hypot(u2, u3), 0 ~ u5 - hypot(u4, u1)]
MTK.@named sys = MTK.NonlinearSystem(eqs, [u1, u2, u3, u4, u5], [])

If we run structural simplification, we receive the following form:

sys = MTK.structural_simplify(sys)
MTK.equations(sys)

How did it do this? Let's look at the observed to see the relationships that it found:

MTK.observed(sys)

Using ModelingToolkit, we can build and solve the simplified system:

u0 = [u5 .=> 1.0]
prob = NLS.NonlinearProblem(sys, u0)
sol = NLS.solve(prob, NLS.NewtonRaphson())

We can then use symbolic indexing to retrieve any variable:

sol[u1]
sol[u2]
sol[u3]
sol[u4]
sol[u5]

Component-Based and Acausal Modeling

If you're interested in building models in a component or block based form, such as seen in systems like Simulink or Modelica, take a deeper look at ModelingToolkit.jl's documentation which goes into detail on such features.