跳转至

引言

前几节课程系统地探索了一维动力系统的世界。从第2讲中源于物理学的金茨堡-朗道理论和生物学的演化博弈论开始,揭示了复杂现象可被简化为低维非线性动力学问题。随后的第3讲第4讲深入分析了这些一维系统的不动点、稳定性(线性稳定性分析),以及系统在控制参数变化时如何通过基本的分岔(如鞍点分岔、叉形分岔、尖点分岔与跨临界分岔)发生质变。这些一维模型为理解状态转换提供了坚实的基础。

然而,自然界中的自组织现象,尤其是在生物学中,其复杂性远超一维模型所能描述的范畴。这些系统通常涉及多种相互作用的组分,并且其动态行为受到基本物理定律的严格约束。这节课将从一维系统扩展到二维系统,并引入一个至关重要的物理约束——质量守恒定律。这个约束条件不仅使模型更贴近生物现实,也从根本上改变了系统的动力学特性和分析方法。

这节课将以细胞极性的建立这一核心生物学问题为切入点。细胞极性是指细胞内部物质分布不均,从而形成特定功能轴向的过程(如酵母出芽)。这背后是蛋白质在细胞质(三维体)和细胞膜(二维表面)之间的动态穿梭与相互作用。将构建一个描述此过程的二维动力学模型,并首先分析其在“充分混合”(即忽略空间分布)假设下的稳态行为。通过对该系统的深入分析,将掌握二维系统中的零斜线分析、分岔图构建,以及如何利用雅可比矩阵(Jacobian Matrix) 进行严格的线性稳定性分析。这不仅是前四节课一维稳定性分析的直接推广,也将揭示质量守恒约束(\(m+c=n\))如何从根本上(例如通过引入一个零特征值)简化系统动力学。

课堂板书截图

对本节质量守恒系统的理解,是为后续课程中引入空间维度、探讨图灵斑图(Turing Patterns) 等时空自组织模式奠定理论基础。同时,这节课对守恒系统的分析,也将为下一讲 中研究不满足质量守恒的开放系统(如生物钟)及其产生的振荡(Oscillations) 行为(如霍普夫分岔,Hopf Bifurcation)形成鲜明对比。

1. 概念模型:从细胞极性到质量守恒系统

第4讲中,通过分析“接触过程”(一个流行病模型),探讨了跨临界分岔。这节课将展示这个看似抽象的感染模型,如何对应于一个核心的生物物理问题:细胞极性的建立。这个类比将自然地引导出一个二维动力系统,并引入一个至关重要的物理约束——质量守恒

1.1 生物学背景:从“感染”到“蛋白质招募”

首先,构建一个概念模型。本讲的出发点是一个具体的生物学问题:细胞极性(Cell Polarity)。一个典型的例子是酵母菌(Yeast)的出芽生殖(Budding)。细胞如何“知道”在哪个特定位置形成“芽”?这需要细胞打破自身的对称性,将特定蛋白质(如Cdc42)高度集中到膜的某个点上,这个点随后会组织肌动蛋白(Actin cables),推动细胞膜向外生长,形成新的子代细胞。

课堂PPT截图

然而,实现这一功能的真实生化网络极其复杂。它涉及多种蛋白质(如Cdc42, Bem1, GEFs, GAPs等)在激活态和失活态之间的循环,以及它们在细胞质和细胞膜之间的穿梭。

课堂PPT截图

面对如此复杂的网络,理论物理学家的任务不是复现每一个细节,而是建立一个概念模型(Conceptual Model),抓住主导系统行为的核心原理。因此,我们将这个复杂系统简化为只含一个“主角”:一种可以在两种状态间转换的蛋白质。

课堂PPT截图

这两种状态即是:

\(c(t)\)在细胞质(Cytosol)中自由扩散的蛋白质浓度。

\(m(t)\)附着在细胞膜(Membrane)上的蛋白质浓度(或密度)。

这两个量构成了二维动力系统的状态变量。它们之间的转换过程,与第4讲的感染模型形成了对映:

1.脱离(Detachment): 膜上的蛋白质 \(m\) 会自发地脱落,返回细胞质 \(c\)。这在数学上等同于感染者的“康复”(\(M \xrightarrow{\delta} C\))。

2.附着(Attachment): 细胞质中的蛋白质 \(c\) 会附着到膜上。

3.招募(Recruitment): 关键在于,这种附着过程受到正反馈调控。已经“定居”在膜上的蛋白质 \(m\) 会“招募”更多的 \(c\) 到它身边。这个“招募”过程(\(m + c \rightarrow m + m\))在动力学上与病毒的“感染”过程(\(M + C \xrightarrow{\lambda} M + M\))完全一致。

课堂板书截图

这种由正反馈驱动的蛋白质聚集,是细胞建立极性的物理基础。

1.2 质量守恒定律:一个关键的物理约束

在上述模型中,蛋白质只是在细胞质和细胞膜这两个“隔间”之间来回穿梭,它既不会被凭空创造,也不会被销毁。这意味着,在任何时刻,系统中该蛋白质的总量 \(n\) 是恒定的。

这个基本物理约束被称为质量守恒定律(Mass Conservation Law)。在数学上,它表现为一个代数约束:

\[ m(t) + c(t) = n \]

这个守恒律具有两个层面的深刻意义:

1.动力学约束:它将一个二维系统(\(m\)\(c\))的自由度降为一维。一旦知道了 \(m\)\(n\)\(c\) 的值就立刻被确定为 \(c = n - m\)

2.控制参数:在生物学上,细胞可以通过调控基因表达(即蛋白质的生产速率)来改变总蛋白量 \(n\)。因此,\(n\) 不仅仅是一个常数,它是一个关键的控制参数。通过改变 \(n\),细胞可以驱使系统跨越分岔点,实现(例如)从均匀分布到极化状态的“开关”切换。这正是连接动力系统理论与生物功能的关键。

