Experiment Data Interface

Data Format

The data ingested by JuliaSimModelOptimizer needs a particular format. Let us first go through the rules and then demonstrate them with an example.

The rules are as follows:

  1. Data which can either be simulations or real world data are bunch of timeseries for all the states/observables arranged in a tabular format which has a Tables.jl interface such as DataFrame, CSV.File, NamedTuple containing vectors for each columns etc.
  2. First column of every table should be named as "timestamp". This column is the series of time points. This does not apply when we have steady state data as there will no dependence on time.
  3. The names for the rest of the columns should match the names of the states or algebraic variables of the corresponding model. Note that: i. The independent variable suffix (usually "(t)"), which ModelingToolkit generates is not required, it can either be present or omitted. ii. For models with sub-systems, the variables should be namespaced according to the system they belong to. The character used to delimit the namespace can either be "." or "₊".

Now, let us go through an example which is the same as the example in the getting started page.

using JuliaSimModelOptimizer
using ModelingToolkit, OrdinaryDiffEq
using ModelingToolkitStandardLibrary.Electrical
using ModelingToolkitStandardLibrary.Blocks: Sine
using DataSets
using Plots

function create_model(; C₁ = 3e-5, C₂ = 1e-6)
    @variables t
    @named resistor1 = Resistor(R = 5.0)
    @named resistor2 = Resistor(R = 2.0)
    @named capacitor1 = Capacitor(C = C₁)
    @named capacitor2 = Capacitor(C = C₂)
    @named source = Voltage()
    @named input_signal = Sine(frequency = 100.)
    @named ground = Ground()
    @named ampermeter = CurrentSensor()

    eqs = [connect(input_signal.output, source.V)
           connect(source.p, capacitor1.n, capacitor2.n)
           connect(source.n, resistor1.p, resistor2.p, ground.g)
           connect(resistor1.n, capacitor1.p, ampermeter.n)
           connect(resistor2.n, capacitor2.p, ampermeter.p)]

    @named circuit_model = ODESystem(eqs, t,
                             systems = [
                                 resistor1, resistor2, capacitor1, capacitor2,
                                 source, input_signal, ground, ampermeter,
                             ])
end

model = create_model()
sys = structural_simplify(model)

\[ \begin{align} \frac{\mathrm{d} capacitor2_{+}v\left( t \right)}{\mathrm{d}t} =& capacitor1_{+}vˍt\left( t \right) \\ 0 =& - capacitor1_{+}i\left( t \right) - capacitor2_{+}i\left( t \right) + resistor1_{+}i\left( t \right) + resistor2_{+}i\left( t \right) \end{align} \]

We can see the states of the model by:

states(sys)
2-element Vector{Any}:
 capacitor2₊v(t)
 capacitor1₊i(t)

We can see that states of the model are defined in an interpretable manner. For example,"capacitor2₊v(t)" means the voltage across capacitor2. So, using the rules defined above, the name of this column in the dataset can be:

  • "capacitor2₊v(t)"
  • "capacitor2₊v"
  • "capacitor2.v(t)"
  • "capacitor2.v"

All the above names map to the same state in the model.

Data Storage

The data can be saved in any format on disk as long we can deserialize it in the formats mentioned above.

For ease, JuliaSimModelOptimizer natively provides supports for using DataSets.jl. So, we can pass in a DataSet object without worrying about downloading and deserializing as it is all handled internally. Currently, we support files to be in the form of CSVs for using DataSets.jl.

Let us demonstrate this with an example. We will use the dataset from getting started page.

data = dataset("juliasimtutorials/circuit_data")
experiment = Experiment(data, sys; alg = Rodas4(), abstol=1e-6, reltol=1e-5)
Experiment with default parameters and initial conditions.
timespan: (0.0, 0.1)

We can see that we only need to pass in the DataSet object directly into the Experiment constructor to use the data.