Using the XMILE Compiler Standalone

In-depth explanation of how to use the XMILE Compiler Standalone.
Keywords

system dynamics, systemdynamics, xmile, bptk, bptk-py, python, business simulation, stella

Using the XMILE Compiler Standalone

The BPTK-Py package contains a compiler which compiles System Dynamics models that use the XMILE format into Python code. This means that you can compile models created with Stella, iThink or Vensim (via the XMILE export) into Python code which will then run standalone.

The result of the compilation is a Python class that only depends on NumPy and SciPy. This class contains the entire simulation model and it can run “standalone”, i.e. you don’t need the BPTK framework to actually run the simulation and extract simulation results.

Running models standalone can be useful if you want to run your model many times with different initial settings, e.g. for machine learning experiments. In such cases you don’t need the extra functionality provided by the framework’s bptk class and it is more efficient to work with the raw simulation model.

The compiler needs the [xmile] extra (pip install bptk_py[xmile]) and it writes a file, so the code on this page is shown rather than run - the outputs below are from a real session with the model described next.

Compiling a model

Assume a small XMILE model called test_compiler.stmx in a simulation_models folder. It contains one converter called pulse_converter with the equation 1+PULSE(0.5, 3.25). Compiling it into test_compiler.py takes three lines:

from BPTK_Py.sdcompiler.compile import compile_xmile

compile_xmile("./simulation_models/test_compiler.stmx",
              "./simulation_models/test_compiler.py",
              "py")

The generated file contains everything needed to run the model. The model itself is in a class simulation_model, alongside a number of generic methods that system dynamics simulations need - simulation_model.delay or simulation_model.random_with_seed among them. These are always generated, whether or not your model uses them, so that every generated simulation_model class has the same methods.

Running it

The equations themselves are stored in the class as a dictionary of lambda functions. Since the example model has a single converter, so does the dictionary - and because each equation is a lambda taking the time t, it can be called directly:

from simulation_models.test_compiler import simulation_model

model = simulation_model()

model.equations
# {'pulseConverter':
#     <function simulation_model.__init__.<locals>.<lambda>(t)>}

model.equations["pulseConverter"](5.25)
# 3.0

Plotting the results

The simulation_model has no convenience functions for plotting - those live in the framework’s bptk class. But the equations are ordinary callables, so collecting them into a Pandas dataframe and using its plot() method takes just as few lines:

import pandas as pd
from numpy import arange

data = [[t, model.equations["pulseConverter"](t)]
        for t in arange(0, 20, 0.25)]
df = pd.DataFrame(data, columns=["time", "pulse_converter"])
df = df.set_index("time")

df.loc[2:5]   # the interesting stretch: the pulse arrives at t = 3.25
time pulse_converter
2.00 1.0
2.25 1.0
2.50 1.0
2.75 1.0
3.00 1.0
3.25 3.0
3.50 3.0
3.75 3.0
4.00 3.0
4.25 3.0
4.50 3.0
4.75 3.0
5.00 3.0

The dataframe plots itself:

df.plot()

The pulse converter over time: 1.0 until t = 3.25, then 3.0