1.3 反应-扩散(Reaction-Diffusion)框架

课堂PPT截图

为了更全面地描述这个系统,需要考虑两种物理过程:局部的生化反应(附着/脱离)和空间上的扩散。1952年,艾伦·图灵(Alan Turing)在他的开创性论文中提出了描述这类系统的通用框架,即反应-扩散方程

对于一个双组分系统,其最一般的形式为:

\[ \begin{aligned} \partial_t m &= f(m,c) + D_m \nabla^2 m \\ \partial_t c &= g(m,c) + D_c \nabla^2 c \end{aligned} \]

各部分的含义如下:

  • \(\partial_t m\)\(\partial_t c\):膜蛋白和胞质蛋白浓度的时间变化率。

  • \(f(m,c)\)\(g(m,c)\)反应项。在图灵的原始模型中(\(f\)\(g\) 相互独立),这通常代表物质的生产和降解。

  • \(D_m \nabla^2 m\)\(D_c \nabla^2 c\)扩散项\(D\) 是扩散系数,\(\nabla^2\)(拉普拉斯算子)描述了物质从高浓度区域向低浓度区域的净流动。

现在,将1.2节的质量守恒约束应用到这个框架上。由于蛋白质只是在 \(m\)\(c\) 两种状态间转换(\(c \leftrightarrow m\)),任何导致 \(m\) 增加一个单位的局部“反应”(附着),必然同时导致 \(c\) 减少一个单位。因此,反应项 \(f\)\(g\) 必须严格满足:

\[ g(m,c) = -f(m,c) \]

这个负号是质量守恒系统的核心特征。它使方程组变为:

\[ \begin{aligned} \partial_t m &= f(m,c) + D_m \nabla^2 m \\ \partial_t c &= -f(m,c) + D_c \nabla^2 c \end{aligned} \]

这与非守恒的图灵系统(\(f, g\) 相互独立)有着本质的不同,并将导致截然不同的动力学行为。

这节课的分析路径: 在引入空间(扩散项)并探究复杂的图灵斑图之前,必须首先理解系统的基本反应动力学。因此,这节课将暂时忽略空间维度,即假设系统是“充分混合”(well-mixed)的。这相当于设置扩散系数为零(\(D_m = D_c = 0\)),从而将复杂的偏微分方程组(PDEs)简化为常微分方程组(ODEs)。对这个零维系统的分析(不动点、稳定性、分岔),是理解后续时空模式形成的必要前提。

2. 反应动力学与模型的无量纲化

在第1节中,已经将复杂的细胞极性问题,简化为一个双组分(\(m\)\(c\))概念模型,其核心是质量守恒定律 \(g(m,c) = -f(m,c)\)。现在,任务是为这个“反应项” \(f(m,c)\) 构建一个具体的、能够反映生物学机制(特别是第1节中提到的“招募”/正反馈)的数学形式。

2.1 构建反应项 \(f(m,c)\)

反应项 \(f(m,c)\) 代表了膜上蛋白质 \(m\) 的净增长速率。它可以被分解为两个相互竞争的过程:附着(\(c \to m\))和脱离(\(m \to c\))。

\[ f(m, c) = \text{附着速率} - \text{脱离速率} \]

这两个速率可以被建模为:

附着速率 \(a(m)c\) :蛋白质从细胞质附着到膜上的速率,理应正比于细胞质中可用的蛋白质浓度 \(c\)。附着速率系数 \(a(m)\) 本身可以依赖于膜上的蛋白质浓度 \(m\)(即正反馈)。

脱离速率 \(d(m)m\) :蛋白质从膜上脱离回到细胞质的速率,理应正比于膜上已有的蛋白质浓度 \(m\)

因此,动力学方程的一般形式为:

\[ f(m, c) = a(m)c - d(m)m \]

细胞极性形成的关键在于一个正反馈机制:已经附着在膜上的蛋白质 \(m\) 能够促进(“招募”)更多 \(c\) 附着到膜上。为了在模型中体现这一点,为附着速率系数 \(a(m)\) 引入一个非线性形式:

\[ a(m) = k_{\text{on}} + k_{\text{fb}} \frac{m^2}{K_d^2 + m^2} \]

课堂板书截图

这里的各项具有明确的物理意义:

\(k_{\text{on}}\)基础附着速率常数。这代表了即使膜上没有蛋白质(\(m=0\))时,蛋白质也会发生的自发附着过程。

\(k_{\text{fb}} \frac{m^2}{K_d^2 + m^2}\)正反馈(招募)项。这是一个二阶希尔函数(Hill function),描述了一种协同效应:

  • \(m^2\)(二阶):暗示至少需要两个膜蛋白分子协同作用,才能有效地招募新的蛋白质。这导致该项在 \(m\) 很小时增长缓慢(二次方增长)。

  • \(K_d\)(半最大效应浓度):定义了反馈开始“饱和”的浓度尺度。

  • \(k_{\text{fb}}\)(最大反馈速率):当膜蛋白浓度很高时(\(m \gg K_d\)),该项趋于一个最大值 \(k_{\text{fb}}\)。这反映了化学反应的物理极限。

为简化模型,假设脱离速率是恒定的,即 \(d(m) = k_{\text{off}}\)。综合以上各项,得到完整的、具有物理维度的反应动力学方程:

\[ f(m, c) = \left(k_{\text{on}} + k_{\text{fb}} \frac{m^2}{K_d^2 + m^2} \right) c - k_{\text{off}} m \]

课堂PPT截图

2.2 无量纲化(Nondimensionalization)

