Mini Cases#
This page is the short path between the quickstart and the full API reference. Use it when you already know the kind of system you have and want the smallest working pyna pattern.
Which Entry Point?#
You have |
Start with |
Geometry you usually get |
|---|---|---|
An ODE |
|
|
A Hamiltonian |
|
|
A finite-dimensional map |
|
|
A toroidal magnetic field |
|
|
A stochastic teaching model |
|
sampled |
Case 1: ODE Sample to Closed Cycle#
Trajectory means sampled data. Cycle means you are making the stronger
claim that the sample is closed.
import numpy as np
from pyna.topo import TopologyWorkflow
wf = TopologyWorkflow(closure_tol=2e-2)
flow = wf.system(
"callable-flow",
rhs=lambda x, t: np.array([x[1], -x[0]]),
dim=2,
coordinate_names=("q", "p"),
)
traj = wf.trajectory(flow, [1.0, 0.0], (0.0, 2*np.pi), dt=0.01)
print(wf.closing_error(traj))
cycle = wf.closed_cycle(traj)
print(cycle.period_value, cycle.ambient_dim)
For a production workflow, keep the closing tolerance explicit. It makes numerical assumptions reviewable.
Case 2: Map Iterate to Periodic Orbit#
Maps produce Orbit objects first. Promote to PeriodicOrbit only for
known or numerically verified closed samples.
import numpy as np
from pyna.dynamics import CallableMap
from pyna.topo import TopologyWorkflow
flip = CallableMap(lambda x: np.array([-x[0], -x[1]]), dim=2)
wf = TopologyWorkflow(closure_tol=1e-12)
orbit = wf.orbit(flip, [1.0, 0.0], n_iter=2)
periodic = wf.periodic_orbit(
orbit.states[:-1],
map_obj=flip,
coordinate_names=("x", "y"),
)
print(periodic.period, periodic.points[0].state)
If your map comes from another package, either wrap it with CallableMap or
implement __call__(x) plus a phase_space attribute.
Case 3: Analytic Stellarator O/X Points#
For magnetic-confinement work, a field-line flow is cut by a Poincare section. The executable tutorial RMP Stellarator Resonance Analysis now carries the complete visual calculation:
build the public analytic stellarator model;
validate divergence-free
m=1andm>1RMP templates;trace unperturbed and perturbed Poincare sections;
compare analytic resonant X/O phases with
cynaNewton fixed points;analyze multi-component RMP spectra with contravariant
B^rpcolormesh atlases,q/m/nresonance maps with optional Poincare projections, interactive Plotly 3-D bars, radial fixed-n/fixed-mmaps, resonance curves, and toggleable island-width markers;compute the total nRMP response from all non-resonant spectrum rows;
use contribution tables only as diagnostics for ranking and convergence;
visualise nRMP flux-surface deformation and field-line speed modulation;
overlay local stable branches and a PEST-style coordinate grid.
Use this notebook when testing changes to fixed-point plotting, section geometry, RMP/nRMP diagnostics, or tutorial rendering. It is small enough to run locally before publishing docs, while still exercising the public helper APIs used by downstream analysis scripts.
Case 4: Custom System Registration#
Factories are optional. They matter when your downstream project is configuration-driven.
import numpy as np
from pyna.dynamics import CallableFlow
from pyna.topo.factories import DynamicalSystemFactory
def make_damped_oscillator(gamma=0.1):
return CallableFlow(
lambda x, t: np.array([x[1], -x[0] - gamma*x[1]]),
dim=2,
coordinate_names=("q", "p"),
label="damped oscillator",
)
DynamicalSystemFactory.register(
"damped-oscillator",
lambda gamma=0.1: make_damped_oscillator(gamma),
overwrite=True,
)
flow = DynamicalSystemFactory.create("damped-oscillator", gamma=0.05)
Use local Registry instances in tests if global registration would make the
test order-dependent.
Case 5: SDE Distribution Estimate#
Single SDE paths are pyna trajectories. Monte Carlo ensembles are statistical estimators; keep them as arrays until pyna grows a dedicated ensemble object.
import numpy as np
from pyna.dynamics import BrownianMotion, GeometricBrownianMotion
bm = BrownianMotion(dim=1, diffusion=1.0)
path = bm.euler_maruyama([0.0], (0.0, 1.0), dt=0.01, rng=1)
print(path.final)
gbm = GeometricBrownianMotion(mu=[0.08], sigma=[0.20])
rng = np.random.default_rng(20260701)
z = rng.normal(size=100_000)
terminal = 100.0 * np.exp(gbm.expected_log_growth()[0] + gbm.sigma[0] * z)
print(np.mean(terminal), np.quantile(terminal, [0.05, 0.5, 0.95]))
For a full executed case with Brownian, Ornstein-Uhlenbeck and geometric Brownian motion distributions, use SDE Monte Carlo Distributions.
Case 6: Where to Customize#
Goal |
Extend |
Keep in mind |
|---|---|---|
New physics model |
|
return pyna geometry from integration methods |
New map family |
|
expose stable coordinate names |
New section |
|
implement crossing/project semantics clearly |
New data format |
|
normalize data; do not silently claim periodicity |
New assembly policy |
|
centralize validation and metadata |
New backend selection |
factories or workflow facade |
keep raw backend arrays behind pyna objects |
The rule of thumb: use dataclasses for mathematical objects, adapters for input normalization, builders for validation, and factories only when users need stable string keys.
Notebook Checklist#
Before publishing documentation:
.venv/bin/python -m pytest --nbmake \
notebooks/tutorials/RMP_resonance_analysis.ipynb \
notebooks/tutorials/island_jacobian_analysis.ipynb
For heavy notebooks with saved outputs, run them locally and commit the updated
.ipynb file:
.venv/bin/jupyter nbconvert --to notebook --execute --inplace \
notebooks/tutorials/sde_monte_carlo_distribution.ipynb
For the same notebook set used by GitHub Pages, run the Sphinx build locally:
rm -rf docs/notebooks docs/_build
cp -r notebooks docs/notebooks
make -C docs html SPHINXBUILD=../.venv/bin/sphinx-build