Skip to content

Simulation results

Peter Corke edited this page Aug 19, 2026 · 1 revision

Simulation results

sim.run(bd, T, ...) returns a BDStruct — a simple attribute/dict-hybrid container (out.foo and out["foo"] both work) holding everything the run collected. print(out) gives a quick overview of what's inside.

Continuous state: out.t, out.x, out.xnames

  • out.tndarray(samples), the simulation time at each recorded sample.
  • out.xndarray(samples, nstates), the trajectory of every continuous-time state (from ContinuousBlock subclasses) concatenated in diagram order.
  • out.xnames — one name per column of out.x, eg. 'plant:x_0'.

Discrete/clocked state: out.<clockname>.t, out.<clockname>.X, out.<clockname>.Xnames

If the diagram has one or more Clocks (see Discrete-time blocks), each clock gets its own nested sub-struct on out, named after the clock (dots stripped from the name), holding:

  • .t — that clock's own tick times, generally a different, coarser grid than the top-level out.t.
  • .Xcapital Xndarray(ticks, ndstates), the trajectory of that clock's discrete/sampled state (from SampledBlock subclasses driven by it).
  • .Xnames — one name per column of .X.

This is the .x vs .X distinction to watch for: lowercase x is continuous state, at the top level of out. Uppercase X is discrete state, one level down inside each clock's own sub-struct — not at the top level at all, and the case difference is significant. For example, with a clock named "controller" driving one ZOH block and a continuous plant, print(out) shows:

controller::
             X      = ndarray:float64 (20, 1)
             Xnames = ['zoh.0:X_0'] (list)
             t      = ndarray:float64 (20,)
t          = ndarray:float64 (159,)
x          = ndarray:float64 (159, 1)
xnames     = ['plant:x_0'] (list)

Watched signals: out.y, out.ynames

Anything passed to the watch=[...] option of run() is logged separately from state:

out = sim.run(bd, T=5, watch=[demand, plant])
  • out.yndarray(samples, n_watched_columns), every watched signal column-stacked into one array. A watched signal that's itself an array contributes one column per element.
  • out.ynames — one name per column of out.y, in the same order as watch=[...]. Names default to the watched block/plug's full repr() unless it has a friendly name=, so it's worth naming blocks you intend to watch.

Deprecated: out.y0, out.y1, ...

Older code may use out.y0, out.y1, ... — one array per watched signal, from before they were column-stacked into out.y. These still work but raise a DeprecationWarning pointing at the replacement:

out.y0 is deprecated and will be removed in a future release; use out.y[:, 0] instead
(out.ynames gives the column order for multi-signal watches).

Replace out.yN with out.y[:, N].

Run statistics

out.stats (a hidden field, .stats, exposed via normal attribute access) holds counters from the run: integration_time_points, run_interval_calls, ydot_calls, integrator_wall_time, events_detected_total, and events_detected_by_source. The same numbers are what gets printed to the console at the end of a run when quiet=False.

See also

  • Running for run()'s options, including watch=.
  • Discrete-time blocks for how clocks and discrete state work.
  • Home for the getting-started example that first shows out.

Clone this wiki locally