这个方程包含了四个速率参数(\(k_{\text{on}}, k_{\text{fb}}, K_d, k_{\text{off}}\)),外加一个总浓度 \(n\),使得分析变得复杂。在理论物理中,一个强大的技术是无量纲化,通过重新定义时间、浓度等变量的单位,来减少模型中的独立参数数量,从而揭示系统最本质的控制参数。

采取以下步骤进行无量纲化:

1.重塑时间尺度:将时间单位选为蛋白质的平均脱离时间 \(k_{\text{off}}^{-1}\)。定义新的无量纲时间 \(\tau = t \cdot k_{\text{off}}\)。这相当于将整个动力学方程 \(\partial_t m = f(m,c)\) 两边同除以 \(k_{\text{off}}\)。原方程变为:

\[ \partial_{\tau} m = \left(\frac{k_{\text{on}}}{k_{\text{off}}} + \frac{k_{\text{fb}}}{k_{\text{off}}} \frac{m^2}{K_d^2 + m^2} \right) c - m \]

2.重塑浓度尺度:将浓度单位选为半最大效应浓度 \(K_d\)。定义新的无量纲浓度 \(\tilde{m} = m/K_d\)\(\tilde{c} = c/K_d\)(因此 \(m = K_d \tilde{m}, c = K_d \tilde{c}\))。代入上式:

\[ \partial_{\tau} (K_d \tilde{m}) = \left(\frac{k_{\text{on}}}{k_{\text{off}}} + \frac{k_{\text{fb}}}{k_{\text{off}}} \frac{(K_d \tilde{m})^2}{K_d^2 + (K_d \tilde{m})^2} \right) (K_d \tilde{c}) - (K_d \tilde{m}) \]

\(K_d^2\) 从分母中提出并约掉公因子 \(K_d\)

\[ \partial_{\tau} \tilde{m} = \left(\frac{k_{\text{on}}}{k_{\text{off}}} + \frac{k_{\text{fb}}}{k_{\text{off}}} \frac{\tilde{m}^2}{1 + \tilde{m}^2} \right) \tilde{c} - \tilde{m} \]

3.定义无量纲参数与简化: 为简洁起见,后续仍用 \(m, c, t\) 表示无量纲化的量。

上述变换产生了两个无量纲参数组合:

\(\kappa = \frac{k_{\text{on}}}{k_{\text{off}}}\):基础附着速率与脱离速率的比值。

\(\kappa_{\text{fb}} = \frac{k_{\text{fb}}}{k_{\text{off}}}\):最大反馈速率与脱离速率的比值。

为了进一步简化,这节课将做一个选择:只研究 \(\kappa_{\text{fb}} = 1\) 的情况。这相当于固定了反馈强度,专门研究基础附着率 \(\kappa\) 的影响。

经过无量纲化和简化后,反应项最终变为一个仅含单个参数 \(\kappa\) 的简洁形式:

\[ f(m, c) = \left(\kappa + \frac{m^2}{1 + m^2}\right) c - m \]

无量纲化不仅是数学上的简化,它揭示了一个深刻的物理事实:这个复杂系统的定性行为(例如,是只有一个稳态还是存在多个稳态)本质上仅由两个控制参数共同决定:

\(\kappa\) (基础附着与脱离的速率之比)。

\(n\) (无量纲化的总蛋白浓度)。

这极大地简化了对系统行为的探索,即下一节的分岔分析。

3. 零维系统分析:不动点与分岔

第2节通过构建反应项并进行无量纲化,最终将复杂的生物模型简化为一组动力学方程。这节课将开始分析这个零维(即“充分混合”)系统的稳态行为。其动力学由以下方程组描述:

\[ \begin{aligned} \frac{d}{dt}m &= f(m, c) \\ \frac{d}{dt}c &= -f(m, c) \end{aligned} \]

同时满足约束条件 \(m + c = n\)

3.1 零斜线分析(Nullcline Analysis)

系统达到平衡状态(即不动点)的条件是所有变量的时间导数都为零。对于本系统,这意味着 \(\frac{d}{dt}m = 0\)\(\frac{d}{dt}c = 0\),这两个条件简化为同一个要求:\(f(m,c) = 0\)

\((m,c)\) 相空间中,所有满足 \(f(m,c) = 0\) 的点的集合被称为零斜线(Nullcline)。这条线代表了系统所有可能的平衡点(即在没有质量守恒约束时,反应动力学本身达到平衡的点)。

根据第2节的推导(但保留 \(\kappa_{fb}\) 作为独立参数),\(f(m,c)=0\) 对应的零斜线方程为:

\[ c^{*}(m) = \frac{m}{\kappa + \kappa_{fb} \frac{m^2}{1 + m^2}} \]

这个函数 \(c^*(m)\) 的非线性特性是理解系统行为的关键。

  • \(m \to 0\) 时,分母 \(\approx \kappa\),所以 \(c^* \approx m/\kappa\)(线性增长)。

  • \(m \to \infty\) 时,\(\frac{m^2}{1 + m^2} \to 1\),分母 \(\approx \kappa + \kappa_{fb}\),所以 \(c^* \approx m/(\kappa + \kappa_{fb})\)(也是线性增长,但斜率更小)。

  • 关键在于:参数 \(\kappa_{fb}\) 控制着正反馈的强度。只有当正反馈足够强(即 \(\kappa_{fb}\) 足够大,数学上 \(\kappa_{fb} > 8\kappa\))时,中间区域的协同效应才会导致曲线隆起,形成一个“驼峰”,从而使整条曲线呈现出特征性的“S”形(或教授所说的“N”形)。

3.2 图形法寻找不动点与分岔

零斜线 \(c = c^*(m)\) 提供了系统所有可能的平衡点。然而,在一个具有质量守恒的特定系统中,并非所有这些点都可以被访问。系统还必须满足在第1.2节中引入的质量守恒约束

