codex/enzyme-substrate-saturation.py

enzyme-substrate-saturation.py
"""Draw normalized saturation examples; write basename.png in cwd.

Uses the repository's Python 3.14, NumPy and Matplotlib dependencies.
All values illustrate a model; no experimental measurements are plotted.
"""

from pathlib import Path

import matplotlib

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


def main():
    x = np.geomspace(0.01, 100, 601)
    samples = np.array([0.1, 1, 10])
    values = samples / (1 + samples)
    blue, orange = "#176b92", "#b65f1b"
    fig, (ax, bars) = plt.subplots(1, 2, figsize=(8.6, 4.5),
                                  gridspec_kw={"width_ratios": [1.5, 1]},
                                  layout="constrained")
    fig.set_facecolor("white")
    ax.plot(x, x / (1 + x), color=blue, lw=2.7)
    ax.axhline(1, color="#69747d", ls="--", lw=1.2)
    for point, value, label, offset in zip(
        samples, values, ["1/11 ≈ 0.091", "1/2 = 0.500", "10/11 ≈ 0.909"],
        [(9, 9), (9, -21), (-12, -27)],
    ):
        ax.vlines(point, 0, value, color="#b9c6ce", ls=":")
        ax.scatter(point, value, color=orange, s=38, zorder=3)
        ax.annotate(label, (point, value), xytext=offset,
                    textcoords="offset points", fontsize=9,
                    ha="right" if point == 10 else "left")
    ax.set(xscale="log", xlim=(0.01, 100), ylim=(0, 1.12),
           xticks=[0.01, 0.1, 1, 10, 100],
           xticklabels=["0.01", "0.1", "1", "10", "100"],
           xlabel=r"Substrate concentration $s/K_M$ (log scale)",
           ylabel=r"Initial rate $v/V_{\max}$")
    ax.text(0.012, 1.035, r"Limiting rate $V_{\max}$", fontsize=10, color="#4b5964")
    ax.set_title("More substrate gives diminishing returns", fontsize=11)
    bars.bar(np.arange(3), values * 110, color=blue, width=0.62)
    for i, value in enumerate(values * 110):
        bars.text(i, value + 3, f"{value:.0f}", ha="center", fontsize=11)
    bars.axhline(110, color="#69747d", ls="--", lw=1.2)
    bars.set(xticks=np.arange(3), xticklabels=["0.1", "1", "10"],
             ylim=(0, 125), xlabel=r"Substrate concentration $s/K_M$",
             ylabel="Initial rate (μM/min)")
    bars.set_title(r"Chosen $V_{\max}$ = 110 μM/min", fontsize=11)
    for axes in [ax, bars]:
        axes.set_facecolor("white")
        axes.spines[["top", "right"]].set_visible(False)
    fig.savefig(Path(Path(__file__).stem + ".png"), dpi=150,
                facecolor="white", transparent=False)
    plt.close(fig)
    print("s/K_M:", samples.tolist())
    print("v/Vmax:", values.tolist(), "; chosen rates:", (110 * values).tolist())
    print("Doubling s from 10 K_M to 20 K_M: relative increase =",
          (20 / 21 / (10 / 11) - 1) * 100, "%")


if __name__ == "__main__":
    main()