Bifurcation & Stability Analysis
This section teaches bifurcation and stability analysis in TVBO — locating fixed points, testing their linear stability, drawing phase portraits, and continuing the canonical bifurcations (saddle-node, pitchfork, Hopf) as a parameter varies. Each page is a short, copy-pasteable recipe.
It is built as a self-contained, declarative reproduction of the canonical normal-form bifurcation tutorial, written entirely with TVBO primitives.
The original tutorial is built on hand-written ContinuationSystem Python classes that wrap AUTO-07p Fortran subroutines (one model.f90 per system). Here we replace every Fortran/Python pair with a single inline YAML string that defines the dynamical system declaratively. TVBO then routes the metadata to its bifurcation backends (BifurcationKit.jl or PyRates/PyCoBi) through the unified Continuation API.
Goals
- Zero hidden state. No
from_db(), nofrom_ontology(), no Fortran files, no scratch directories. Every model and every continuation is a self-describing YAML string in the page. - One backend-agnostic specification. The same YAML drives BifurcationKit.jl and PyRates (AUTO-07p) – picked at
exp.run(...)time. - Reproduce every figure from the legacy tutorial using TVBO plotting only (
plot_dynamicsfor phase portraits andBifurcationResult.plot()/.plot_3d()for diagrams).
Plan
| § | Page | Legacy script | System | Figure(s) reproduced |
|---|---|---|---|---|
| 1 | Linear Stability | ExpExample.py |
\(\dot x = a\,x\) | ExpExample0Red.png, ExpExamplePert.png |
| 2 | 2D Phase Portraits | PhasePortrait2DLinearSystem.py |
\(\dot{\vec x} = A\vec x\) — node, saddle, focus, centre | PhasePortraitSaddle.png |
| 3 | Saddle-Node | SaddleNode.py |
\(\dot x = a - x^2\) | SaddleNode.png |
| 4 | Pitchfork | PitchFork.py |
\(\dot x = a x - x^3\) | PitchFork.png |
| 5 | Hysteresis | DoubleSaddleNode.py |
\(\dot x = a + x - x^3\) | Hysteresis.png |
| 6 | Hopf normal form | PhasePortraitHopf.py |
\(\dot x_1 = (a-r^2)x_1 - w x_2\), \(\dot x_2 = (a-r^2)x_2 + w x_1\) | HopfPhasePortrait_and_BifDiag3D_a=*.png |
| 7 | Numerical Continuation | — | theory: Newton, pseudo-arclength, branch switching | — |
Methodology
Defining the system inline
Every system in the tutorial is a small ODE expressed as a Dynamics YAML string and loaded with Dynamics.from_string(...):
name: SaddleNode
parameters:
a: { name: a, value: 0.5 }
state_variables:
x:
name: x
domain: { lo: -2.0, hi: 2.0 }
equation:
lhs: Derivative(x, t)
rhs: a - x**2
initial_value: 0.5Defining the continuation inline
Continuations are similarly inline. A 1-parameter sweep over a:
name: eq_in_a
dynamics: SaddleNode
free_parameters:
- name: a
domain: { lo: -2.0, hi: 2.0 }For Hopf systems we additionally request continuation of periodic orbits from each detected Hopf point via a nested branch (source_point: "hopf:all").
Running
The Continuation is attached to a SimulationExperiment and dispatched to a backend:
exp = SimulationExperiment(dynamics=dyn, continuations=[cont])
result = exp.run("bifurcationkit.jl") # or "pyrates-bifurcation"
result.continuations["eq_in_a"].plot(VOI="x")Phase portraits
For pure phase-portrait figures we use Dynamics.plot with kind="vectorfield" (stream-plot only) or kind="phaseplane" (stream-plot plus nullclines and detected fixed points):
dyn.plot("x1", "x2", kind="phaseplane", n_trajectories=3)Both kinds use the state-variable domains declared in the YAML to set the axis limits.
Animations across a parameter
Dynamics.animate(parameter, values, *dims, kind=...) produces a matplotlib.animation.FuncAnimation by re-running plot() for each value of the chosen parameter:
anim = dyn.animate("a", np.linspace(-2, 1.5, 60),
"x1", "x2", kind="phaseplane")
HTML(anim.to_jshtml())3D Hopf diagram
The 3D figure (equilibrium spine + limit-cycle tube + Hopf point) is produced with BifurcationResult.plot_3d(VOI="x1") once periodic orbits have been continued from the Hopf point.
What this tutorial does NOT need
- No
model.f90, noAUTO_DIR, no scratch BifurcationData folders. - No
ContinuationSystem, noContinuationData, noContinuationPlot. - No global mutation of plot kwargs.
- No
from_db/from_ontologylookups – every page is fully portable.