\[ c = n - m \]

因此,系统在给定总浓度 \(n\) 下的真实不动点,必须同时满足这两个条件。

\((m,c)\) 相空间中,这在几何上被解释为两条曲线的交点

1.S形零斜线\(c = c^{*}(m)\)(反应的平衡)

2.守恒直线\(c = n - m\)(质量的约束)

课堂PPT截图

这种图形法提供了一种极其直观的方式来理解系统的行为。守恒直线 \(c = n - m\) 具有固定的斜率 -1,其在 \(c\) 轴上的截距恰好是控制参数 \(n\)。因此,改变总蛋白浓度 \(n\)(例如,通过改变基因表达),在相空间中就等同于将这条斜率为 -1 的直线平行上下平移

通过观察交点的变化,可以清晰地看到分岔的发生:

  • \(n\) 很小(如 \(n_1\))或很大(如 \(n_3\))时,直线与S形曲线只有一个交点。这意味着系统只有一个唯一的稳态,是单稳态(monostable)的。

  • \(n\) 处于某个中间范围(如 \(n_2\))时,直线与S形曲线有三个交点。这意味着系统存在三个可能的稳态,是双稳态(bistable)的(第4节将证明,中间的点是不稳定的)。

  • 随着 \(n\) 的连续变化,不动点的数量从1个变为3个,然后再变回1个。这种不动点数量和性质的质变就是分岔

  • 具体来说,在从单稳态“进入”双稳态区域(以及“离开”时),守恒直线会与零斜线的“膝盖”部分相切。在这一点,两个不动点(一个稳定,一个不稳定)会碰撞并湮灭。这种分岔类型正是第3讲中详细讨论过的鞍点分岔(Saddle-Node Bifurcation)

通过系统地改变 \(n\) 并记录下所有交点对应的稳态浓度值(\(m^*\)\(c^*\)),就可以构建出系统的分岔图(Bifurcation Diagram)。该图以控制参数 \(n\) 为横轴,以稳态浓度(如 \(c^*\))为纵轴,它清晰地展示了经典的S形双稳态曲线,是理解系统如何“开关”的核心工具。

3.3 Python 模拟

下面的 Python 代码实践部分使用 kappa = 0.02 和 k_fb = 1.6 这一组参数复现了上述理论分析。

import numpy as np
import matplotlib.pyplot as plt
plt.style.use('dark_background')
kappa = 0.02
k_fb  = 1.6
def a(m):
    return kappa + k_fb * m**2 / (1.0 + m**2)
def ap(m):
    return k_fb * (2.0*m) / (1.0 + m**2)**2
def f(m, c):
    return a(m) * c - m
def c_nullcline(m):
    return m / a(m)
def g(m, n):
    return a(m) * (n - m) - m
def gm(m, n):
    return ap(m) * (n - m) - a(m) - 1.0
def sn_m_values(m_min=1e-6, m_max=2.5, num=5000):
    m = np.linspace(m_min, m_max, num)
    H = a(m) * (a(m) + 1.0) / ap(m) - m
    idx = np.where(np.sign(H[:-1]) * np.sign(H[1:]) < 0)[0]
    roots = []
    for i in idx:
        lo, hi = m[i], m[i+1]
        f_lo = a(lo) * (a(lo) + 1.0) / ap(lo) - lo
        f_hi = a(hi) * (a(hi) + 1.0) / ap(hi) - hi
        for _ in range(20):
            mid = 0.5 * (lo + hi)
            f_mid = a(mid) * (a(mid) + 1.0) / ap(mid) - mid
            if f_lo * f_mid <= 0:
                hi, f_hi = mid, f_mid
            else:
                lo, f_lo = mid, f_mid
        roots.append(0.5 * (lo + hi))
    return np.array(roots)
sn_ms = sn_m_values()
# Get the two saddle-node m values
sn_m1, sn_m2 = sn_ms[0], sn_ms[1]
sn_ns = sn_ms + (a(sn_ms) + 1.0) / ap(sn_ms)
sn_cs = sn_ns - sn_ms
n_low  = 0.7 * float(np.min(sn_ns))
n_mid  = np.mean(sn_ns) + 0.1
n_high = 1.2 * float(np.max(sn_ns))
n_list = [n_low, n_mid, n_high]
n_labels = ["$n_1$", "$n_2$", "$n_3$"]

def equilibria_on_n(n, m_grid=np.linspace(1e-6, 2.5, 2000)):
    y = g(m_grid, n)
    idx = np.where(np.sign(y[:-1]) * np.sign(y[1:]) <= 0)[0]
    roots = []
    for i in idx:
        lo, hi = m_grid[i], m_grid[i+1]
        f_lo, f_hi = y[i], y[i+1]
        m_star = 0.5 * (lo + hi)
        for _ in range(20):
            mid = 0.5 * (lo + hi)
            f_mid = g(mid, n)
            if f_lo * f_mid <= 0:
                hi, f_hi = mid, f_mid
            else:
                lo, f_lo = mid, f_mid
        m_star = 0.5 * (lo + hi)
        c_star = n - m_star
        stab = gm(m_star, n) < 0.0
        roots.append((m_star, c_star, stab))
    return roots
