signal.s = LinearSystem(sys=([1, 0], [1]), analog=True)
signal.z = LinearSystem(sys=([1, 0], [1]), analog=False)
class nengolib.signal.LinearSystem(sys, analog=None)[source]

Generic linear system representation.

This extends nengo.LinearFilter to unify a variety of representations (transfer function, state-space, zero-pole gain) in continuous (i.e., analog) and discrete (i.e., digital) time-domains. Instances provide access to a number of common attributes and methods that are core to a variety of routines and networks throughout nengolib.

This can be used anywhere a nengo.synapses.Synapse (or nengo.LinearFilter) object is expected within Nengo. For instance, this can be passed as a synapse parameter to nengo.Connection. If the system is analog, then it will be automatically discretized using the simulation time-step (see cont2discrete()). We advocate for using this class to represent and manipulate linear systems whenever possible (e.g., to create synapse objects, and to specify dynamical systems, whenever modelling within the NEF).

The objects s and z are instances of LinearSystem that form the basic building blocks for analog or digital systems respectively (see examples). These objects respect the usual interpretation of differentiation and time-shifting in the Laplace– and z–domains respectively.

sys : linear_system_like

Linear system representation.

analog : boolean, optional

Continuous or discrete time-domain. Defaults to sys.analog if isinstance(sys, nengo.LinearFilter), otherwise True. If specified, it must not contradict sys.analog.


Instances of this class are intended to be immutable.

Currently, support is focused primarily on SISO systems. There is some limited support for SIMO, MISO, and MIMO systems within state-space representations, but the functionality of such systems is currently experimental / limited as they must remain in state-space form.

State-space representations must be causal (proper) and finite. Transfer functions must also be finite (Padé approximants may help here) but may be acausal (not necessarily proper) and must remain SISO.

Conversions between representations are cached within the object itself. Redundantly casting a LinearSystem to itself returns the same underlying object, in order to persist this cache whenever possible. This is done not as a performance measure, but to guard against numerical issues that would result from accidentally converting back and forth between the same formats.


A simple continuous-time integrator:

>>> from nengolib.signal import s
>>> integrator = 1/s
>>> assert integrator == ~s == s**(-1)
>>> t = integrator.trange(2.)
>>> step = np.ones_like(t)
>>> cosine = np.cos(t)
>>> import matplotlib.pyplot as plt
>>> plt.subplot(211)
>>> plt.title("Integrating a Step Function")
>>> plt.plot(t, step, label="Step Input")
>>> plt.plot(t, integrator.filt(step), label="Ramping Output")
>>> plt.legend(loc='lower center')
>>> plt.subplot(212)
>>> plt.title("Integrating a Cosine Wave")
>>> plt.plot(t, cosine, label="Cosine Input")
>>> plt.plot(t, integrator.filt(cosine), label="Sine Output")
>>> plt.xlabel("Time (s)")
>>> plt.legend(loc='lower center')

(Source code)


Building up higher-order continuous systems:

>>> sys1 = 1000/(s**2 + 2*s + 1000)   # Bandpass filtering
>>> sys2 = 500/(s**2 + s + 500)       # Bandpass filtering
>>> sys3 = .5*sys1 + .5*sys2          # Mixture of two bandpass
>>> assert len(sys1) == 2  # sys1.order_den
>>> assert len(sys2) == 2  # sys2.order_den
>>> assert len(sys3) == 4  # sys3.order_den
>>> plt.subplot(311)
>>> plt.title("sys1.impulse")
>>> plt.plot(t, sys1.impulse(len(t)), label="sys1")
>>> plt.subplot(312)
>>> plt.title("sys2.impulse")
>>> plt.plot(t, sys2.impulse(len(t)), label="sys2")
>>> plt.subplot(313)
>>> plt.title("sys3.impulse")
>>> plt.plot(t, sys3.impulse(len(t)), label="sys3")
>>> plt.xlabel("Time (s)")

Plotting a linear transformation of the state-space from sys3.impulse:

>>> from nengolib.signal import balance
>>> plt.title("balance(sys3).X.impulse")
>>> plt.plot(t, balance(sys3).X.impulse(len(t)))
>>> plt.xlabel("Time (s)")

A discrete trajectory:

>>> from nengolib.signal import z
>>> trajectory = 1 - .5/z + 2/z**3 + .5/z**4
>>> t = np.arange(7)
>>> y = trajectory.impulse(len(t))
>>> plt.title("trajectory.impulse")
>>> plt.step(t, y, where='post')
>>> plt.fill_between(t, np.zeros_like(y), y, step='post', alpha=.3)
>>> plt.xticks(t)
>>> plt.xlabel("Step")

A matrix from state-space representation.


B matrix from state-space representation.


C matrix from state-space representation.


D matrix from state-space representation.


Returns the multiple-output system for the state-space vector.


Boolean indicating whether system is analog or digital.


Boolean indicating if the system is causal / proper.


Returns a new system in controllable canonical form.


Steady-state response to unit step input.


A parameter where the value is an integer.


A parameter where the value is an integer.


Denominator of transfer function.


Gain from zero-pole gain representation.


Boolean indicating if the system has a passthrough.


Boolean indicating whether system is SISO.


Boolean indicating whether state-space has been computed.


Boolean indicating if system is exponentially stable.


Boolean indicating whether transfer function has been computed.


Boolean indicating whether zero-pole gain has been computed.


Numerator of transfer function.


Returns a new system in observable canonical form.


Order of transfer function’s denominator (i.e., state dimension).


Order of transfer function’s numerator.


Poles from zero-pole gain representation.


Short-hand for (.size_in, .size_out).


Input dimensionality (this equals 1 for SISO or SIMO).


Output dimensionality (this equals 1 for SISO or MISO).


State-space representation (A, B, C, D).


Boolean indicating if the system is strictly proper.


Transfer function representation (num, den).


Zeros from zero-pole gain representation.


Zero-pole gain representation (zeros, poles, gain).


General(num, den, output[, y0]) An LTI step function for any given transfer function.
NoDen(num, den, output) An LTI step function for transfer functions with no denominator.
Simple(num, den, output[, y0]) An LTI step function for transfer functions with one num and den.
Step(num, den, output) Abstract base class for LTI filtering step functions.
__call__(s) Evaluate the transfer function at the given complex value(s).
apply(x[, d, dt, rng, copy]) Run process on a given input.
combine(obj) Combine in series with another LinearFilter.
evaluate(frequencies) Evaluate the transfer function at the given frequencies.
filt(u[, dt, axis, y0, copy, filtfilt]) Filter the input using this linear system.
filtfilt(x, **kwargs) Zero-phase filtering of x using this filter.
get_rng(rng) Get a properly seeded independent RNG for the process step.
impulse(length[, dt]) Impulse response with length timesteps and width dt.
make_step(shape_in, shape_out, dt, rng[, …]) Produces the function for filtering across one time-step.
ntrange(n_steps[, dt]) Create time points corresponding to a given number of steps.
run(t[, d, dt, rng]) Run process without input for given length of time.
run_steps(n_steps[, d, dt, rng]) Run process without input for given number of steps.
trange(t[, dt]) Create time points corresponding to a given length of time.
transform(T[, Tinv]) Changes basis of state-space matrices to T.

Subset of magic methods

__len__() Dimensionality of state vector.
__invert__() Reciprocal of transfer function.