Documentation

This document provides a general introduction to FunctionalModels using ModelingToolkit.

Unknowns

Models consist of equations and unknown variables. The number of equations should match the number of unknowns. In FunctionalModels, the function Unknown is used to define unknown Symbolics.jl variables.

Unknowns also contain a value. This is used for setting initial values. Unknowns can be different types.

The label string is used for labeling simulation outputs. Unlabeled Unknowns are not included in results.

Here are several ways to define Unknowns:

using FunctionalModels, ModelingToolkit
x = Unknown()          # An initial value of 0.0 with an anonymous name.
y = Unknown(1.0, name = :y)  # An initial value of 1.0 and a name of `y`.
@named y = Unknown(1.0)       # Same.
@named z = Unknown([1.0, 0.0])  # An Unknown with array values.

In model equations, derivatives are specified with der or D:

   der(y) ~ 3

Models

Here is a model of the Van Der Pol oscillator:

function Vanderpol()
    @named y = Unknown(1.0)   
    @named x = Unknown()       
    @named dx = Unknown(-1.0)  
    # The following gives the return value which is a list of equations.
    # Expressions with Unknowns are kept as expressions. Regular
    # variables are evaluated immediately (like normal).
    [
        der(x) ~ dx
        dx ~ (1 - y^2) * x - y
        der(y) ~ x
    ]
end

A device model is a function that returns a list of equations or other devices that also return lists of equations.

Models should normally be locally balanced, meaning the number of unknowns matches the number of equations. It's pretty easy to match unknowns and equations as shown below:

function Capacitor(n1, n2; C) 
    i = Current()              # Unknown #1
    v = Voltage()              # Unknown #2
    [
        Branch(n1, n2, v, i)      # Equation #1 - this returns `v ~ n1 - n2`
        der(v) ~ i / C            # Equation #2
    ]
end

In the model above, the nodes n1 and n2 are also Unknowns, but they are defined outside of this model.

Here is the top-level circuit definition. In this case, there are no input parameters. The ground reference g is assigned zero volts.

function Circuit()
    @named n1 = ElectricalNode()
    @named n2 = ElectricalNode()
    @named n3 = ElectricalNode()
    g = 0.0  # a ground has zero volts; it's not an Unknown.
    [
        VSource(n1, g, V = 10.0, f = 60.0)
        Resistor(n1, n2, R = 10.0)
        Resistor(n2, g, R = 5.0)
        SeriesProbe(n2, n3, name = "Capcurrent")
        Capacitor(n3, g, C = 5.0e-3)
    ]
end

All of the equations returned in this list of equations are other models with different parameters.

In this top-level model, three new Unknowns are introduced (n1, n2, and n2). Because these are nodes, each Unknown node will also cause an equation to be generated that sums the flows into the node to be zero.

In this model, the voltages n1 and n2 are labeled, so they will appear in the output. A SeriesProbe is used to label the current through the capacitor.

Simulating a Model

Steps to building and simulating a model are straightforward.

using FunctionalModels, ModelingToolkit
v = Vanderpol()  # returns the hierarchical model
v_sys = system(v)     # returns the flattened model as a ModelingToolkit.ODEProblem

From here, all solving and plotting is done with ModelingToolkit.

st = states(v_sys)
prob = ODAEProblem(v_sys, Dict(st[1] => 0.0, st[2] => 1.0), (0, 10.0))
using OrdinaryDiffEq
sol = solve(prob, Tsit5())
using Plots
plot(sol)