def plot_flow_arrows(n, eqs, ax, m_max_plot):
    m_stars_sorted = sorted([eq[0] for eq in eqs])
    arrow_props = dict(arrowstyle="->", color="red", lw=1.5, ls='-')
    if len(m_stars_sorted) == 1:
        m_stable = m_stars_sorted[0]
        m_start_1 = m_stable - 0.4
        m_end_1   = m_stable - 0.1
        if m_start_1 > 0.05:
            ax.annotate("", xy=(m_end_1, n - m_end_1), xytext=(m_start_1, n - m_start_1),
                         arrowprops=arrow_props)
        m_start_2 = m_stable + 0.4
        m_end_2   = m_stable + 0.1
        if m_start_2 < m_max_plot:
            ax.annotate("", xy=(m_end_2, n - m_end_2), xytext=(m_start_2, n - m_start_2),
                         arrowprops=arrow_props)
    elif len(m_stars_sorted) == 3:
        m_low_stable, m_unstable, m_high_stable = m_stars_sorted
        m_start_1 = m_unstable - 0.05
        m_end_1   = m_low_stable + 0.05
        ax.annotate("", xy=(m_end_1, n - m_end_1), xytext=(m_start_1, n - m_start_1),
                     arrowprops=arrow_props)
        m_start_2 = m_unstable + 0.05
        m_end_2   = m_high_stable - 0.05
        ax.annotate("", xy=(m_end_2, n - m_end_2), xytext=(m_start_2, n - m_start_2),
                     arrowprops=arrow_props)
        m_start_3 = m_low_stable - 0.1
        if m_start_3 > 0.05:
            ax.annotate("", xy=(m_low_stable-0.02, n - (m_low_stable-0.02)), xytext=(m_start_3, n - m_start_3),
                         arrowprops=arrow_props)
        m_start_4 = m_high_stable + 0.2
        if m_start_4 < m_max_plot:
            ax.annotate("", xy=(m_high_stable+0.02, n - (m_high_stable+0.02)), xytext=(m_start_4, n - m_start_4),
                         arrowprops=arrow_props)
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 5))
ax1.set_facecolor('black')
ax2.set_facecolor('black')
fig.patch.set_facecolor('black')

# Phase space with segmented f=0 nullcline
m_max = 2.0
m = np.linspace(1e-6, m_max, 3000)
c_nc = c_nullcline(m)

# Draw f=0 as solid for m<=sn_m1 and m>=sn_m2, dashed for sn_m1<m<sn_m2
mask_left  = m <= sn_m1
mask_mid   = (m > sn_m1) & (m < sn_m2)
mask_right = m >= sn_m2

ax1.plot(m[mask_left],  c_nc[mask_left],  linewidth=2, color='white', label="$f(m,c)=0$")
ax1.plot(m[mask_mid],   c_nc[mask_mid],   linestyle="--", linewidth=2, color='white')
ax1.plot(m[mask_right], c_nc[mask_right], linewidth=2, color='white')

# Plot n lines and equilibria
for i, n in enumerate(n_list):
    m_line = np.linspace(0.0, min(m_max, n), 600)
    c_line = n - m_line
    ax1.plot(m_line, c_line, linewidth=1.2, color='gray')
    # equilibria markers
    eqs = equilibria_on_n(n, np.linspace(1e-6, m_max, 2000))
    for (m_star, c_star, stable) in eqs:
        if stable:
            ax1.plot(m_star, c_star, marker="o", markersize=8, color='white', zorder=5)
        else:
            ax1.plot(m_star, c_star, marker="o", markersize=9, markerfacecolor="none",
                     markeredgewidth=2.0, color='white', zorder=6)
    # arrows along flow (skip vicinity of roots)
    pts = np.linspace(0.12, 0.88, 6) * (min(m_max, n))
    for mi in pts:
        ci = n - mi
        if abs(g(mi, n)) < 2e-3:  # avoid near equilibria
            continue
        s = 0.06 * min(m_max, n)  # step
        dx, dy = (s, -s) if g(mi, n) > 0 else (-s, s)
        ax1.annotate("", xy=(mi+dx, ci+dy), xytext=(mi, ci),
                     arrowprops=dict(arrowstyle="->", color="red", lw=1.2))

ax1.text(0.1, n_list[0] - 0.1, "$n_1$", fontsize=12, color='gray')
ax1.text(0.1, n_list[1] - 0.1, "$n_2$", fontsize=12, color='gray')
ax1.text(0.1, n_list[2] - 0.1, "$n_3$", fontsize=12, color='gray')
ax1.text(1.2, 2.0, "$f(m,c) = 0$", fontsize=12, color='white')
ax1.text(0.6, 1.6, "reactive flow", fontsize=12, rotation=-40, color='red')
ax1.set_xlim(0, m_max)
ax1.set_ylim(0, float(np.nanmax(c_nc))*1.05)
ax1.set_xlabel("m", fontsize=14, color='white')
ax1.set_ylabel("c", fontsize=14, color='white')
ax1.set_title("(m,c)-phase space (only unstable segment dashed)", fontsize=14, color='white')
ax1.grid(True, linestyle="--", alpha=0.3, color='gray')
ax1.legend(loc="best")
ax1.tick_params(colors='white')

# Bifurcation diagram
m_branch = np.linspace(1e-6, m_max, 2000)
c_branch = c_nullcline(m_branch)
n_branch = m_branch + c_branch
stab_branch = gm(m_branch, n_branch) < 0.0
mask = stab_branch.astype(int)
change_idx = np.where(mask[1:] != mask[:-1])[0] + 1
segments = np.split(np.arange(len(m_branch)), change_idx)

for seg in segments:
    if stab_branch[seg[0]]:
        style = "-"
    else:
        style = (0, (5, 3))

    ax2.plot(n_branch[seg], c_branch[seg], linestyle=style, linewidth=2, color='white')
ylim_top = 2.5
ax2.set_ylim(-0.1, ylim_top) 

