Documentation

This document provides a general introduction to Sims.

Unknowns

Models consist of equations and unknown variables. The number of equations should match the number of unknowns. In Sims, the type Unknown is used to define unknown variables. Without the constructor parts, the definition of Unknown is:

type Unknown{T<:UnknownCategory} <: UnknownVariable
    sym::Symbol
    value         # holds initial values (and type info)
    label::String 
end

Unknowns can be grouped into categories. That's what the T is for in the definition above. One can define different types of Unknowns (electrical vs. mechanical for example). The default is DefaultUnknown. Unknowns of different types can also be used to define models of the same name that act differently depending on what type of node they are connected to.

Unknowns also contain a value. This is used for setting initial values, and these values are updated if there is a structural change in the model. Unknowns can be different types. Eventually, all Unknowns are converted to Float64's in an array for simulation. Currently, Sim supports Unknowns of type Float64, Complex128, and arrays of either of these. Adding support for other structures is not hard as long as they can be converted to Float64's.

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

Here are several ways to define Unknowns:

x = Unknown()          # An initial value of 0.0 with no labeling.
y = Unknown(1.0, "y")  # An initial value of 1.0 and a label of "y" on outputs.
z = Unknown([1.0, 0.0], "vector")  # An Unknown with array values.
V = Unknown{Voltage}(10.0, "Output voltage")  # An Unknown of type Voltage

Here are ways to create new Unknown types:

# Untyped Unknowns:
Angle = AngularVelocity = AngularAcceleration = Torque = RotationalNode = Unknown

# Typed Unknowns:
type UVoltage <: UnknownCategory
end
type UCurrent <: UnknownCategory
end
typealias ElectricalNode Unknown{UVoltage}
typealias Voltage Unknown{UVoltage}
typealias Current Unknown{UCurrent}

In model equations, derivatives are specified with der:

   der(y)

Derivatives of Unknowns are an object of type DerUnknown. DerUnknown objects contain an initial value, and a pointer to the Unknown object it references. Initial values can be entered as the second parameter to the der function:

   der(y, 3.0) - (x+1)   #  The derivative of y starts with the value 3.0

Models

Here is a model of the Van Der Pol oscillator:

function Vanderpol()
    y = Unknown(1.0, "y")   
    x = Unknown("x")       
    # 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).
    Equation[
        der(x, -1.0) - ((1 - y^2) * x - y)   # == 0 is assumed
        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. The equations each are assumed equal to zero. In Julia, this is the best we can do, because there isn't an equality operator (== doesn't fit the bill, either).

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::Real) 
    i = Current()              # Unknown #1
    v = Voltage()              # Unknown #2
    Equation[
        Branch(n1, n2, v, i)      # Equation #1 - this returns n1 - n2 - v
        C * der(v) - i            # 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()
    n1 = ElectricalNode("Source voltage")   # The string indicates labeling for plots
    n2 = ElectricalNode("Output voltage")
    n3 = ElectricalNode()
    g = 0.0  # a ground has zero volts; it's not an Unknown.
    Equation[
        VSource(n1, g, 10.0, 60.0)
        Resistor(n1, n2, 10.0)
        Resistor(n2, g, 5.0)
        SeriesProbe(n2, n3, "Capacitor current")
        Capacitor(n3, g, 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.

v = Vanderpol()       # returns the hierarchical model
v_f = elaborate(v)    # returns the flattened model
v_s = create_sim(v_f) # returns a "Sim" ready for simulation
v_yout = sim(v_s, 10.0) # run the simulation to 10 seconds and return
                        # the result as an array plus column headings

Two solvers are available: dasslsim and sunsim using the Sundials. Right now, sim is equivalent to dasslsim.

Simulations can also be run directly from a hierarchical model:

v_yout = sim(v, 10.0) 

Right now, there are really no options available for simulation parameters.

Simulation Output

The result of a sim run is an object with components y and colnames. y is a two-dimensional array with time slices along rows and variables along columns. The first column is simulation time. The remaining columns are for each unknown in the model including derivatives. colnames contains the names of each of the columns in y after the time column.

Hybrid Modeling

Sims provides basic support for hybrid modeling. Discrete variables are variables that are not involved in integration but apply when "events" occur. Models can define events denoting changes in behavior.

Event is the main type used for hybrid modeling. It contains a condition for root finding and model expressions to process after positive and negative root crossings are detected.

type Event <: ModelType
    condition::ModelType   # An expression used for the event detection. 
    pos_response::Model    # An expression indicating what to do when
                           # the condition crosses zero positively.
    neg_response::Model    # An expression indicating what to do when
                           # the condition crosses zero in the
                           # negative direction.
end

The function reinit is used in Event responses to redefine variables. Here is an example of a voltage source defined with a square wave:

function VSquare(n1, n2, V::Real, f::Real)  
    i = Current()
    v = Voltage()
    v_mag = Discrete(V)
    Equation[
        Branch(n1, n2, v, i)
        v - v_mag
        Event(sin(2 * pi * f * MTime),
              Equation[reinit(v_mag, V)],    # positive crossing
              Equation[reinit(v_mag, -V)])   # negative crossing
    ]
end

The variable v_mag is the Discrete variable that is changed using reinit whenever the sin(2 * pi * f * MTime) crosses zero. A response is provided for both positive and negative zero crossings.

Two other constructs that are useful are BoolEvent and ifelse. ifelse is like an if-then-else block, but for ModelTypes (you can't use a regular if-then-else block, at least not without macros).
BoolEvent is a helper for attaching an event to a boolean variable. Here is an example for an ideal diode:

function IdealDiode(n1, n2)
    i = Current()
    v = Voltage()
    s = Unknown()  # dummy variable
    openswitch = Discrete(false)  # on/off state of diode
    Equation[
        Branch(n1, n2, v, i)
        BoolEvent(openswitch, -s)  # openswitch becomes true when s goes negative
        v - ifelse(openswitch, s, 0.0) 
        i - ifelse(openswitch, 0.0, s) 
    ]
end

Structurally Varying Systems

StructuralEvent defines a type for elements that change the structure of the model. An event is created, and when the event is triggered, the model is re-flattened after replacing default with new_relation in the model.

type StructuralEvent <: ModelType
    condition::ModelType   # Expression indicating a zero crossing for event detection.
    default                # The default relation.
    new_relation::Function # Function to call when the new relation is needed.
    activated::Bool        # Used internally to indicate whether the event fired.
end

Here is an example for a breaking pendulum. The model starts with the Pendulum construct. Then, when five seconds is reached, the StructuralEvent triggers, and the model is recompiled with the FreeFall construct.

function BreakingPendulum()
    x = Unknown(cos(pi/4), "x")
    y = Unknown(-cos(pi/4), "y")
    vx = Unknown()
    vy = Unknown()
    Equation[
        StructuralEvent(MTime - 5.0,     # when time hits 5 sec, switch to FreeFall
            Pendulum(x,y,vx,vy),
            () -> FreeFall(x,y,vx,vy))
    ]
end

One special thing to note is that new_relation must be a function (in the case above, an anonymous function). If new_relation is not a function, it will evaluate right away. The use of a function delays evaluation until the model is recompiled.

Known Issues