1. Linear Stability

“The simplest dynamical system is also the most fundamental: its qualitative behaviour is governed by a single number.”

The starting point of bifurcation theory is the scalar linear ODE:

\[ \dot x(t) = a\,x(t), \qquad x(0) = x_0, \]

with closed-form solution \(x(t) = x_0\,e^{a\,t}\).

Three regimes follow from the sign of \(a\):

\(a\) Eigenvalue Equilibrium \(x = 0\) Trajectories
\(a < 0\) real, negative stable node exponential decay \(x_0\,e^{-|a|t} \to 0\)
\(a = 0\) zero non-hyperbolic \(x(t) \equiv x_0\)
\(a > 0\) real, positive unstable node exponential growth \(x_0\,e^{+|a|t} \to \infty\)

The line \(a = 0\) is the simplest example of a bifurcation — the qualitative phase portrait changes there.

We define the system inline as a TVBO Dynamics. Only the YAML changes; all plotting calls are TVBO standard library:

from tvbo import Dynamics
import matplotlib.pyplot as plt

LIN_YAML = """
name: LinearScalar
description: |
  Scalar linear ODE $\\dot x = a x$. Stability of the origin is governed by
  the sign of $a$.
parameters:
  a:
    name: a
    value: 1.0
state_variables:
  x:
    name: x
    domain: { lo: -5.0, hi: 5.0 }
    equation:
      lhs: Derivative(x, t)
      rhs: a*x
    initial_value: 1.0
"""

A family of solutions across \(a\)

Five values of \(a\) from strongly damped to strongly amplified, all started at \(x_0 = 1\) and integrated for 4 time units. Axis limits match the legacy slide (ExpExample0Red.png).

import numpy as np
fig, ax = plt.subplots(figsize=(5, 4))
for a, color in zip([-1.0, -0.5, 0.0, 0.5, 1.0], ["C0", "C1", "0.4", "C2", "C3"]):
    dyn = Dynamics.from_string(LIN_YAML)
    dyn.parameters["a"].value = a
    dyn.plot("x", duration=4, dt=1e-3, ax=ax)
    ax.lines[-1].set_color(color)
    ax.lines[-1].set_label(f"$a = {a:+.1f}$")
ax.set_xlabel("Time $t$"); ax.set_ylabel("$x(t)$")
ax.set_xlim(0, 4); ax.set_ylim(-0.2, 4.0)
ax.legend(handlelength=1, loc="upper left")
plt.tight_layout(); plt.show()

The slope of every curve at \(t = 0\) equals \(a\) (compare \(\dot x(0) = a\,x_0\)); its sign decides whether the trajectory grows or decays. This is the eigenvalue criterion in its simplest form.

Reproducing ExpExamplePert.png

Same system, but starting from a tiny perturbation \(\delta = 10^{-6}\) applied at \(t = 1\). The qualitative behaviour is identical to the un-perturbed case: the only information that survives a small perturbation of an unstable trajectory is the sign of \(a\). This motivates the local-flow / linearisation viewpoint explored in the next section.

tv = np.linspace(0, 4, 1000)
n4 = tv.size // 4
delta = 1e-6
x_pos = np.zeros_like(tv); x_pos[n4:] = delta * np.exp(tv[n4:] - tv[n4])
x_neg = np.zeros_like(tv); x_neg[n4:] = delta * np.exp(-(tv[n4:] - tv[n4]))

fig, ax = plt.subplots(figsize=(5, 4))
ax.axhline(0, color="#c9211e", linestyle="--")
ax.plot(tv, x_pos, color="C0", lw=2, label="$a=+1$ (unstable)")
ax.plot(tv, x_neg, color="C1", lw=2, label="$a=-1$ (stable)")
ax.set_xlabel("Time $t$"); ax.set_ylabel("$x(t)$")
ax.set_xlim(0, 4); ax.set_ylim(-0.2 * 1e-5 / 4, 1e-5)
ax.legend(handlelength=1)
plt.tight_layout(); plt.show()

Linking to the next section

For nonlinear systems \(\dot x = f(x)\), linear stability of an equilibrium \(x^*\) is decided by the eigenvalues of the Jacobian \(J = \partial f / \partial x \,\big|_{x = x^*}\). The 1-D linear ODE above is just the special case \(f(x) = a\,x\) with \(J \equiv a\); everything else is a multidimensional generalisation. The next chapter shows the canonical 2-D classification (node / saddle / focus / centre) directly from YAML.