for i, n in enumerate(n_list):
    ax2.text(n, -0.05, n_labels[i], ha='center', va='top', fontsize=12, color='white')

    eqs = equilibria_on_n(n, np.linspace(1e-6, m_max, 2000))
    eqs_sorted = sorted(eqs, key=lambda x: x[1])
    if len(eqs_sorted) == 1:
        c_star = eqs_sorted[0][1]
        ax2.annotate("", xy=(n, c_star - 0.1), xytext=(n, -0.1),
                    arrowprops=dict(arrowstyle="->", color="red", lw=1.5))
        ax2.plot(n, c_star, marker="o", markersize=9, color='white')

    elif len(eqs_sorted) == 3:
        c_low_stable = eqs_sorted[0][1]
        c_unstable = eqs_sorted[1][1]
        c_high_stable = eqs_sorted[2][1]

        ax2.annotate("", xy=(n, c_low_stable - 0.1), xytext=(n, -0.1),
                    arrowprops=dict(arrowstyle="->", color="red", lw=1.5))

        ax2.annotate("", xy=(n, c_low_stable + 0.1), xytext=(n, c_unstable - 0.1),
                    arrowprops=dict(arrowstyle="->", color="red", lw=1.5))

        if n != n_list[1]: 
            ax2.annotate("", xy=(n, ylim_top - 0.1), xytext=(n, c_high_stable + 0.1),
                        arrowprops=dict(arrowstyle="->", color="red", lw=1.5))
            ax2.plot(n, c_high_stable, marker="o", markersize=9, color='white')
        ax2.plot(n, c_unstable, marker="o", markersize=9, markerfacecolor="black", markeredgecolor='white', mew=1.5)
        ax2.plot(n, c_low_stable, marker="o", markersize=9, color='white')

if n_list[0] in [n_low, n_mid, n_high]:
    eqs_n1 = equilibria_on_n(n_list[0], np.linspace(1e-6, m_max, 2000))
    eqs_n1_sorted = sorted(eqs_n1, key=lambda x: x[1])
    if len(eqs_n1_sorted) == 1:
        c_star_n1 = eqs_n1_sorted[0][1]
        ax2.annotate("", xy=(n_list[0], c_star_n1 + 0.1), xytext=(n_list[0], ylim_top - 0.1),
                    arrowprops=dict(arrowstyle="->", color="red", lw=1.5))
if n_list[2] in [n_low, n_mid, n_high]:
    eqs_n3 = equilibria_on_n(n_list[2], np.linspace(1e-6, m_max, 2000))
    eqs_n3_sorted = sorted(eqs_n3, key=lambda x: x[1])
    if len(eqs_n3_sorted) == 1:
        c_star_n3 = eqs_n3_sorted[0][1]
        ax2.annotate("", xy=(n_list[2], c_star_n3 + 0.1), xytext=(n_list[2], ylim_top - 0.1),
                    arrowprops=dict(arrowstyle="->", color="red", lw=1.5))
ax2.text(0.2, 1.0, "$c^*(n)$", fontsize=14, rotation=50, color='white')
ax2.set_xlabel("n", fontsize=14, color='white')
ax2.set_ylabel("$c^*$", fontsize=14, color='white')
ax2.set_title("Bifurcation diagram", fontsize=14, color='white')
ax2.grid(True, linestyle="--", alpha=0.3, color='gray')
ax2.tick_params(colors='white')
plt.tight_layout()
plt.savefig("combined_diagram.png", dpi=200, bbox_inches='tight')
plt.show()

运行代码输出

左图((m,c)-phase space):展示了 \((m,c)\) 相空间。白色曲线是N形的零斜线 \(f(m,c)=0\)。代码通过稳定性分析,已将不稳定的平衡区域绘制为虚线。三条灰色斜线是不同总浓度 \(n_1, n_2, n_3\) 下的守恒直线 \(c=n-m\)

不动点(白点):是零斜线和守恒直线的交点。在 \(n_1\)\(n_3\) 处,系统只有一个稳定不动点(实心圆);在 \(n_2\) 处,系统处于双稳态区域,存在两个稳定不动点(实心圆)和一个不稳定不动点(空心圆)。红色的“reactive flow”箭头展示了系统将如何演化到稳定的不动点。

右图(Bifurcation diagram):此图是系统的分岔图,绘制了稳态浓度 \(c^*\) (纵轴)随控制参数 \(n\) (横轴)的变化。这条S形(或Z形)曲线 展示了由两次鞍点分岔(SN) 所界定的双稳态区域。红色箭头再次标示了在 \(n_1, n_2, n_3\) 处,系统从任何初始状态出发的最终归宿。

4. 线性稳定性分析:从一维简化到雅可比矩阵

第3小节的图形法和Python模拟展示了不动点的位置数量(例如,在 \(n_2\) 处存在三个不动点)。然而,这个分析并没有在数学上严格证明哪个不动点是稳定的(吸引子),哪个是不稳定的(排斥子)。

为了回答这个问题,需要进行线性稳定性分析(Linear Stability Analysis, LSA)。这与第3讲中使用的方法相同:在不动点 \(u^*\) 附近施加一个微小的扰动 \(\delta u(t)\),然后考察这个扰动是随时间指数增长(不稳定)还是衰减(稳定)。

对于这个二维系统,存在两种等价但视角不同的分析方法。

4.1 方法一:降维到一维系统

课堂板书截图

由于质量守恒定律 \(c = n - m\) 的存在,可以将二维系统精确地简化为一个只关于变量 \(m\) 的一维系统:

\[ \frac{d}{dt}m = f(m, c) \quad \xrightarrow{\text{代入} c=n-m} \quad \frac{d}{dt}m = f(m, n-m) \]

现在问题就转化为了在第3讲中已经解决的一维系统稳定性分析。考察不动点 \(m^*\) 附近的一个小扰动 \(\delta m = m - m^*\)。扰动的线性化动力学方程为:

\[ \frac{d}{dt}\delta m \approx \left. \frac{d}{dm}f(m, n-m) \right|_{m=m^*} \cdot \delta m \]

课堂PPT截图

