xpplab 0.1.1__py3-none-win_amd64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- xpplab/__init__.py +23 -0
- xpplab/_docs/auto.md +102 -0
- xpplab/_docs/odefile.md +88 -0
- xpplab/_engine.py +331 -0
- xpplab/_lib/libxppe.dll +0 -0
- xpplab/analysis.py +405 -0
- xpplab/animation.py +136 -0
- xpplab/auto.py +370 -0
- xpplab/examples/6x6.ani +7 -0
- xpplab/examples/6x6.ode +47 -0
- xpplab/examples/SM2.dat +601 -0
- xpplab/examples/SM3.dat +259 -0
- xpplab/examples/UM0.dat +601 -0
- xpplab/examples/UM1.dat +601 -0
- xpplab/examples/V0.tab +14 -0
- xpplab/examples/acoaster.ani +17 -0
- xpplab/examples/acoaster.ode +49 -0
- xpplab/examples/ai.ode +7 -0
- xpplab/examples/allinfo.dat +146 -0
- xpplab/examples/amari.ode +12 -0
- xpplab/examples/amari2.ode +12 -0
- xpplab/examples/amarig.ode +12 -0
- xpplab/examples/angela.ode +5 -0
- xpplab/examples/atcoaster.ani +16 -0
- xpplab/examples/atcoaster.ode +49 -0
- xpplab/examples/bnn.ode +8 -0
- xpplab/examples/bob.ode +3 -0
- xpplab/examples/borcol.ode +11 -0
- xpplab/examples/cable.ode +20 -0
- xpplab/examples/candelator.ode +16 -0
- xpplab/examples/chemo.ani +3 -0
- xpplab/examples/chemotax.ode +12 -0
- xpplab/examples/clustor.ode +26 -0
- xpplab/examples/coaster2D.ani +16 -0
- xpplab/examples/coaster2D.ode +46 -0
- xpplab/examples/cobweb2.ode +26 -0
- xpplab/examples/crazy.ode +5 -0
- xpplab/examples/cuplamdif.ode +23 -0
- xpplab/examples/curve.dat +100 -0
- xpplab/examples/curve2.dat +100 -0
- xpplab/examples/curve3.dat +100 -0
- xpplab/examples/curve4.dat +100 -0
- xpplab/examples/dae.ode +10 -0
- xpplab/examples/dae_ex1.ode +7 -0
- xpplab/examples/dae_ex2.ode +7 -0
- xpplab/examples/dae_ex3.ode +10 -0
- xpplab/examples/dde.ode +7 -0
- xpplab/examples/del_log.ode +9 -0
- xpplab/examples/delay.ode +17 -0
- xpplab/examples/delta.ode +8 -0
- xpplab/examples/diagram.dat +146 -0
- xpplab/examples/dirfields.dat +289 -0
- xpplab/examples/dlog.ode +6 -0
- xpplab/examples/doubpend.ani +7 -0
- xpplab/examples/doubpend.ode +11 -0
- xpplab/examples/duck.ani +11 -0
- xpplab/examples/duck.ode +31 -0
- xpplab/examples/duck2.ani +13 -0
- xpplab/examples/duck3.ani +18 -0
- xpplab/examples/duckx.ode +31 -0
- xpplab/examples/dumbbvp.ode +8 -0
- xpplab/examples/ecofilip.ode +7 -0
- xpplab/examples/efm.ode +9 -0
- xpplab/examples/efmplot.ode +9 -0
- xpplab/examples/efmx.ode +11 -0
- xpplab/examples/efmxfb.ode +12 -0
- xpplab/examples/efmxplot.ode +22 -0
- xpplab/examples/efmxplott.ode +26 -0
- xpplab/examples/efmxrun.ode +11 -0
- xpplab/examples/elaspen.ani +7 -0
- xpplab/examples/elaspen.ode +22 -0
- xpplab/examples/equil.dat +2 -0
- xpplab/examples/ev1.ode +16 -0
- xpplab/examples/evelyn.ode +16 -0
- xpplab/examples/fancylorenz.ode +28 -0
- xpplab/examples/fhn.ode +10 -0
- xpplab/examples/fhn3d.ode +8 -0
- xpplab/examples/fhn_noise.ode +8 -0
- xpplab/examples/fieldnoy.ode +17 -0
- xpplab/examples/fish.ani +6 -0
- xpplab/examples/forcpend.ode +6 -0
- xpplab/examples/fr.ode +39 -0
- xpplab/examples/gausvolt.ode +5 -0
- xpplab/examples/geisel.ode +10 -0
- xpplab/examples/getfrefm.ode +15 -0
- xpplab/examples/gill_bruss.ode +22 -0
- xpplab/examples/greg.ode +13 -0
- xpplab/examples/hedge.ode +2 -0
- xpplab/examples/henhei.ode +12 -0
- xpplab/examples/hhred.ode +29 -0
- xpplab/examples/iaf.ode +4 -0
- xpplab/examples/idoubpend.ani +13 -0
- xpplab/examples/idoubpend.ode +16 -0
- xpplab/examples/ielaspen.ani +20 -0
- xpplab/examples/ielaspen.ode +25 -0
- xpplab/examples/invpend.ani +7 -0
- xpplab/examples/invpend.ode +12 -0
- xpplab/examples/itoy.ani +27 -0
- xpplab/examples/itoy.ode +103 -0
- xpplab/examples/jcoaster.ani +20 -0
- xpplab/examples/jcoaster.ode +80 -0
- xpplab/examples/jcoaster.old.ani +16 -0
- xpplab/examples/jcoaster1.ani +20 -0
- xpplab/examples/jcoaster1.ode +80 -0
- xpplab/examples/julia.ode +10 -0
- xpplab/examples/junk.ode +10 -0
- xpplab/examples/junk2.ode +9 -0
- xpplab/examples/kepler.ode +14 -0
- xpplab/examples/koho.ode +46 -0
- xpplab/examples/koho2.ode +38 -0
- xpplab/examples/koho3.ode +40 -0
- xpplab/examples/kohonen.ode +31 -0
- xpplab/examples/kohox.ode +11 -0
- xpplab/examples/kuramot100.ode +9 -0
- xpplab/examples/kuramot100m.ode +11 -0
- xpplab/examples/kuramot100w.ode +10 -0
- xpplab/examples/lamomeg.ode +16 -0
- xpplab/examples/lamprey.ode +13 -0
- xpplab/examples/lamvolt.ode +26 -0
- xpplab/examples/lecar (Dan Dougherty's conflicted copy 2017-01-01).ode +60 -0
- xpplab/examples/lecar (Dan Dougherty's conflicted copy 2017-06-03).ode +60 -0
- xpplab/examples/lecar.ode +61 -0
- xpplab/examples/lecdiff200.ode +31 -0
- xpplab/examples/lecsimp2.ode +31 -0
- xpplab/examples/lecwave.ode +27 -0
- xpplab/examples/lin.ode +17 -0
- xpplab/examples/linc.ode +5 -0
- xpplab/examples/lo.ode +7 -0
- xpplab/examples/logbif.ode +21 -0
- xpplab/examples/lor2.ode +17 -0
- xpplab/examples/lorenz.ani +11 -0
- xpplab/examples/lorenz.ode +13 -0
- xpplab/examples/lorenz2.ode +28 -0
- xpplab/examples/lorknead.ode +19 -0
- xpplab/examples/lorliap.ode +21 -0
- xpplab/examples/lorpecor.ode +23 -0
- xpplab/examples/minode.ode +22 -0
- xpplab/examples/ml-range.ode +34 -0
- xpplab/examples/ml.ani +2 -0
- xpplab/examples/ml1.ode +32 -0
- xpplab/examples/mlvolt.ode +18 -0
- xpplab/examples/myret.ode +7 -0
- xpplab/examples/nc.ani +5 -0
- xpplab/examples/nf3.ode +17 -0
- xpplab/examples/nnet.ode +8 -0
- xpplab/examples/nnet2.ode +9 -0
- xpplab/examples/nochaos.ode +6 -0
- xpplab/examples/nthet.ode +10 -0
- xpplab/examples/nullclines.dat +1221 -0
- xpplab/examples/osc.ani +32 -0
- xpplab/examples/osc.ode +18 -0
- xpplab/examples/osc1.ani +32 -0
- xpplab/examples/pHtools_ml.ode +51 -0
- xpplab/examples/pHtools_sillen.ode +53 -0
- xpplab/examples/pHtools_simple.ode +8 -0
- xpplab/examples/pend-cart1-kick.ode +35 -0
- xpplab/examples/pend-cart1.ani +4 -0
- xpplab/examples/pend-cart1.ode +30 -0
- xpplab/examples/pend.ani +26 -0
- xpplab/examples/pend.ode +21 -0
- xpplab/examples/pend2.ani +35 -0
- xpplab/examples/pendx.ani +30 -0
- xpplab/examples/pendx.ode +11 -0
- xpplab/examples/phs.ode +7 -0
- xpplab/examples/pp.ode +10 -0
- xpplab/examples/r3b.ode +13 -0
- xpplab/examples/ross-orbit.ode +7 -0
- xpplab/examples/rosscmpliap.ode +88 -0
- xpplab/examples/rossler-pecora.ode +23 -0
- xpplab/examples/rossler.ode +6 -0
- xpplab/examples/rossliap2d.dat +2601 -0
- xpplab/examples/simplecube.ode +7 -0
- xpplab/examples/sine-circel.ode +12 -0
- xpplab/examples/test.ode +4 -0
- xpplab/examples/testdll.ode +7 -0
- xpplab/examples/torus.ode +9 -0
- xpplab/examples/toy.ani +20 -0
- xpplab/examples/toy_ok.ode +104 -0
- xpplab/examples/transient.ode +8 -0
- xpplab/examples/triple.ode +5 -0
- xpplab/examples/tst.ode +3 -0
- xpplab/examples/tstar.ode +2 -0
- xpplab/examples/tstdel.ode +11 -0
- xpplab/examples/tstdelsp.ode +12 -0
- xpplab/examples/tstdll.ode +61 -0
- xpplab/examples/tstdll2.ode +11 -0
- xpplab/examples/tstset.ode +4 -0
- xpplab/examples/tstvol2.ode +8 -0
- xpplab/examples/tstvol3.ode +7 -0
- xpplab/examples/vanderpol.ode +7 -0
- xpplab/examples/vdp.ode +10 -0
- xpplab/examples/vlsi.ode +15 -0
- xpplab/examples/voltex1.ode +4 -0
- xpplab/examples/voltex2.ode +4 -0
- xpplab/examples/voltex3.ode +4 -0
- xpplab/examples/voltex4.ode +6 -0
- xpplab/examples/vtst.ode +10 -0
- xpplab/examples/waterwheel.ani +9 -0
- xpplab/examples/waterwheel.ode +21 -0
- xpplab/examples/wave (Dan Dougherty's conflicted copy 2017-01-01).ode +15 -0
- xpplab/examples/wave (Dan Dougherty's conflicted copy 2017-06-03).ode +15 -0
- xpplab/examples/wave.ani +3 -0
- xpplab/examples/wave.ode +16 -0
- xpplab/examples/wavepp.ani +3 -0
- xpplab/examples/wcring.ode +6 -0
- xpplab/examples/wcstim.ani +4 -0
- xpplab/examples/wcstim.ode +10 -0
- xpplab/examples/wta.ode +13 -0
- xpplab/mcp/__init__.py +39 -0
- xpplab/mcp/__main__.py +7 -0
- xpplab/mcp/diagnose.py +252 -0
- xpplab/mcp/server.py +515 -0
- xpplab/mcp/session.py +1657 -0
- xpplab/model.py +556 -0
- xpplab/plotting.py +165 -0
- xpplab-0.1.1.dist-info/METADATA +84 -0
- xpplab-0.1.1.dist-info/RECORD +221 -0
- xpplab-0.1.1.dist-info/WHEEL +5 -0
- xpplab-0.1.1.dist-info/entry_points.txt +3 -0
- xpplab-0.1.1.dist-info/licenses/LICENSE +282 -0
- xpplab-0.1.1.dist-info/licenses/engine/xppaut/LICENSE +282 -0
xpplab/__init__.py
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""XPP Lab: XPPAUT's simulation and AUTO bifurcation engine for Python.
|
|
2
|
+
|
|
3
|
+
import xpplab as xs
|
|
4
|
+
m = xs.load("lecar.ode")
|
|
5
|
+
sol = m.run(total=100)
|
|
6
|
+
d = m.auto.equilibria("iapp")
|
|
7
|
+
d.special()
|
|
8
|
+
|
|
9
|
+
The numerics are XPPAUT 8.0's own code, compiled into a native
|
|
10
|
+
library. No Fortran or C compiler and no X11 server are needed.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from ._engine import EngineError, find_engine_library
|
|
14
|
+
from .analysis import Table
|
|
15
|
+
from .animation import Animation
|
|
16
|
+
from .auto import Diagram, Point
|
|
17
|
+
from .model import METHODS, Equilibrium, Model, Trajectory, load
|
|
18
|
+
|
|
19
|
+
__all__ = ["Model", "Trajectory", "Equilibrium", "Diagram", "Point", "Table", "Animation",
|
|
20
|
+
"EngineError", "METHODS", "load", "find_engine_library",
|
|
21
|
+
"__version__"]
|
|
22
|
+
|
|
23
|
+
__version__ = "0.1.1"
|
xpplab/_docs/auto.md
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# Bifurcation analysis with AUTO
|
|
2
|
+
|
|
3
|
+
A **bifurcation diagram** shows how the long-term behaviour of a system
|
|
4
|
+
changes as one parameter varies: where equilibria appear, vanish or lose
|
|
5
|
+
stability, and where oscillations are born. XPP Lab computes these
|
|
6
|
+
diagrams with **AUTO** (E. Doedel), the same continuation code that XPPAUT
|
|
7
|
+
embeds.
|
|
8
|
+
|
|
9
|
+
## The idea in brief
|
|
10
|
+
|
|
11
|
+
Pick a parameter, say a current `I`. AUTO starts from an equilibrium and
|
|
12
|
+
follows it step by step as `I` changes. At each step it solves for the
|
|
13
|
+
equilibrium and checks its stability. When something qualitative happens it
|
|
14
|
+
marks a **special point**:
|
|
15
|
+
|
|
16
|
+
| Label | Name | What happens |
|
|
17
|
+
|---|---|---|
|
|
18
|
+
| LP | limit point (fold, saddle-node) | Two equilibria meet and vanish; the branch turns back. |
|
|
19
|
+
| HB | Hopf bifurcation | A pair of eigenvalues crosses the imaginary axis; periodic orbits are born. |
|
|
20
|
+
| BP | branch point | Two branches cross (common with symmetry). |
|
|
21
|
+
| PD | period doubling | An orbit loses stability to one of twice the period. |
|
|
22
|
+
| TR | torus (Neimark-Sacker) | An orbit loses stability to quasi-periodic motion. |
|
|
23
|
+
| LP (on orbits) | fold of limit cycles | Two periodic orbits meet and vanish. |
|
|
24
|
+
| EP | end point | Where the computation stopped. |
|
|
25
|
+
|
|
26
|
+
Stable parts are drawn solid, unstable parts dashed. The colours follow
|
|
27
|
+
XPPAUT: steady states are red (stable) and black (unstable); periodic orbits
|
|
28
|
+
are green (stable) and blue (unstable).
|
|
29
|
+
|
|
30
|
+
## A typical session
|
|
31
|
+
|
|
32
|
+
1. **Start at an equilibrium.** Run the model until it settles, or press
|
|
33
|
+
**Equilibrium**. **Follow steady states** also polishes the start point
|
|
34
|
+
with Newton's method, but AUTO needs to begin close to an equilibrium.
|
|
35
|
+
2. **Choose the parameter** and its range (**Par min** and **Par max**).
|
|
36
|
+
3. **Follow steady states.** The special points appear in the table and on
|
|
37
|
+
the plot.
|
|
38
|
+
4. **Select a special point** (click it in the table or the plot) and choose
|
|
39
|
+
what to do from there:
|
|
40
|
+
- from **HB**: *Periodic orbits from this Hopf point*, or a two-parameter
|
|
41
|
+
curve of Hopf points;
|
|
42
|
+
- from **LP**: a two-parameter curve of folds;
|
|
43
|
+
- from **BP**: *Switch to the other branch*;
|
|
44
|
+
- from **PD**: the period-doubled branch.
|
|
45
|
+
5. For two-parameter curves, choose the **2nd param.** first. The result
|
|
46
|
+
appears in the **Two parameters** tab.
|
|
47
|
+
|
|
48
|
+
Try it: [open the Morris-Lecar model](example:lecar.ode), apply the `hopf`
|
|
49
|
+
set (Workspace > Sets), then [follow the steady states in iapp](cmd:auto steady iapp).
|
|
50
|
+
|
|
51
|
+
## Check the diagram with simulations
|
|
52
|
+
|
|
53
|
+
**Workspace > Range** (XPPAUT's *Initial conds > Range*) runs the model
|
|
54
|
+
once for each value of a parameter, and in *Max / min* mode records the
|
|
55
|
+
maximum and minimum of a variable after a transient. When the parameter and
|
|
56
|
+
the plotted variable match the Bifurcation window's, the results appear as
|
|
57
|
+
grey dots on AUTO's diagram. They should sit on the stable branches: the
|
|
58
|
+
solid red lines and the solid green ones. Where the system is bistable,
|
|
59
|
+
different initial values land on different branches; tick *Start each run
|
|
60
|
+
where the last ended* to follow one branch as the parameter moves.
|
|
61
|
+
|
|
62
|
+
## The numerical settings
|
|
63
|
+
|
|
64
|
+
| Setting | Meaning | Advice |
|
|
65
|
+
|---|---|---|
|
|
66
|
+
| Step DS | Size of the first step. **Its sign is the direction.** | Use a negative DS to explore towards smaller parameter values. |
|
|
67
|
+
| DS min, DS max | Bounds on the adaptive step. | A smaller DS max gives smoother curves and fewer missed points. |
|
|
68
|
+
| Par min, Par max | The parameter range. | AUTO stops at the edges. |
|
|
69
|
+
| Max steps (NMAX) | Most steps per run. | A closed loop of orbits runs until NMAX: lower it to stop sooner. |
|
|
70
|
+
| Label every (NPR) | Also label a point every N steps. | Labelled points can be continued from. |
|
|
71
|
+
| Mesh NTST | Mesh intervals for periodic orbits. | Increase it (to 50 or 100) for spiky orbits such as action potentials. |
|
|
72
|
+
|
|
73
|
+
## When things go wrong
|
|
74
|
+
|
|
75
|
+
- **"AUTO computed no points"**: the start point is probably not an
|
|
76
|
+
equilibrium. Run the model to steady state first, or press Equilibrium.
|
|
77
|
+
- **The branch stops early**: check Par min and Par max, and Norm max.
|
|
78
|
+
Then try a smaller DS max.
|
|
79
|
+
- **Periodic orbits fail from a Hopf point**: increase NTST, or reduce DS.
|
|
80
|
+
- **Points come out jagged**: reduce DS max.
|
|
81
|
+
|
|
82
|
+
## Scripting the same analysis
|
|
83
|
+
|
|
84
|
+
Every step is also available from the command window:
|
|
85
|
+
|
|
86
|
+
```
|
|
87
|
+
use hopf
|
|
88
|
+
auto set ds=0.02 dsmax=0.05 parmax=0.6
|
|
89
|
+
auto steady iapp
|
|
90
|
+
auto from 2 periodic
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
and from Python:
|
|
94
|
+
|
|
95
|
+
```
|
|
96
|
+
import xpplab as xs
|
|
97
|
+
m = xs.load("lecar.ode")
|
|
98
|
+
m.apply_set("hopf")
|
|
99
|
+
d = m.auto.equilibria("iapp")
|
|
100
|
+
orbits = m.auto.continue_from(d.first("HB"), "periodic")
|
|
101
|
+
m.auto.diagram.plot()
|
|
102
|
+
```
|
xpplab/_docs/odefile.md
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# The .ode file format
|
|
2
|
+
|
|
3
|
+
A model is a plain text file, usually ending in `.ode`. XPP Lab reads
|
|
4
|
+
exactly the format of XPPAUT 8.0, so files written for XPPAUT work
|
|
5
|
+
unchanged. Each statement goes on its own line, and the file ends with
|
|
6
|
+
`done`. Upper and lower case are the same in names.
|
|
7
|
+
|
|
8
|
+
## A complete example
|
|
9
|
+
|
|
10
|
+
```
|
|
11
|
+
# Lotka-Volterra predator-prey model
|
|
12
|
+
x' = a*x - b*x*y
|
|
13
|
+
y' = -c*y + d*x*y
|
|
14
|
+
par a=1, b=0.5, c=0.75, d=0.25
|
|
15
|
+
init x=2, y=1
|
|
16
|
+
aux total = x + y
|
|
17
|
+
@ total=50, dt=0.02, xp=x, yp=y, xlo=0, xhi=8, ylo=0, yhi=6
|
|
18
|
+
done
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Equations
|
|
22
|
+
|
|
23
|
+
| Form | Meaning |
|
|
24
|
+
|---|---|
|
|
25
|
+
| `x' = f` or `dx/dt = f` | A differential equation. |
|
|
26
|
+
| `x(t+1) = f` | A map (difference equation); use method `discrete`. |
|
|
27
|
+
| `x(t) = f` | A Volterra integral equation (`f` may contain `int{...}`). |
|
|
28
|
+
| `0 = f` | An algebraic constraint (with `solv` variables: a DAE). |
|
|
29
|
+
| `s = expression` | A fixed (intermediate) quantity, computed at each step. |
|
|
30
|
+
| `aux name = expression` | An auxiliary output: plotted and saved, but not integrated. |
|
|
31
|
+
|
|
32
|
+
## Declarations
|
|
33
|
+
|
|
34
|
+
| Statement | Meaning |
|
|
35
|
+
|---|---|
|
|
36
|
+
| `par a=1, b=2` | Parameters. |
|
|
37
|
+
| `init x=0.1, y=0` | Initial values (`x(0)=0.1` also works). |
|
|
38
|
+
| `number pi2=6.2832` | A constant that cannot be changed while running. |
|
|
39
|
+
| `f(u,v) = u^2 - v` | A user function, with up to 9 arguments. |
|
|
40
|
+
| `!d = 2*a` | A derived parameter, recomputed when parameters change. |
|
|
41
|
+
| `table g g.tab` | A function tabulated in a file. |
|
|
42
|
+
| `wiener w` | A Wiener process for noise; use `euler` or `backeul`. |
|
|
43
|
+
| `markov z 2` | A Markov chain variable with 2 states (transition table follows). |
|
|
44
|
+
| `global 1 x-1 {x=0}` | An event: when `x-1` crosses zero upwards, reset `x=0`. |
|
|
45
|
+
| `bdry x-1` | A boundary condition for boundary-value problems. |
|
|
46
|
+
| `set name {a=2, total=50}` | A named parameter set. |
|
|
47
|
+
| `#include file.inc` | Insert another file. |
|
|
48
|
+
|
|
49
|
+
Arrays save typing: `x[1..10]' = -x[j] + x[j-1]` defines ten equations,
|
|
50
|
+
with `j` running from 1 to 10.
|
|
51
|
+
|
|
52
|
+
## Expressions
|
|
53
|
+
|
|
54
|
+
Arithmetic: `+ - * / ^` and parentheses. Comparisons and logic
|
|
55
|
+
(`< > <= >= == != & |`) give 1 or 0, so `(x>0)*x` is max(x, 0).
|
|
56
|
+
|
|
57
|
+
Functions: `sin cos tan asin acos atan atan2 sinh cosh tanh exp ln log
|
|
58
|
+
log10 sqrt abs heav sign mod max min flr ran normal besselj bessely erf erfc
|
|
59
|
+
hom_bcs`, and `if(cond)then(a)else(b)`.
|
|
60
|
+
|
|
61
|
+
Special terms: `t` is time. `delay(x, tau)` or `x(t-tau)` is a delayed
|
|
62
|
+
value. `pi` is 3.14159...
|
|
63
|
+
|
|
64
|
+
## Options (@ lines)
|
|
65
|
+
|
|
66
|
+
Options set how the model runs. They can be combined on one line:
|
|
67
|
+
`@ total=100, dt=0.05, meth=stiff`.
|
|
68
|
+
|
|
69
|
+
| Option | Meaning |
|
|
70
|
+
|---|---|
|
|
71
|
+
| `total`, `dt`, `t0`, `trans` | Integration time, step, start time, transient to discard. |
|
|
72
|
+
| `meth` | Method: `rungekutta`, `euler`, `modeuler`, `adams`, `gear`, `stiff`, `cvode`, `5dp`, `83dp`, `2rb`, `backeul`, `qualrk`, `volterra`, `ymp`, `discrete`. |
|
|
73
|
+
| `tol`, `atol` | Relative and absolute tolerances of adaptive methods. |
|
|
74
|
+
| `dtmin`, `dtmax` | Limits on the adaptive step. |
|
|
75
|
+
| `bound` | Stop if a variable exceeds this size. |
|
|
76
|
+
| `nout` | Store every n-th step. |
|
|
77
|
+
| `maxstor` | Maximum number of stored points. |
|
|
78
|
+
| `delay` | Maximum delay, for delay equations. |
|
|
79
|
+
| `xp`, `yp`, `xlo`, `xhi`, `ylo`, `yhi` | The phase-plane variables and window. |
|
|
80
|
+
| `nmesh` | Grid for nullclines. |
|
|
81
|
+
| `poimap`, `poivar`, `poipln`, `poisgn` | Poincare sections. |
|
|
82
|
+
| `ds`, `dsmin`, `dsmax`, `parmin`, `parmax`, `nmax`, `npr`, `ntst` | AUTO settings. |
|
|
83
|
+
|
|
84
|
+
## Comments
|
|
85
|
+
|
|
86
|
+
Lines starting with `#` are comments. The first comment lines of a file are
|
|
87
|
+
shown as its description in the Models window, so describe your model there.
|
|
88
|
+
Lines starting with `"` are XPPAUT's interactive tutorial text.
|
xpplab/_engine.py
ADDED
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
"""Low-level access to the XPPAUT engine library (engine/api/xppe.h).
|
|
2
|
+
|
|
3
|
+
XPPAUT keeps its state in global variables, so one loaded copy of the
|
|
4
|
+
engine library holds one model. Each Engine therefore loads its own private
|
|
5
|
+
copy of the library file. The copies are independent: several models can
|
|
6
|
+
be open at once, and closing one frees its memory.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import ctypes
|
|
12
|
+
import os
|
|
13
|
+
import shutil
|
|
14
|
+
import sys
|
|
15
|
+
import tempfile
|
|
16
|
+
import threading
|
|
17
|
+
from contextlib import contextmanager
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
|
|
20
|
+
__all__ = ["Engine", "EngineError", "find_engine_library"]
|
|
21
|
+
|
|
22
|
+
API_VERSION = 2
|
|
23
|
+
|
|
24
|
+
OK = 0
|
|
25
|
+
ERR_STATE = -1
|
|
26
|
+
ERR_LOAD = -2
|
|
27
|
+
ERR_FATAL = -3
|
|
28
|
+
ERR_NAME = -4
|
|
29
|
+
ERR_RANGE = -5
|
|
30
|
+
ERR_NUMERIC = -6
|
|
31
|
+
|
|
32
|
+
VARIABLES = 0
|
|
33
|
+
PARAMETERS = 1
|
|
34
|
+
COLUMNS = 2
|
|
35
|
+
SETS = 3
|
|
36
|
+
|
|
37
|
+
AUTO_U0 = 0
|
|
38
|
+
AUTO_UMAX = 1
|
|
39
|
+
AUTO_UMIN = 2
|
|
40
|
+
AUTO_UMEAN = 3
|
|
41
|
+
AUTO_EIG_RE = 4
|
|
42
|
+
AUTO_EIG_IM = 5
|
|
43
|
+
AUTO_PARAMS = 6
|
|
44
|
+
|
|
45
|
+
_ERROR_NAMES = {
|
|
46
|
+
ERR_STATE: "state",
|
|
47
|
+
ERR_LOAD: "load",
|
|
48
|
+
ERR_FATAL: "fatal",
|
|
49
|
+
ERR_NAME: "name",
|
|
50
|
+
ERR_RANGE: "range",
|
|
51
|
+
ERR_NUMERIC: "numeric",
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class EngineError(RuntimeError):
|
|
56
|
+
"""An error reported by the XPPAUT engine.
|
|
57
|
+
|
|
58
|
+
``log`` holds everything the engine printed during the failing call,
|
|
59
|
+
which usually explains the problem (for example a syntax error in the
|
|
60
|
+
model file).
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
def __init__(self, message: str, code: int = 0, log: str = ""):
|
|
64
|
+
super().__init__(message)
|
|
65
|
+
self.code = code
|
|
66
|
+
self.kind = _ERROR_NAMES.get(code, "error")
|
|
67
|
+
self.log = log
|
|
68
|
+
|
|
69
|
+
def __str__(self) -> str:
|
|
70
|
+
text = super().__str__()
|
|
71
|
+
if self.log.strip():
|
|
72
|
+
tail = "\n".join(self.log.strip().splitlines()[-12:])
|
|
73
|
+
text += "\n\nEngine output (last lines):\n" + tail
|
|
74
|
+
return text
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class AutoPointInfo(ctypes.Structure):
|
|
78
|
+
_fields_ = [
|
|
79
|
+
("branch", ctypes.c_int),
|
|
80
|
+
("point", ctypes.c_int),
|
|
81
|
+
("type", ctypes.c_int),
|
|
82
|
+
("label", ctypes.c_int),
|
|
83
|
+
("stable", ctypes.c_int),
|
|
84
|
+
("periodic", ctypes.c_int),
|
|
85
|
+
("two_parameter", ctypes.c_int),
|
|
86
|
+
("par1_slot", ctypes.c_int),
|
|
87
|
+
("par2_slot", ctypes.c_int),
|
|
88
|
+
("par1", ctypes.c_double),
|
|
89
|
+
("par2", ctypes.c_double),
|
|
90
|
+
("norm", ctypes.c_double),
|
|
91
|
+
("period", ctypes.c_double),
|
|
92
|
+
("type_name", ctypes.c_char * 4),
|
|
93
|
+
]
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
class AniItem(ctypes.Structure):
|
|
97
|
+
"""One shape of an animation frame (xppe_ani_item in xppe.h)."""
|
|
98
|
+
_fields_ = [
|
|
99
|
+
("shape", ctypes.c_int),
|
|
100
|
+
("command", ctypes.c_int),
|
|
101
|
+
("filled", ctypes.c_int),
|
|
102
|
+
("x1", ctypes.c_double),
|
|
103
|
+
("y1", ctypes.c_double),
|
|
104
|
+
("x2", ctypes.c_double),
|
|
105
|
+
("y2", ctypes.c_double),
|
|
106
|
+
("color", ctypes.c_int),
|
|
107
|
+
("level", ctypes.c_double),
|
|
108
|
+
("thickness", ctypes.c_int),
|
|
109
|
+
("trail", ctypes.c_int),
|
|
110
|
+
("font_size", ctypes.c_int),
|
|
111
|
+
("serif", ctypes.c_int),
|
|
112
|
+
("text", ctypes.c_char * 128),
|
|
113
|
+
]
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def _library_name() -> str:
|
|
117
|
+
if sys.platform == "win32":
|
|
118
|
+
return "libxppe.dll"
|
|
119
|
+
if sys.platform == "darwin":
|
|
120
|
+
return "libxppe.dylib"
|
|
121
|
+
return "libxppe.so"
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def find_engine_library() -> Path:
|
|
125
|
+
"""Locate the engine library.
|
|
126
|
+
|
|
127
|
+
The XPPLAB_ENGINE environment variable wins. Otherwise use the copy
|
|
128
|
+
installed inside the package, then a development build in the
|
|
129
|
+
repository's build/ directory.
|
|
130
|
+
"""
|
|
131
|
+
env = os.environ.get("XPPLAB_ENGINE")
|
|
132
|
+
if env:
|
|
133
|
+
return Path(env)
|
|
134
|
+
here = Path(__file__).resolve().parent
|
|
135
|
+
candidates = [here / "_lib" / _library_name()]
|
|
136
|
+
repo = here.parents[2]
|
|
137
|
+
candidates += [repo / d / "engine" / _library_name()
|
|
138
|
+
for d in ("build", "build-release", "build-debug")]
|
|
139
|
+
for c in candidates:
|
|
140
|
+
if c.exists():
|
|
141
|
+
return c
|
|
142
|
+
raise EngineError(
|
|
143
|
+
"the XPP Lab engine library was not found; set XPPLAB_ENGINE "
|
|
144
|
+
"to the path of " + _library_name())
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
_SIGNATURES = {
|
|
148
|
+
"xppe_api_version": ([], ctypes.c_int),
|
|
149
|
+
"xppe_version": ([], ctypes.c_char_p),
|
|
150
|
+
"xppe_load": ([ctypes.c_char_p, ctypes.c_int,
|
|
151
|
+
ctypes.POINTER(ctypes.c_char_p)], ctypes.c_int),
|
|
152
|
+
"xppe_log": ([], ctypes.c_char_p),
|
|
153
|
+
"xppe_error": ([], ctypes.c_char_p),
|
|
154
|
+
"xppe_count": ([ctypes.c_int], ctypes.c_int),
|
|
155
|
+
"xppe_name": ([ctypes.c_int, ctypes.c_int], ctypes.c_char_p),
|
|
156
|
+
"xppe_get": ([ctypes.c_char_p, ctypes.POINTER(ctypes.c_double)],
|
|
157
|
+
ctypes.c_int),
|
|
158
|
+
"xppe_set": ([ctypes.c_char_p, ctypes.c_char_p], ctypes.c_int),
|
|
159
|
+
"xppe_set_number": ([ctypes.c_char_p, ctypes.c_double], ctypes.c_int),
|
|
160
|
+
"xppe_apply_set": ([ctypes.c_char_p], ctypes.c_int),
|
|
161
|
+
"xppe_run": ([], ctypes.c_int),
|
|
162
|
+
"xppe_rows": ([], ctypes.c_int),
|
|
163
|
+
"xppe_column": ([ctypes.c_int, ctypes.POINTER(ctypes.c_double),
|
|
164
|
+
ctypes.c_int], ctypes.c_int),
|
|
165
|
+
"xppe_equilibrium": ([ctypes.POINTER(ctypes.c_double)] * 3, ctypes.c_int),
|
|
166
|
+
"xppe_rhs": ([ctypes.c_double, ctypes.POINTER(ctypes.c_double),
|
|
167
|
+
ctypes.POINTER(ctypes.c_double)], ctypes.c_int),
|
|
168
|
+
"xppe_manifold_count": ([], ctypes.c_int),
|
|
169
|
+
"xppe_manifold": ([ctypes.c_int, ctypes.POINTER(ctypes.c_int)], ctypes.c_int),
|
|
170
|
+
"xppe_fourier": ([ctypes.c_char_p], ctypes.c_int),
|
|
171
|
+
"xppe_power": ([ctypes.c_char_p], ctypes.c_int),
|
|
172
|
+
"xppe_spectrum": ([ctypes.c_int, ctypes.c_char_p, ctypes.c_char_p,
|
|
173
|
+
ctypes.c_int, ctypes.c_int], ctypes.c_int),
|
|
174
|
+
"xppe_histogram": ([ctypes.c_char_p, ctypes.c_int, ctypes.c_double,
|
|
175
|
+
ctypes.c_double, ctypes.c_char_p], ctypes.c_int),
|
|
176
|
+
"xppe_histogram2d": ([ctypes.c_char_p, ctypes.c_int, ctypes.c_double,
|
|
177
|
+
ctypes.c_double, ctypes.c_char_p, ctypes.c_int,
|
|
178
|
+
ctypes.c_double, ctypes.c_double], ctypes.c_int),
|
|
179
|
+
"xppe_autocorrelation": ([ctypes.c_char_p, ctypes.c_int, ctypes.c_double,
|
|
180
|
+
ctypes.c_double], ctypes.c_int),
|
|
181
|
+
"xppe_correlation": ([ctypes.c_char_p, ctypes.c_char_p, ctypes.c_int,
|
|
182
|
+
ctypes.c_int], ctypes.c_int),
|
|
183
|
+
"xppe_mean": ([ctypes.c_char_p, ctypes.POINTER(ctypes.c_double),
|
|
184
|
+
ctypes.POINTER(ctypes.c_double)], ctypes.c_int),
|
|
185
|
+
"xppe_lyapunov": ([ctypes.POINTER(ctypes.c_double)], ctypes.c_int),
|
|
186
|
+
"xppe_adjoint": ([ctypes.c_int, ctypes.c_double], ctypes.c_int),
|
|
187
|
+
"xppe_hfunction": ([ctypes.c_int, ctypes.POINTER(ctypes.c_char_p)],
|
|
188
|
+
ctypes.c_int),
|
|
189
|
+
"xppe_fit": ([ctypes.c_char_p, ctypes.c_int, ctypes.c_int, ctypes.c_char_p,
|
|
190
|
+
ctypes.c_char_p, ctypes.c_char_p, ctypes.c_double,
|
|
191
|
+
ctypes.c_double, ctypes.c_int], ctypes.c_int),
|
|
192
|
+
"xppe_result_rows": ([], ctypes.c_int),
|
|
193
|
+
"xppe_result_count": ([], ctypes.c_int),
|
|
194
|
+
"xppe_result_name": ([ctypes.c_int], ctypes.c_char_p),
|
|
195
|
+
"xppe_result_column": ([ctypes.c_int, ctypes.POINTER(ctypes.c_double),
|
|
196
|
+
ctypes.c_int], ctypes.c_int),
|
|
197
|
+
"xppe_ic_last": ([], ctypes.c_int),
|
|
198
|
+
"xppe_bvp_count": ([], ctypes.c_int),
|
|
199
|
+
"xppe_bvp": ([ctypes.c_int, ctypes.c_double, ctypes.c_double], ctypes.c_int),
|
|
200
|
+
"xppe_ani_load": ([ctypes.c_char_p], ctypes.c_int),
|
|
201
|
+
"xppe_ani_window": ([ctypes.POINTER(ctypes.c_double)] * 4, ctypes.c_int),
|
|
202
|
+
"xppe_ani_frame": ([ctypes.c_int], ctypes.c_int),
|
|
203
|
+
"xppe_ani_get": ([ctypes.c_int, ctypes.POINTER(AniItem)], ctypes.c_int),
|
|
204
|
+
"xppe_nullcline": ([ctypes.c_int, ctypes.c_int, ctypes.c_int,
|
|
205
|
+
ctypes.c_double, ctypes.c_double, ctypes.c_double,
|
|
206
|
+
ctypes.c_double, ctypes.c_int,
|
|
207
|
+
ctypes.POINTER(ctypes.c_float), ctypes.c_int],
|
|
208
|
+
ctypes.c_int),
|
|
209
|
+
"xppe_auto_get": ([ctypes.c_char_p, ctypes.POINTER(ctypes.c_double)],
|
|
210
|
+
ctypes.c_int),
|
|
211
|
+
"xppe_auto_set": ([ctypes.c_char_p, ctypes.c_double], ctypes.c_int),
|
|
212
|
+
"xppe_auto_parameters": ([ctypes.c_char_p, ctypes.c_char_p], ctypes.c_int),
|
|
213
|
+
"xppe_auto_set_workdir": ([ctypes.c_char_p], ctypes.c_int),
|
|
214
|
+
"xppe_auto_reset": ([], ctypes.c_int),
|
|
215
|
+
"xppe_auto_run": ([ctypes.c_char_p], ctypes.c_int),
|
|
216
|
+
"xppe_auto_grab": ([ctypes.c_int], ctypes.c_int),
|
|
217
|
+
"xppe_auto_ungrab": ([], ctypes.c_int),
|
|
218
|
+
"xppe_auto_parameter_count": ([], ctypes.c_int),
|
|
219
|
+
"xppe_auto_parameter_name": ([ctypes.c_int], ctypes.c_char_p),
|
|
220
|
+
"xppe_auto_main_slot": ([ctypes.c_int], ctypes.c_int),
|
|
221
|
+
"xppe_auto_count": ([], ctypes.c_int),
|
|
222
|
+
"xppe_auto_point": ([ctypes.c_int, ctypes.POINTER(AutoPointInfo)],
|
|
223
|
+
ctypes.c_int),
|
|
224
|
+
"xppe_auto_point_data": ([ctypes.c_int, ctypes.c_int,
|
|
225
|
+
ctypes.POINTER(ctypes.c_double), ctypes.c_int],
|
|
226
|
+
ctypes.c_int),
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
# The engine reads and writes files relative to the working directory, and
|
|
230
|
+
# AUTO uses the HOME environment variable. Both are process-wide, so engine
|
|
231
|
+
# calls are serialised.
|
|
232
|
+
_process_lock = threading.RLock()
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
def _text(b: bytes | None) -> str:
|
|
236
|
+
return b.decode("latin-1") if b else ""
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
class Engine:
|
|
240
|
+
"""One private copy of the engine library, holding at most one model."""
|
|
241
|
+
|
|
242
|
+
def __init__(self, library: str | os.PathLike | None = None):
|
|
243
|
+
source = Path(library) if library else find_engine_library()
|
|
244
|
+
self._tmpdir = Path(tempfile.mkdtemp(prefix="xpplab-"))
|
|
245
|
+
self._path = self._tmpdir / source.name
|
|
246
|
+
shutil.copy2(source, self._path)
|
|
247
|
+
self.workdir = self._tmpdir / "work"
|
|
248
|
+
self.workdir.mkdir()
|
|
249
|
+
self._lib = ctypes.CDLL(str(self._path))
|
|
250
|
+
for name, (args, res) in _SIGNATURES.items():
|
|
251
|
+
fn = getattr(self._lib, name)
|
|
252
|
+
fn.argtypes = args
|
|
253
|
+
fn.restype = res
|
|
254
|
+
if self._lib.xppe_api_version() != API_VERSION:
|
|
255
|
+
raise EngineError("engine library has an incompatible API version")
|
|
256
|
+
self.cwd: Path | None = None
|
|
257
|
+
self._closed = False
|
|
258
|
+
self._batch = 0
|
|
259
|
+
|
|
260
|
+
# -- plumbing ---------------------------------------------------------
|
|
261
|
+
|
|
262
|
+
@contextmanager
|
|
263
|
+
def batch(self):
|
|
264
|
+
"""Hold the engine for many calls in a row (reading a diagram point by
|
|
265
|
+
point): one change of directory instead of two per call."""
|
|
266
|
+
with _process_lock:
|
|
267
|
+
old = os.getcwd()
|
|
268
|
+
if self.cwd is not None:
|
|
269
|
+
os.chdir(self.cwd)
|
|
270
|
+
self._batch += 1
|
|
271
|
+
try:
|
|
272
|
+
yield self
|
|
273
|
+
finally:
|
|
274
|
+
self._batch -= 1
|
|
275
|
+
os.chdir(old)
|
|
276
|
+
|
|
277
|
+
def call(self, name: str, *args, check: bool = True):
|
|
278
|
+
"""Call an xppe_* function inside the model's working directory and
|
|
279
|
+
raise EngineError on failure."""
|
|
280
|
+
if self._closed:
|
|
281
|
+
raise EngineError("this model has been closed")
|
|
282
|
+
fn = getattr(self._lib, name)
|
|
283
|
+
with _process_lock:
|
|
284
|
+
if self._batch:
|
|
285
|
+
rc = fn(*args)
|
|
286
|
+
else:
|
|
287
|
+
old = os.getcwd()
|
|
288
|
+
if self.cwd is not None:
|
|
289
|
+
os.chdir(self.cwd)
|
|
290
|
+
try:
|
|
291
|
+
rc = fn(*args)
|
|
292
|
+
finally:
|
|
293
|
+
os.chdir(old)
|
|
294
|
+
if check and isinstance(rc, int) and rc < 0:
|
|
295
|
+
raise EngineError(_text(self._lib.xppe_error()) or name + " failed",
|
|
296
|
+
rc, self.log)
|
|
297
|
+
return rc
|
|
298
|
+
|
|
299
|
+
@property
|
|
300
|
+
def log(self) -> str:
|
|
301
|
+
return _text(self._lib.xppe_log())
|
|
302
|
+
|
|
303
|
+
@property
|
|
304
|
+
def version(self) -> str:
|
|
305
|
+
return _text(self._lib.xppe_version())
|
|
306
|
+
|
|
307
|
+
def names(self, kind: int) -> list[str]:
|
|
308
|
+
n = self._lib.xppe_count(kind)
|
|
309
|
+
return [_text(self._lib.xppe_name(kind, i)) for i in range(n)]
|
|
310
|
+
|
|
311
|
+
def close(self) -> None:
|
|
312
|
+
"""Unload this copy of the engine and delete its files."""
|
|
313
|
+
if self._closed:
|
|
314
|
+
return
|
|
315
|
+
self._closed = True
|
|
316
|
+
handle = self._lib._handle
|
|
317
|
+
del self._lib
|
|
318
|
+
try:
|
|
319
|
+
if sys.platform == "win32":
|
|
320
|
+
ctypes.windll.kernel32.FreeLibrary(ctypes.c_void_p(handle))
|
|
321
|
+
else:
|
|
322
|
+
ctypes.CDLL(None).dlclose(ctypes.c_void_p(handle))
|
|
323
|
+
except Exception:
|
|
324
|
+
pass
|
|
325
|
+
shutil.rmtree(self._tmpdir, ignore_errors=True)
|
|
326
|
+
|
|
327
|
+
def __del__(self):
|
|
328
|
+
try:
|
|
329
|
+
self.close()
|
|
330
|
+
except Exception:
|
|
331
|
+
pass
|
xpplab/_lib/libxppe.dll
ADDED
|
Binary file
|