Bifurcation & Stability Analysis

Find where a model’s behaviour changes qualitatively: fixed points, linear stability, phase portraits, and numerical continuation of bifurcations.

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

  1. Zero hidden state. No from_db(), no from_ontology(), no Fortran files, no scratch directories. Every model and every continuation is a self-describing YAML string in the page.
  2. One backend-agnostic specification. The same YAML drives BifurcationKit.jl and PyRates (AUTO-07p) – picked at exp.run(...) time.
  3. Reproduce every figure from the legacy tutorial using TVBO plotting only (plot_dynamics for phase portraits and BifurcationResult.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.5

Defining 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, no AUTO_DIR, no scratch BifurcationData folders.
  • No ContinuationSystem, no ContinuationData, no ContinuationPlot.
  • No global mutation of plot kwargs.
  • No from_db / from_ontology lookups – every page is fully portable.