Package: poincare
Poincaré: simulation of dynamical systemsPoincaré allows to define and simulate dynamical systems in Python.DefinitionTo define the system$$ \frac{dx}{dt} = -x \quad \text{with} \quad x(0) = 1 $$we write can:>>>frompoincareimportVariable,System,initial>>>classModel(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...SimulationTo simulate that system,
we do:>>>frompoincareimportSimulator>>>sim=Simulator(Model)>>>sim.solve(save_at=range(3))xtime01.00000010.36813920.135501The output is apandas.DataFrame,
which can be plotted with.plot().Changing initial conditionsTo 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))xtime02.00000010.73627820.271002which reuses the previously compiled model in theSimulatorinstance.Instantiating the model with other values:>>>Simulator(Model(x=2)).solve(save_at=range(3))xtime02.00000010.73627820.271002This second option allows to compose systems
into bigger systems.
See the example inexamples/oscillators.py.Transforming the outputWe 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))x2xtime01.0000002.00000010.3681390.73627820.1355010.271002Higher-order systemsTo define a higher-order system,
we have to assign an initial condition to the derivative of a variable:>>>frompoincareimportDerivative>>>classOscillator(System):...x:Variable=initial(default=1)...v:Derivative=x.derive(initial=0)...eq=v.derive()<<-x...>>>Simulator(Oscillator).solve(save_at=range(3))xvtime01.0000000.00000010.540366-0.8415612-0.416308-0.909791Constants, Parameters, and functionsBesides variables,
we can define parameters and constants,
and use functions fromsymbolite.ConstantsConstants allow to define common initial conditions for Variables and Derivatives:>>>frompoincareimportassign,Constant>>>classModel(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))xytime01.0000002.00000010.3681390.73627820.1355010.271002Now, we can vary their initial conditions jointly:>>>Simulator(Model(c=2)).solve(save_at=range(3))xytime02.0000004.00000010.7362781.47255620.2710010.542003But we can break that connection by passingyinitial value directly:>>>Simulator(Model(c=2,y=2)).solve(save_at=range(3))xytime02.0000002.00000010.7362780.73627820.2710020.271002ParametersParameters are like Variables,
but their time evolution is given directly as a function of time,
Variables, Constants and other Parameters:>>>frompoincareimportParameter>>>classModel(System):...p:Parameter=assign(default=1)...x:Variable=initial(default=1)...eq=x.derive()<<-p*x...>>>Simulator(Model).solve(save_at=range(3))xtime01.00000010.36813920.135501FunctionsSymbolite functions are accessible from thesymbolite.scalarmodule:>>>fromsymboliteimportscalar>>>classModel(System):...x:Variable=initial(default=1)...eq=x.derive()<<scalar.sin(x)...>>>Simulator(Model).solve(save_at=range(3))xtime01.00000011.95146422.654572Unitspoincaré also supports functions throughpintandpint-pandas.>>>importpint>>>unit=pint.get_application_registry()>>>classModel(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.pintraises aDimensionalityErrorif we try to add them:>>>result["x"]+result["v"]Traceback(mostrecentcalllast):...pint.errors.DimensionalityError:Cannotconvertfrom'meter'([length])to'meter / second'([length]/[time])We can remove the units and set them as string metadata with:>>>result.pint.dequantify()xvunitmetermeter/secondtime01.0000000.00000010.540366-0.8415612-0.416308-0.909791which allows to plot the DataFrame with.plot().Installationpipinstall-Upoincare