Simulation of dynamical systems.
Project description
Poincaré: simulation of dynamical systems
Poincaré allows to define and simulate dynamical systems in Python.
Definition
To define the system
$$ \frac{dx}{dt} = -x \quad \text{with} \quad x(0) = 1 $$
we write can:
>>> from poincare import Variable, System, initial
>>> class Model(System):
... # Define a variable with name `x` with an initial value (t=0) of `1``.
... x: Variable = initial(default=1)
... # The rate of change of `x` (i.e. velocity) is assigned (<<) to `-x`.
... # This relation is assigned to a Python variable (`eq`)
... eq = x.derive() << -x
...
Simulation
To simulate that system, we do:
>>> from poincare import Simulator
>>> sim = Simulator(Model)
>>> sim.solve(save_at=range(3))
x
time
0 1.000000
1 0.368139
2 0.135501
The output is a pandas.DataFrame
,
which can be plotted with .plot()
.
Changing initial conditions
To change the initial condition, we have two options.
- Passing a dictionary to the `solve`` method:
>>> sim.solve(values={Model.x: 2}, save_at=range(3))
x
time
0 2.000000
1 0.736278
2 0.271002
which reuses the previously compiled model in the Simulator
instance.
- Instantiating the model with other values:
>>> Simulator(Model(x=2)).solve(save_at=range(3))
x
time
0 2.000000
1 0.736278
2 0.271002
This second option allows to compose systems into bigger systems. See the example in examples/oscillators.py.
Transforming the output
We can compute transformations of the output by passing a dictionary of expressions:
>>> Simulator(Model, transform={"x": Model.x, "2x": 2 * Model.x}).solve(save_at=range(3))
x 2x
time
0 1.000000 2.000000
1 0.368139 0.736278
2 0.135501 0.271002
Higher-order systems
To define a higher-order system, we have to assign an initial condition to the derivative of a variable:
>>> from poincare import Derivative
>>> class Oscillator(System):
... x: Variable = initial(default=1)
... v: Derivative = x.derive(initial=0)
... eq = v.derive() << -x
...
>>> Simulator(Oscillator).solve(save_at=range(3))
x v
time
0 1.000000 0.000000
1 0.540366 -0.841561
2 -0.416308 -0.909791
Constants, Parameters, and functions
Besides variables, we can define parameters and constants, and use functions from symbolite.
Constants
Constants allow to define common initial conditions for Variables and Derivatives:
>>> from poincare import assign, Constant
>>> class Model(System):
... c: Constant = assign(default=1, constant=True)
... x: Variable = initial(default=c)
... y: Variable = initial(default=2 * c)
... eq_x = x.derive() << -x
... eq_y = y.derive() << -y
...
>>> Simulator(Model).solve(save_at=range(3))
x y
time
0 1.000000 2.000000
1 0.368139 0.736278
2 0.135501 0.271002
Now, we can vary their initial conditions jointly:
>>> Simulator(Model(c=2)).solve(save_at=range(3))
x y
time
0 2.000000 4.000000
1 0.736278 1.472556
2 0.271001 0.542003
But we can break that connection by passing y
initial value directly:
>>> Simulator(Model(c=2, y=2)).solve(save_at=range(3))
x y
time
0 2.000000 2.000000
1 0.736278 0.736278
2 0.271002 0.271002
Parameters
Parameters are like Variables, but their time evolution is given directly as a function of time, Variables, Constants and other Parameters:
>>> from poincare import Parameter
>>> class Model(System):
... p: Parameter = assign(default=1)
... x: Variable = initial(default=1)
... eq = x.derive() << -p * x
...
>>> Simulator(Model).solve(save_at=range(3))
x
time
0 1.000000
1 0.368139
2 0.135501
Functions
Symbolite functions are accessible from the symbolite.scalar
module:
>>> from symbolite import scalar
>>> class Model(System):
... x: Variable = initial(default=1)
... eq = x.derive() << scalar.sin(x)
...
>>> Simulator(Model).solve(save_at=range(3))
x
time
0 1.000000
1 1.951464
2 2.654572
Units
poincaré also supports functions through
pint
and pint-pandas
.
>>> import pint
>>> unit = pint.get_application_registry()
>>> class Model(System):
... x: Variable = initial(default=1 * unit.m)
... v: Derivative = x.derive(initial=0 * unit.m/unit.s)
... w: Parameter = assign(default=1 * unit.Hz)
... eq = v.derive() << -w**2 * x
...
>>> result = Simulator(Model).solve(save_at=range(3))
The columns have units of m and m/s, respectively.
pint
raises a DimensionalityError
if we try to add them:
>>> result["x"] + result["v"]
Traceback (most recent call last):
...
pint.errors.DimensionalityError: Cannot convert from 'meter' ([length]) to 'meter / second' ([length] / [time])
We can remove the units and set them as string metadata with:
>>> result.pint.dequantify()
x v
unit meter meter / second
time
0 1.000000 0.000000
1 0.540366 -0.841561
2 -0.416308 -0.909791
which allows to plot the DataFrame with .plot()
.
Installation
pip install -U poincare
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.