codex/fetal-hemoglobin-affinity-model.py

fetal-hemoglobin-affinity-model.py
"""Illustrative Hill curves, not measured fetal/adult hemoglobin parameters.
Python 3.14; NumPy 2.3.5; Matplotlib 3.10.7.
"""
from pathlib import Path
import numpy as np
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt

def occupancy(pressure, midpoint, exponent=2):
    p = np.asarray(pressure)
    return p**exponent / (midpoint**exponent + p**exponent)

def main():
    p = np.linspace(0, 80, 801)
    fig, ax = plt.subplots(figsize=(8, 4.2), dpi=100, facecolor='white')
    ax.set_facecolor('white')
    for midpoint, color, label in [(20, '#245d9c', 'Higher-affinity model'), (30, '#c86429', 'Lower-affinity model')]:
        ax.plot(p, occupancy(p, midpoint), lw=2.2, color=color,
                label=f'{label}: midpoint {midpoint}')
        ax.scatter([25], [occupancy(25, midpoint)], color=color, s=34, zorder=4)
    ax.axvline(25, color='#666666', ls=':', lw=1)
    ax.set(xlim=(0, 80), ylim=(0, 1), xlabel='Oxygen partial pressure (arbitrary units)',
           ylabel='Fraction of sites occupied', title='Higher affinity changes occupancy at the same oxygen pressure')
    ax.legend(loc='lower right', fontsize=9)
    ax.grid(alpha=0.15)
    fig.text(0.5, 0.015, 'Original Hill models, exponent 2. Midpoints are stipulated; no physiological reference values or fitted data.',
             ha='center', fontsize=8)
    fig.tight_layout(rect=(0, 0.045, 1, 1))
    fig.savefig(Path('fetal-hemoglobin-affinity-model.png'), facecolor='white')
    plt.close(fig)

if __name__ == '__main__':
    main()