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.
Source Distribution
Built Distribution
File details
Details for the file poincare-0.3.0.tar.gz
.
File metadata
- Download URL: poincare-0.3.0.tar.gz
- Upload date:
- Size: 35.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/4.0.2 CPython/3.11.6
File hashes
Algorithm | Hash digest | |
---|---|---|
SHA256 | de0518c6f1ab0282e441db04055a1f024c134a68fb5b34c582b3bcd4894a2636 |
|
MD5 | 83bf0e78b6b21ce0e26e15eac55da1a9 |
|
BLAKE2b-256 | ebd1c27bbff61fe8dbde6bcd54e913824182390b14e15a8ef8899c0fa00abe24 |
File details
Details for the file poincare-0.3.0-py3-none-any.whl
.
File metadata
- Download URL: poincare-0.3.0-py3-none-any.whl
- Upload date:
- Size: 36.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/4.0.2 CPython/3.11.6
File hashes
Algorithm | Hash digest | |
---|---|---|
SHA256 | 2585811ca7839d4def05ace650ca3a7d1ebcaaa8efe5927b7ff109eaa539f604 |
|
MD5 | d9a75247bf8e355e49ec669c0b48a499 |
|
BLAKE2b-256 | 75f90d1e19c0ed2c12e2eaa1118619552728f8597ef865c94ba5073514dc847f |