使用链式法则计算导数:

\[ \begin{aligned} \frac{d}{dm}f(m, n-m) &= \left. \frac{\partial f}{\partial m} \right|_{(m^*, c^*)} \cdot \frac{dm}{dm} + \left. \frac{\partial f}{\partial c} \right|_{(m^*, c^*)} \cdot \frac{d(n-m)}{dm} \\ &= \left. \frac{\partial f}{\partial m} \right|_{(m^*, c^*)} + \left. \frac{\partial f}{\partial c} \right|_{(m^*, c^*)} \cdot (-1) \\ &= f_m - f_c \end{aligned} \]

其中,\(f_m \equiv \left. \frac{\partial f}{\partial m} \right|_{(m^*, c^*)}\)\(f_c \equiv \left. \frac{\partial f}{\partial c} \right|_{(m^*, c^*)}\)\(f\)\(m\)\(c\) 的偏导数,并在不动点 \((m^*, c^*)\) 处取值。

因此,扰动的动力学方程为:

\[ \frac{d}{dt}\delta m \approx (f_m - f_c) \cdot \delta m \]

这个扰动的指数增长率(即一维系统中的特征值)为 \(\sigma_{\text{loc}}(n) = f_m - f_c\)。稳定性判据非常直观:

  • 如果 \(\sigma_{\text{loc}}(n) < 0\),扰动会随时间指数衰减,不动点是稳定的

  • 如果 \(\sigma_{\text{loc}}(n) > 0\),扰动会随时间指数增长,不动点是不稳定的

这是第3小节Python代码中 \(gm(m, n)\) 函数所计算的量。

几何意义:斜率判据

这个分析结果有一个优美且强大的几何解释。回顾3.1节的零斜线方程 \(c^*(m)\),其斜率 \(s_{\text{nc}}\) 可以通过对 \(f(m, c^*(m)) = 0\) 使用隐函数定理求导得到:

\[ \frac{d}{dm}f(m, c^*(m)) = 0 \implies \frac{\partial f}{\partial m} + \frac{\partial f}{\partial c} \cdot \frac{dc^*}{dm} = 0 \implies f_m + f_c \cdot s_{\text{nc}} = 0 \]

解得零斜线的斜率为 \(s_{\text{nc}} = -\frac{f_m}{f_c}\)

在当前的生物模型中,附着速率 \(a(m)\) 必定为正,因此 \(f_c = \frac{\partial f}{\partial c} = a(m) > 0\)。有了这个条件,就可以将稳定性判据 \(\sigma_{\text{loc}} < 0\)(即 \(f_m - f_c < 0\))改写为:

\[ f_m < f_c \implies \frac{f_m}{f_c} < 1 \implies -\frac{f_m}{f_c} > -1 \]

代入 \(s_{\text{nc}}\),得到最终的几何稳定性判据

\[ s_{\text{nc}} > -1 \]

物理意义:这个结果的几何意义是:一个不动点是稳定的,当且仅当在该点处,零斜线(S形曲线)的斜率 \(s_{\text{nc}}\) 大于 守恒直线(斜率为-1)的斜率

  • 在S形曲线的上、下两个分支,曲线斜率 \(s_{\text{nc}} > -1\)(曲线比-1更“平坦”),因此它们是稳定的。

  • 在S形曲线的中间分支,曲线斜率 \(s_{\text{nc}} < -1\)(曲线比-1更“陡峭”),因此它是不稳定的。

4.2 方法二:完整的二维系统雅可比矩阵分析

第二种方法更为通用,它直接分析原始的二维系统,而无需预先利用守恒律进行降维。这种方法对于下一讲中不满足质量守恒的系统至关重要。

课堂板书截图

课堂板书截图

考虑对不动点 \((m^*, c^*)\) 的一个微小扰动向量 \(\delta \vec{u} = \begin{pmatrix} \delta m \\ \delta c \end{pmatrix}\)。其线性化的动力学由雅可比矩阵(Jacobian Matrix) \(\mathcal{J}\) 决定:

\[ \frac{d}{dt} \delta \vec{u} = \mathcal{J} \cdot \delta \vec{u} \]

其中,雅可比矩阵 \(\mathcal{J}\) 由反应项 \(\begin{pmatrix} f \\ -f \end{pmatrix}\) 的各个偏导数构成:

\[ \mathcal{J} = \begin{pmatrix} \frac{\partial f}{\partial m} & \frac{\partial f}{\partial c} \\ \frac{\partial (-f)}{\partial m} & \frac{\partial (-f)}{\partial c} \end{pmatrix} = \begin{pmatrix} f_m & f_c \\ -f_m & -f_c \end{pmatrix} \]

系统的稳定性由 \(\mathcal{J}\)特征值(eigenvalues) \(\sigma\) 决定。特征值是特征方程 \(\det(\mathcal{J} - \sigma I) = 0\) 的解:

\[ \begin{vmatrix} f_m - \sigma & f_c \\ -f_m & -f_c - \sigma \end{vmatrix} = (f_m - \sigma)(-f_c - \sigma) - (f_c)(-f_m) = 0 \]

展开得:

\[ -f_m f_c - f_m \sigma + f_c \sigma + \sigma^2 + f_m f_c = 0 \]
\[ \sigma^2 + (f_c - f_m) \sigma = 0 \implies \sigma \left( \sigma - (f_m - f_c) \right) = 0 \]

解这个方程,得到两个特征值:

\[ \sigma^{(1)} = 0 \]
\[ \sigma^{(2)} = f_m - f_c \]

这个结果非常深刻,它完美地揭示了质量守恒系统的动力学结构:

特征值 \(\sigma^{(2)} = f_m - f_c\)

