Skip to content

Simulation

Running

Model.run() integrates from the current initial values, exactly as XPPAUT's batch mode (xppaut -silent) does, and returns a Trajectory. Keyword arguments are applied with set() first:

sol = m.run(total=500, dt=0.01, meth="5dp")

A Trajectory holds time (sol.t) and every output column: the state variables, then the auxiliary quantities declared with aux.

Method Result
sol["V"] one column as a NumPy array
sol.columns the column names
sol.to_numpy() all columns, one row per output time
sol.to_pandas() a DataFrame indexed by time
sol.save("out.dat") XPPAUT's output.dat text format
sol.plot("V", "W") a matplotlib plot against time
sol.plot("W", x="V") one column against another

The values are XPPAUT's stored data (single precision), the same numbers XPPAUT writes to output.dat.

Continuing a run, and saving it

m.continue_run(sol, 50) is XPPAUT's Continue: it integrates 50 more time units from where sol ended and returns sol with the new points appended, leaving the initial conditions as they were. m.last_run() is the engine's most recent run, such as the one period of the limit cycle that m.phase_response() leaves behind.

Initial conditions

m.ics["V"] = -0.3                       # one value (m.initial_conditions)
m.ic_last()                             # from the end of the last run (I L)
m.ic_last(shift=True)                   # and T0 from its end time (I S)
m.write_ics("ics.dat")                  # one per line, exactly
m.read_ics("ics.dat")                   # one number per variable (I F)
m.ic_formula("V", "vk + 0.1")           # a formula, evaluated by XPPAUT
m.ic_formula("u[0..199]", "0.8*exp(-([j]-100)^2/50)")   # [j]: the index

read_ics takes the first numbers in a text file, in the order of m.variables; a file with too few raises EngineError and changes nothing. ic_formula is XPPAUT's Initial conds > formUla: the formula may use the parameters and the model's functions, and a numbered range of variables gets one value per index.

sol.to_csv("run.csv") writes a run with a header and every number exact; Table results and bifurcation diagrams (m.auto.diagram.to_csv(...)) have the same method. The files match the desktop app's CSV exports.

Integrators

meth= takes XPPAUT's names or friendlier aliases:

XPPAUT name Alias Kind
rungekutta rk4 fixed-step RK4
5dp dopri5, dormand-prince adaptive Dormand-Prince 5
83dp dopri8, dopri853 adaptive Dormand-Prince 8
stiff adaptive stiff Rosenbrock
cvode CVODE
2rb rosenbrock Rosenbrock 2(3)
euler, modeuler, backeul backward-euler for noise
gear, adams, qualrk, volterra, ymp symplectic
discrete map maps

xs.METHODS lists them all.

Options

Any option an ODE file's @ line accepts can be set from Python:

m.set(total=200, dt=0.05, trans=50, bound=1e6, nout=10, tol=1e-8)
m.options          # a snapshot: {'TOTAL': 200.0, 'DT': 0.05, 'METH': ...}
m.get("total")

Parameter sweeps

runs = m.sweep("I", [0.1, 0.2, 0.3])          # a list of Trajectory
amp = m.sweep("I", np.linspace(0, 2, 100),    # an array: a brute-force
              measure=lambda s: np.ptp(s["V"][len(s)//2:]),
              total=400)                      # bifurcation diagram

The parameter is restored after the sweep.

Two parameters move together when sweep gets a pair of names and a list of pairs, or over every combination with sweep_grid (XPPAUT's 2par range):

peak = lambda s: s["V"][len(s) // 2:].max()
line = m.sweep(("I", "gk"), [(0.1, 1.0), (0.2, 1.5)], measure=peak)
grid = m.sweep_grid("I", np.linspace(0, 2, 40),     # across
                    "gk", np.linspace(1, 3, 20),    # up
                    measure=peak)                   # shape (20, 40)
xs.Table.from_grid("I", np.linspace(0, 2, 40), "gk", np.linspace(1, 3, 20),
                   {"peak": grid}).to_csv("grid.csv")

Row j of the grid holds the runs at the second parameter's j-th value. A measure that returns several numbers adds a last axis. Without a measure, sweep_grid returns the trajectories as a list of rows. continue_runs=True starts each run where the previous one ended, going through the grid row by row. Both parameters are restored afterwards.

Interactive sliders in Jupyter

m.interact("I", "eps")          # sliders around the current values
m.interact(I=(0, 2), y="V")     # explicit range, plot only V

This needs ipywidgets and matplotlib.

Errors

A model with a mistake raises xs.EngineError. The message quotes XPPAUT's parser, and err.log holds everything the engine printed:

try:
    xs.load("broken.ode")
except xs.EngineError as err:
    print(err)        # could not load broken.ode: Y is a duplicate name