codexisosbestic-mixtures-and-species-count.py
isosbestic-mixtures-and-species-count.py
"""Synthetic Beer-Lambert mixtures. Python 3.14/NumPy/Matplotlib.
Writes a white PNG in the current directory; no measured spectra are used.
"""
from pathlib import Path
import numpy as np
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
wavelength = np.linspace(420, 680, 1001)
a = .15 + np.exp(-.5*((wavelength-500)/35)**2)
b = .15 + np.exp(-.5*((wavelength-600)/35)**2)
crossing = .15 + np.exp(-.5*(50/35)**2)
c = .15 + (crossing-.15)*np.exp(-.5*((wavelength-550)/25)**2)
fractions = [0, .25, .5, .75, 1]
colors = plt.cm.viridis(np.linspace(.1, .9, len(fractions)))
fig, axes = plt.subplots(1, 2, figsize=(10, 4.6), dpi=120, sharey=True)
fig.patch.set_facecolor('white')
for f, color in zip(fractions, colors):
axes[0].plot(wavelength, (1-f)*a+f*b, color=color, lw=2, label=f'f = {f:g}')
axes[1].plot(wavelength, (1-f)*a+(f/2)*b+(f/2)*c, color=color, lw=2)
for ax in axes:
ax.set_facecolor('white')
ax.axvline(550, color='#83909A', linestyle=':', lw=1)
ax.scatter([550], [crossing], color='#111111', s=28, zorder=5)
ax.annotate('same value at 550 nm', xy=(550, crossing), xytext=(570, .19),
fontsize=9, ha='center', arrowprops={'arrowstyle':'-', 'color':'#596570'})
ax.set_xlim(420, 680)
ax.set_ylim(.02, 1.22)
ax.set_xlabel('Wavelength (nm)')
ax.grid(alpha=.16)
ax.spines[['top', 'right']].set_visible(False)
axes[0].set_ylabel('Absorbance (chosen normalized scale)')
axes[0].set_title('Two species: fractions 1−f, f', fontsize=11)
axes[1].set_title('Three species: fractions 1−f, f/2, f/2', fontsize=11)
axes[0].legend(frameon=False, fontsize=8, loc='upper right')
fig.suptitle('An isosbestic point does not count the species', fontsize=15, y=.98)
fig.text(.5, .045, 'All three constructed component spectra agree at 550 nm; total concentration and path length stay fixed.', ha='center', fontsize=9)
fig.text(.5, .009, 'Illustrative analytic spectra only — no chemical identities or measured data are assigned.', ha='center', fontsize=9, color='#52606A')
fig.subplots_adjust(top=.82, bottom=.22, left=.08, right=.98, wspace=.16)
fig.savefig(Path.cwd()/'isosbestic-mixtures-and-species-count.png', dpi=120, facecolor='white')
plt.close(fig)