这与方法一(1D降维)得到的增长率 \(\sigma_{\text{loc}}\) 完全相同。它对应的特征向量是 \(\vec{e}^{(2)} = \begin{pmatrix} 1 \\ -1 \end{pmatrix}\)。一个沿着 \(\begin{pmatrix} 1 \\ -1 \end{pmatrix}\) 方向的扰动(即 \(\delta m = 1, \delta c = -1\))满足 \(\delta m + \delta c = 0\),这意味着这个扰动保持了总质量守恒。因此,\(\sigma^{(2)}\) 决定了系统在守恒直线 \(m+c=n\)的稳定性。

特征值 \(\sigma^{(1)} = 0\)

这是一个全新的发现,它是所有具有守恒律的系统的普遍特征。它对应的特征向量是 \(\vec{e}^{(1)} = \begin{pmatrix} f_c \\ -f_m \end{pmatrix}\),这正是零斜线在该点的切线方向

物理意义:零特征值意味着系统在 \(\vec{e}^{(1)}\) 方向上是中性稳定(neutrally stable)的。如果一个扰动(例如,外部注入了少量蛋白质)将系统从守恒线 \(m+c=n\) 推到了邻近的 \(m+c=n+\epsilon\) 上,系统既不会主动回到原来的线上,也不会进一步偏离。系统“不在乎”自己在哪条守恒线上,它只会在新的守恒线上寻找新的平衡点。整个零斜线(Nullcline)实际上是一个连续的平衡点家族(Line of Equilibria),而具体的守恒律 \(m+c=n\) 只是从中挑选出了一个或几个特定的点作为系统的不动点。

4.3 两种分析方法的对比

第3小节的Python代码已经通过计算 \(gm(m, n)\)(即 \(\sigma_{\text{loc}} = f_m - f_c\))的符号,并用实线(稳定)和虚线(不稳定)在分岔图上标示了稳定性。因此,这里不再需要重复的代码,而是用一个总结性表格来对比这两种分析方法:

特征 方法一:降维到一维系统 方法二:完整的二维雅可比矩阵
核心概念 利用 \(c = n - m\) 将系统降维 在完整的二维相空间中进行线性化
数学工具 一维泰勒展开(链式法则) \(2 \times 2\) 雅可比矩阵 \(\mathcal{J}\)
得到的特征值 \(\sigma_{\text{loc}} = f_m - f_c\) \(\sigma^{(1)} = 0\) \(\sigma^{(2)} = f_m - f_c\)
物理诠释 揭示了系统沿着守恒直线(即在物理约束内)的稳定性。 揭示了完整的动力学结构:
\(\sigma^{(2)}\): 沿守恒线的稳定性(同左)。
\(\sigma^{(1)}\): 质量守恒导致的沿零斜线的中性稳定性。
几何判据 \(s_{\text{nc}} > -1\) (稳定) (同左)

总结

这节课通过对一个受质量守恒约束的二维动力学系统的详尽分析,实现了从一维系统到更高维、更具现实意义的生物物理模型的关键过渡。课程的核心脉络可以总结如下:

1.以细胞极性为背景,将第4讲中“感染”模型的动力学类比为蛋白质的“招募”过程,从而抽象出一个双组分(\(m, c\))动力学模型。其核心是质量守恒定律 \(m+c=n\)

2.模型中引入的非线性正反馈(即\(m^2\)协同项),被证明是产生S形(或N形)零斜线的根源,这是实现双稳态等复杂行为的必要条件。

3.通过图形法,发现系统的不动点是零斜线 \(f(m,c)=0\)守恒直线 \(c=n-m\) 的交点。改变总蛋白浓度 \(n\) 这一控制参数,在几何上等同于平行移动守恒直线,导致不动点对在切点处产生或湮灭,这正是第3讲中学习过的鞍点分岔(Saddle-Node Bifurcation)

4.线性稳定性分析(LSA)被用于判断不动点的稳定性。这可以通过两种等价的方法完成:一是通过守恒律降维到一维系统,得到增长率 \(\sigma_{\text{loc}} = f_m - f_c\);二是采用更通用的二维雅可比矩阵分析。

5.雅可比矩阵分析 \(\mathcal{J} = \begin{pmatrix} f_m & f_c \\ -f_m & -f_c \end{pmatrix}\) 揭示了质量守恒系统的普遍特征:存在一个零特征值\(\sigma^{(1)} = 0\))。这个特征值对应于沿着零斜线(即平衡点家族)方向的中性稳定性,是守恒律 \(g=-f\) 的直接数学后果。

至此,该系统在空间均匀(充分混合)下的行为已经分析完毕。然而,细胞极性本身是一个空间现象。这节课的分析是理解空间斑图形成的必要铺垫。在后续课程中,当重新引入扩散项后,在某些条件下,空间均匀的稳态会变得不稳定,从而自发地涌现出不均匀的蛋白质分布,形成图灵斑图(Turing Patterns)

这节课的分析也为下一讲(6.动力系统:二维非守恒系统与振荡器) 建立对比。下一讲将探讨一个截然不同的情景:如果系统不满足质量守恒(例如,在蛋白质被持续合成和降解的开放系统中),会发生什么?在这种情况下,约束 \(g=-f\) 被打破,雅可比矩阵变为更一般的形式 \(\mathcal{J} = \begin{pmatrix} f_m & f_c \\ g_m & g_c \end{pmatrix}\)。零特征值不再是必然存在的,取而代之的是,特征值可以成为复数。复数特征值是振荡(Oscillations) 行为的数学标志。当一对共轭复特征值的实部从负变为正时,系统会经历霍普夫分岔(Hopf Bifurcation),稳定的不动点会失稳,并诞生一个稳定的极限环(Limit Cycle),即持续的振荡。这将为理解生物钟、捕食者-被捕食者循环等节律性现象打开大门。