Skip to main content

Simulation of dynamical systems.

Project description

PyVersion Package Conda Version License CI pre-commit.ci status

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 can write:

>>> 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:

>>> 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 can pass 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

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

Non-autonomous systems

To use the independent variable, we create an instance of Independent:

>>> from poincare import Independent
>>> class NonAutonomous(System):
...   time = Independent()
...   x: Variable = initial(default=0)
...   eq = x.derive() << 2 * time
...
>>> Simulator(NonAutonomous).solve(save_at=range(3))
             x
time
0     0.000000
1     1.000001
2     4.000001

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).solve(values={Model.c: 2}, 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's initial value directly:

>>> Simulator(Model).solve(values={Model.c: 2, Model.y: 2}, 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.0            0.0
1     0.540366      -0.841561
2    -0.416308      -0.909791

which allows to plot the DataFrame with .plot().

Installation

It can be installed from PyPI:

pip install -U poincare

or conda-forge:

conda install -c conda-forge poincare

Development

This project is managed by pixi. You can install it for development using:

git clone https://github.com/{{ github_username }}/{{ project_name }}
cd {{ project_name }}
pixi run pre-commit-install

Pre-commit hooks are used to lint and format the project.

Testing

Run tests using:

pixi run test

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

poincare-0.6.2.tar.gz (96.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

poincare-0.6.2-py3-none-any.whl (37.8 kB view details)

Uploaded Python 3

File details

Details for the file poincare-0.6.2.tar.gz.

File metadata

  • Download URL: poincare-0.6.2.tar.gz
  • Upload date:
  • Size: 96.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.5.6

File hashes

Hashes for poincare-0.6.2.tar.gz
Algorithm Hash digest
SHA256 bbce225c151516e35ca701d3c4f50c3d5e1f5342b0a277e08d15ff78b89ad7cc
MD5 26254565b00308af78d89e08343c7c2f
BLAKE2b-256 26750ac58e7967a122a49398b2d51720eb591c636ed5944a94ee2c1801fe574e

See more details on using hashes here.

File details

Details for the file poincare-0.6.2-py3-none-any.whl.

File metadata

  • Download URL: poincare-0.6.2-py3-none-any.whl
  • Upload date:
  • Size: 37.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.5.6

File hashes

Hashes for poincare-0.6.2-py3-none-any.whl
Algorithm Hash digest
SHA256 43ab292e7c65c2b516426901441bcfabed1e98dd88e64d6d9e487d45db9ad7e0
MD5 9bfbb45719e482ac3d664eaf688abe2b
BLAKE2b-256 0ae086931688b7ddab93e577baed2e53efa04a7e02a988ed9701dc094d4d723f

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page