poincare


Namepoincare JSON
Version 0.6.1 PyPI version JSON
download
home_pageNone
SummarySimulation of dynamical systems.
upload_time2024-12-17 15:12:01
maintainerNone
docs_urlNone
authorNone
requires_python>=3.10
licenseMIT
keywords ode differential equations dynamical systems
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            ![PyVersion](https://img.shields.io/pypi/pyversions/poincare?label=python)
![Package](https://img.shields.io/pypi/v/poincare?label=PyPI)
![Conda Version](https://img.shields.io/conda/vn/conda-forge/poincare)
![License](https://img.shields.io/pypi/l/poincare?label=license)
[![CI](https://github.com/maurosilber/poincare/actions/workflows/ci.yml/badge.svg)](https://github.com/maurosilber/poincare/actions/workflows/ci.yml)
[![pre-commit.ci status](https://results.pre-commit.ci/badge/github/maurosilber/poincare/main.svg)](https://results.pre-commit.ci/latest/github/maurosilber/poincare/main)

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

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

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

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

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

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

```python
>>> 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](https://github.com/hgrecco/symbolite).

#### Constants

Constants allow to define common initial conditions for Variables and Derivatives:

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

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

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

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

```python
>>> 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`](https://github.com/hgrecco/pint)
and [`pint-pandas`](https://github.com/hgrecco/pint-pandas).

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

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

```python
>>> 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](https://pixi.sh).
You can install it for development using:

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

```sh
pixi run test
```

            

Raw data

            {
    "_id": null,
    "home_page": null,
    "name": "poincare",
    "maintainer": null,
    "docs_url": null,
    "requires_python": ">=3.10",
    "maintainer_email": null,
    "keywords": "ODE, differential equations, dynamical systems",
    "author": null,
    "author_email": "\"Hern\u00e1n E. Grecco\" <hernan.grecco@gmail.com>, Mauro Silberberg <maurosilber@gmail.com>",
    "download_url": "https://files.pythonhosted.org/packages/c3/b5/d73560e520bb70b346cb680db9bf6a3d8d635e595fdc8ec1f8866beff776/poincare-0.6.1.tar.gz",
    "platform": null,
    "description": "![PyVersion](https://img.shields.io/pypi/pyversions/poincare?label=python)\n![Package](https://img.shields.io/pypi/v/poincare?label=PyPI)\n![Conda Version](https://img.shields.io/conda/vn/conda-forge/poincare)\n![License](https://img.shields.io/pypi/l/poincare?label=license)\n[![CI](https://github.com/maurosilber/poincare/actions/workflows/ci.yml/badge.svg)](https://github.com/maurosilber/poincare/actions/workflows/ci.yml)\n[![pre-commit.ci status](https://results.pre-commit.ci/badge/github/maurosilber/poincare/main.svg)](https://results.pre-commit.ci/latest/github/maurosilber/poincare/main)\n\n# Poincar\u00e9: simulation of dynamical systems\n\nPoincar\u00e9 allows to define and simulate dynamical systems in Python.\n\n### Definition\n\nTo define the system\n\n$$ \\\\frac{dx}{dt} = -x \\\\quad \\\\text{with} \\\\quad x(0) = 1 $$\n\nwe can write:\n\n```python\n>>> from poincare import Variable, System, initial\n>>> class Model(System):\n...   # Define a variable with name `x` with an initial value (t=0) of `1``.\n...   x: Variable = initial(default=1)\n...   # The rate of change of `x` (i.e. velocity) is assigned (<<) to `-x`.\n...   # This relation is assigned to a Python variable (`eq`)\n...   eq = x.derive() << -x\n...\n```\n\n### Simulation\n\nTo simulate that system:\n\n```python\n>>> from poincare import Simulator\n>>> sim = Simulator(Model)\n>>> sim.solve(save_at=range(3))\n             x\ntime\n0     1.000000\n1     0.368139\n2     0.135501\n```\n\nThe output is a `pandas.DataFrame`,\nwhich can be plotted with `.plot()`.\n\n### Changing initial conditions\n\nTo change the initial condition,\nwe can pass a dictionary to the `solve` method:\n\n```python\n>>> sim.solve(values={Model.x: 2}, save_at=range(3))\n             x\ntime\n0     2.000000\n1     0.736278\n2     0.271002\n```\n\n### Transforming the output\n\nWe can compute transformations of the output\nby passing a dictionary of expressions:\n\n```python\n>>> Simulator(Model, transform={\"x\": Model.x, \"2x\": 2 * Model.x}).solve(save_at=range(3))\n             x        2x\ntime\n0     1.000000  2.000000\n1     0.368139  0.736278\n2     0.135501  0.271002\n```\n\n### Higher-order systems\n\nTo define a higher-order system,\nwe have to assign an initial condition to the derivative of a variable:\n\n```python\n>>> from poincare import Derivative\n>>> class Oscillator(System):\n...   x: Variable = initial(default=1)\n...   v: Derivative = x.derive(initial=0)\n...   eq = v.derive() << -x\n...\n>>> Simulator(Oscillator).solve(save_at=range(3))\n             x         v\ntime\n0     1.000000  0.000000\n1     0.540366 -0.841561\n2    -0.416308 -0.909791\n```\n\n### Non-autonomous systems\n\nTo use the independent variable,\nwe create an instance of `Independent`:\n\n```python\n>>> from poincare import Independent\n>>> class NonAutonomous(System):\n...   time = Independent()\n...   x: Variable = initial(default=0)\n...   eq = x.derive() << 2 * time\n...\n>>> Simulator(NonAutonomous).solve(save_at=range(3))\n             x\ntime\n0     0.000000\n1     1.000001\n2     4.000001\n```\n\n### Constants, Parameters, and functions\n\nBesides variables,\nwe can define parameters and constants,\nand use functions from [symbolite](https://github.com/hgrecco/symbolite).\n\n#### Constants\n\nConstants allow to define common initial conditions for Variables and Derivatives:\n\n```python\n>>> from poincare import assign, Constant\n>>> class Model(System):\n...     c: Constant = assign(default=1, constant=True)\n...     x: Variable = initial(default=c)\n...     y: Variable = initial(default=2 * c)\n...     eq_x = x.derive() << -x\n...     eq_y = y.derive() << -y\n...\n>>> Simulator(Model).solve(save_at=range(3))\n             x         y\ntime\n0     1.000000  2.000000\n1     0.368139  0.736278\n2     0.135501  0.271002\n```\n\nNow, we can vary their initial conditions jointly:\n\n```python\n>>> Simulator(Model).solve(values={Model.c: 2}, save_at=range(3))\n             x         y\ntime\n0     2.000000  4.000000\n1     0.736278  1.472556\n2     0.271001  0.542003\n```\n\nBut we can break that connection by passing `y`'s initial value directly:\n\n```python\n>>> Simulator(Model).solve(values={Model.c: 2, Model.y: 2}, save_at=range(3))\n             x         y\ntime\n0     2.000000  2.000000\n1     0.736278  0.736278\n2     0.271002  0.271002\n```\n\n#### Parameters\n\nParameters are like Variables,\nbut their time evolution is given directly as a function of time,\nVariables, Constants and other Parameters:\n\n```python\n>>> from poincare import Parameter\n>>> class Model(System):\n...     p: Parameter = assign(default=1)\n...     x: Variable = initial(default=1)\n...     eq = x.derive() << -p * x\n...\n>>> Simulator(Model).solve(save_at=range(3))\n             x\ntime\n0     1.000000\n1     0.368139\n2     0.135501\n```\n\n#### Functions\n\nSymbolite functions are accessible from the `symbolite.scalar` module:\n\n```python\n>>> from symbolite import scalar\n>>> class Model(System):\n...     x: Variable = initial(default=1)\n...     eq = x.derive() << scalar.sin(x)\n...\n>>> Simulator(Model).solve(save_at=range(3))\n             x\ntime\n0     1.000000\n1     1.951464\n2     2.654572\n```\n\n### Units\n\npoincar\u00e9 also supports functions through\n[`pint`](https://github.com/hgrecco/pint)\nand [`pint-pandas`](https://github.com/hgrecco/pint-pandas).\n\n```python\n>>> import pint\n>>> unit = pint.get_application_registry()\n>>> class Model(System):\n...     x: Variable = initial(default=1 * unit.m)\n...     v: Derivative = x.derive(initial=0 * unit.m/unit.s)\n...     w: Parameter = assign(default=1 * unit.Hz)\n...     eq = v.derive() << -w**2 * x\n...\n>>> result = Simulator(Model).solve(save_at=range(3))\n```\n\nThe columns have units of `m` and `m/s`, respectively.\n`pint` raises a `DimensionalityError` if we try to add them:\n\n```python\n>>> result[\"x\"] + result[\"v\"]\nTraceback (most recent call last):\n...\npint.errors.DimensionalityError: Cannot convert from 'meter' ([length]) to 'meter / second' ([length] / [time])\n```\n\nWe can remove the units and set them as string metadata with:\n\n```python\n>>> result.pint.dequantify()\n             x              v\nunit     meter meter / second\ntime\n0          1.0            0.0\n1     0.540366      -0.841561\n2    -0.416308      -0.909791\n```\n\nwhich allows to plot the DataFrame with `.plot()`.\n\n## Installation\n\nIt can be installed from PyPI:\n\n```\npip install -U poincare\n```\n\nor conda-forge:\n\n```\nconda install -c conda-forge poincare\n```\n\n## Development\n\nThis project is managed by [pixi](https://pixi.sh).\nYou can install it for development using:\n\n```sh\ngit clone https://github.com/{{ github_username }}/{{ project_name }}\ncd {{ project_name }}\npixi run pre-commit-install\n```\n\nPre-commit hooks are used to lint and format the project.\n\n### Testing\n\nRun tests using:\n\n```sh\npixi run test\n```\n",
    "bugtrack_url": null,
    "license": "MIT",
    "summary": "Simulation of dynamical systems.",
    "version": "0.6.1",
    "project_urls": {
        "Documentation": "https://maurosilber.github.io/poincare",
        "Homepage": "https://github.com/maurosilber/poincare",
        "Issues": "https://github.com/maurosilber/poincare/issues"
    },
    "split_keywords": [
        "ode",
        " differential equations",
        " dynamical systems"
    ],
    "urls": [
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "d0228a914d715df57cc66784e0b9d97a7a78c059e2f73151faff0655cece9844",
                "md5": "642ff53746d723b63dbbc528330c4574",
                "sha256": "9a1d4811610a23d40a0d9aa3eaef25ffbac907c533459583bef02f6c02e7bd78"
            },
            "downloads": -1,
            "filename": "poincare-0.6.1-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "642ff53746d723b63dbbc528330c4574",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.10",
            "size": 36981,
            "upload_time": "2024-12-17T15:11:59",
            "upload_time_iso_8601": "2024-12-17T15:11:59.496214Z",
            "url": "https://files.pythonhosted.org/packages/d0/22/8a914d715df57cc66784e0b9d97a7a78c059e2f73151faff0655cece9844/poincare-0.6.1-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "c3b5d73560e520bb70b346cb680db9bf6a3d8d635e595fdc8ec1f8866beff776",
                "md5": "c7d0358ab5664a3556b389a15bb9b8bb",
                "sha256": "0c4026e3a940f1a40d483f33b70ecd5e1977295e08489c34378a89a4a9161d58"
            },
            "downloads": -1,
            "filename": "poincare-0.6.1.tar.gz",
            "has_sig": false,
            "md5_digest": "c7d0358ab5664a3556b389a15bb9b8bb",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.10",
            "size": 76897,
            "upload_time": "2024-12-17T15:12:01",
            "upload_time_iso_8601": "2024-12-17T15:12:01.787343Z",
            "url": "https://files.pythonhosted.org/packages/c3/b5/d73560e520bb70b346cb680db9bf6a3d8d635e595fdc8ec1f8866beff776/poincare-0.6.1.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2024-12-17 15:12:01",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "maurosilber",
    "github_project": "poincare",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": true,
    "lcname": "poincare"
}
        
Elapsed time: 0.75814s