codex/monod-growth-and-dilution.py

monod-growth-and-dilution.py
"""Plot the stipulated empirical Monod law; save basename PNG to the CWD.

Tested with Python 3.14.4, Matplotlib 3.10.7, NumPy 2.3.5.
Run from _media: MPLCONFIGDIR=/tmp/chemostat-mpl python3 ../monod-growth-and-dilution.py
"""

from pathlib import Path

import matplotlib

matplotlib.use("Agg")
import matplotlib.pyplot as plt
import numpy as np


def main():
    mu_max, k_s, dilution, feed = 1.0, 1.0, 0.5, 9.0
    s = np.linspace(0, 12, 601)
    mu = mu_max * s / (k_s + s)
    plt.rcParams.update({"font.size": 11, "axes.spines.top": False,
                         "axes.spines.right": False})
    fig, ax = plt.subplots(figsize=(8.8, 4.8), dpi=100, layout="constrained")
    fig.patch.set_facecolor("white")
    ax.set_facecolor("white")
    ax.plot(s, mu, color="#17679b", lw=2.5, label=r"Growth: $\mu(s)=s/(1+s)$")
    ax.axhline(mu_max, color="#7b7b7b", ls=":", lw=1.5,
               label=r"Limiting rate $\mu_{\max}=1$ h$^{-1}$")
    ax.axhline(dilution, color="#c56a16", lw=2, ls="--",
               label=r"Dilution $D=0.5$ h$^{-1}$")
    ax.vlines(k_s, 0, dilution, color="#c56a16", ls=":", lw=1.5)
    ax.scatter([k_s, feed], [dilution, mu_max * feed / (k_s + feed)],
               color=["#c56a16", "#17679b"], s=45, zorder=5)
    ax.annotate(r"$s_*=K_s=1$ g/L", (k_s, dilution), xytext=(2.0, 0.30),
                arrowprops={"arrowstyle": "->", "color": "#c56a16"})
    ax.annotate("Feed: 9 g/L permits\n" + r"$\mu(s_{\rm in})=0.9$ h$^{-1}$",
                (feed, 0.9), xytext=(6.0, 0.67),
                arrowprops={"arrowstyle": "->", "color": "#17679b"})
    ax.set(xlim=(0, 12), ylim=(0, 1.12),
           xlabel=r"Residual nutrient concentration $s$ (g/L)",
           ylabel=r"Growth or dilution rate (h$^{-1}$)",
           title="Monod growth: the pump selects a residual nutrient level")
    ax.legend(loc="lower right", frameon=True, facecolor="white", framealpha=1)
    ax.grid(alpha=0.18)
    destination = Path.cwd() / (Path(__file__).stem + ".png")
    fig.savefig(destination, facecolor="white", transparent=False)
    plt.close(fig)
    print(destination)


if __name__ == "__main__":
    main()