Turing Instability
Pattern Formation in Reaction-Diffusion
Turing instability explains when a uniform steady state becomes unstable to spatial perturbations, leading to patterns. We will implement the checks and visualize the instability region.
Conditions
Let the Jacobian at the fixed point be
\[J=\begin{pmatrix} f_u & f_v \\ g_u & g_v \end{pmatrix}. \tag{1}\]
A common set of conditions for Turing instability is:
\[ \begin{aligned} &f_u + g_v < 0, \\ &f_u g_v - f_v g_u > 0, \\ &g_v + d f_u > 2\sqrt{d\,(f_u g_v - f_v g_u)}. \end{aligned} \tag{2}\]
Implement a Turing test
Create a function that returns True if all conditions are satisfied. In the reference script the Jacobian entries are hard-coded at the fixed point.
def giere_meinhardt_jacobian(a=0.40, b=1.00):
# TODO: compute fu, fv, gu, gv at the fixed point
return fu, fv, gu, gv
def is_turing_instability(a, b, d):
fu, fv, gu, gv = giere_meinhardt_jacobian(a, b)
# TODO: implement the three conditions
return cond1 & cond2 & cond3def giere_meinhardt_jacobian(a=0.40, b=1.00):
fu = 2 * b / (a + 1) - b
fv = -((b / (a + 1)) ** 2)
gu = 2 * (a + 1) / b
gv = -1.0
return fu, fv, gu, gv
def is_turing_instability(a, b, d):
fu, fv, gu, gv = giere_meinhardt_jacobian(a, b)
nabla = fu * gv - fv * gu
cond1 = (fu + gv) < 0
cond2 = nabla > 0
cond3 = (gv + d * fu) > (2 * np.sqrt(d) * np.sqrt(nabla))
return cond1 & cond2 & cond3Plot the Turing space
Plot the region of \((a, d)\) where the instability holds.
import numpy as np
import matplotlib.pyplot as plt
arr_a = np.linspace(0, 1, 1000)
arr_d = np.linspace(0, 100, 1000)
mesh_a, mesh_d = np.meshgrid(arr_a, arr_d)
mask_turing = is_turing_instability(mesh_a, b, mesh_d)
fig, ax = plt.subplots(figsize=(6, 4))
ax.contourf(mesh_a, mesh_d, mask_turing)
ax.set_xlabel("a")
ax.set_ylabel("d")
ax.set_title("Turing Space")Ensure your function works with arrays.
Unstable spatial modes
The temporal eigenvalues for spatial mode \(\lambda_n\) are the eigenvalues of
\[A_n = J - \lambda_n \;\mathrm{diag}(1, d). \tag{3}\]
You can scan modes to find which ones are unstable:
import numpy as np
def find_unstable_spatial_modes(a=0.40, b=1.00, d=30.0, length=40.0, num_modes=10):
# TODO: build Jacobian and scan spatial modes
return unstable_modesdef find_unstable_spatial_modes(a=0.40, b=1.00, d=30.0, length=40.0, num_modes=10):
fu, fv, gu, gv = giere_meinhardt_jacobian(a, b)
jac = np.array([[fu, fv], [gu, gv]])
n_values = np.arange(num_modes)
max_eigs = np.zeros(num_modes)
for n in n_values:
lambda_n = (n * np.pi / length) ** 2
a_n = jac - lambda_n * np.diag([1, d])
sigma1, sigma2 = np.linalg.eigvals(a_n)
max_eigs[n] = max(sigma1.real, sigma2.real)
sorted_indices = np.argsort(max_eigs)[::-1]
unstable_modes = sorted_indices[max_eigs[sorted_indices] > 0]
return unstable_modesInteraction extension (optional)
Make the plot interactive: clicking on the Turing diagram updates \((a, d)\) and restarts the animation of \(v(x)\).
from matplotlib.backend_bases import MouseEvent
def mouse_click(event: MouseEvent):
if event.inaxes == ax_turing:
a = event.xdata
d = event.ydata
# TODO: reset u,v and restart the animation
fig.canvas.mpl_connect("button_press_event", mouse_click)You can find a complete interactive implementation in amlab/pdes_1d.