"""Generate representative phase portraits in cwd; Python 3.14, NumPy, Matplotlib.""" import os os.environ.setdefault('MPLCONFIGDIR', '/tmp/oxford-2023-fmb-matplotlib') import numpy as np import matplotlib matplotlib.use('Agg') import matplotlib.pyplot as plt from matplotlib.lines import Line2D alpha = 0.2 cases = [(0.5, .05, -.1, 'One stable node'), (.4, .5, .112, 'One stable focus'), (.4, .15, .112, 'One unstable focus; sustained oscillation'), (.2, .2, .032, 'Two sinks and a saddle'), (.2, .04, .035, 'One source, one saddle, one sink'), (.25, .1, .052, 'Two sources and a saddle')] fig, axes = plt.subplots(2, 3, figsize=(12, 7), layout='constrained') vv = np.linspace(-.45, 1.25, 61) ww = np.linspace(-.18, .56, 57) V, W = np.meshgrid(vv, ww) curve_v = np.linspace(-.45, 1.25, 700) def f(v): return v*(alpha-v)*(v-1) for ix, (ax, (k, gamma, current, title)) in enumerate(zip(axes.flat, cases)): beta = k*gamma U, Z = f(V)-W+current, beta*V-gamma*W ax.streamplot(vv, ww, U, Z, color='0.81', density=.8, linewidth=.6, arrowsize=.6, integration_direction='both') ax.plot(curve_v, f(curve_v)+current, color='#2463a4', lw=1.4) ax.plot(curve_v, k*curve_v, color='#cb6a23', lw=1.4) roots = np.roots([1, -1-alpha, alpha+k, -current]) roots = np.sort(roots.real[np.abs(roots.imag)<1e-9]) for root in roots: p = -3*root**2+2*(1+alpha)*root-alpha det = gamma*(k-p) if det < 0: ax.scatter(root, k*root, marker='x', s=50, color='black', zorder=6) else: ax.scatter(root, k*root, marker='o', s=40, edgecolor='black', facecolor='black' if p