codex/biochemical-dialysis-repeated-bath-model.py

biochemical-dialysis-repeated-bath-model.py
"""Original fixed-volume dialysis model; Python 3.14, matplotlib 3.10/numpy 2.3."""

from pathlib import Path

import matplotlib.pyplot as plt
import numpy as np


def main():
    sample_volume, bath_volume = 1.0, 9.0
    fraction = sample_volume / (sample_volume + bath_volume)
    cycles = np.arange(4)
    concentration = fraction**cycles
    fig, (balance, repeated) = plt.subplots(1, 2, figsize=(10.2, 4.8), dpi=120, facecolor="white", gridspec_kw={"width_ratios": [1, 1.3]})
    fig.suptitle("Ideal dialysis: conservation first, fresh baths next", fontsize=16, weight="bold", y=0.96)
    balance.bar([0, 1], [fraction, 1 - fraction], color=["#137caf", "#8cb6cc"], width=0.58)
    balance.set(xticks=[0, 1], xticklabels=["Sample\n1 mL", "First bath\n9 mL"], ylim=(0, 1.12), ylabel=r"Solute amount / initial amount $c_0V_s$", title="After the first equilibration")
    for x, value in enumerate([fraction, 1 - fraction]):
        balance.text(x, value + 0.035, f"{value:.1f}", ha="center", fontsize=13, weight="bold")
    balance.text(0.5, 1.065, "0.1 + 0.9 = 1 (conserved)", ha="center", fontsize=10)
    repeated.set_yscale("log")
    repeated.plot(cycles, concentration, "o-", color="#137caf", ms=8, lw=2, label="Permeant solute")
    repeated.plot(cycles, np.ones_like(cycles), "s--", color="#7852a5", ms=6, lw=1.6, label="Perfectly retained protein")
    repeated.set(xlim=(-0.15, 3.6), ylim=(0.00045, 2.3), xticks=cycles, xlabel="Completed equilibrations with fresh baths, n", ylabel=r"Sample concentration / initial concentration", title=r"$c_n/c_0=(1/(1+9))^n=10^{-n}$")
    repeated.set_yticks([1, 0.1, 0.01, 0.001], labels=["1", "0.1", "0.01", "0.001"])
    for n, value, label in zip(cycles[1:], concentration[1:], ["0.1", "0.01", "0.001"], strict=True):
        repeated.annotate(label, (n, value), xytext=(9, 8), textcoords="offset points", fontsize=12, weight="bold", color="#137caf")
    repeated.grid(axis="y", which="major", alpha=0.22)
    repeated.legend(loc="upper right", bbox_to_anchor=(1, 0.83), fontsize=10, framealpha=1)
    for ax in (balance, repeated):
        ax.spines[["top", "right"]].set_visible(False)
        ax.tick_params(labelsize=10)
    fig.text(0.5, 0.08, "Fixed volumes; solute-free new baths; complete equilibration; no binding or loss.", ha="center", fontsize=11)
    fig.text(0.5, 0.035, "Model endpoints, not a time course. Each species uses its own initial concentration; protein is ideally retained.", ha="center", fontsize=9.5, color="#46535d")
    fig.subplots_adjust(left=0.09, right=0.98, bottom=0.24, top=0.78, wspace=0.38)
    fig.savefig(Path(__file__).with_suffix(".png").name, facecolor="white", transparent=False)
    plt.close(fig)


if __name__ == "__main__":
    main()