codex/oil-drop-charge-jump-series.py

oil-drop-charge-jump-series.py
"""Original simulated charge-state readings; Python 3.14, NumPy 2.3.5, Matplotlib 3.10.7.

Run from _media; emits oil-drop-charge-jump-series.png in the working directory.
No historical measurements or SI elementary-charge estimate are used.
"""
from pathlib import Path

import matplotlib.pyplot as plt
import numpy as np


def main():
    means = np.array([2.03, 2.96, 5.02, 3.99])
    offsets = np.array([-0.18, 0.10, -0.04, 0.22, -0.12, 0.02])
    assert np.isclose(offsets.sum(), 0)
    sigma = 0.08  # Stipulated uncertainty of each state mean, not an estimate.
    plt.rcParams.update({"font.size": 11, "axes.spines.top": False,
                         "axes.spines.right": False, "figure.facecolor": "white"})
    fig, (ax, jumps) = plt.subplots(2, 1, figsize=(9, 6.4),
                                   gridspec_kw={"height_ratios": [2.4, 1.2]})
    colors = ["#1864ab", "#087f5b", "#9c36b5", "#d9480f"]
    for j, (mean, color) in enumerate(zip(means, colors)):
        x = np.arange(1, 7) + 7 * j
        readings = mean + np.roll(offsets, j)
        assert np.isclose(readings.mean(), mean)
        ax.plot(x, readings, "o-", color=color, ms=4, lw=0.7)
        ax.hlines(mean, x[0] - 0.3, x[-1] + 0.3, color=color, lw=1.8)
        ax.fill_between([x[0] - 0.3, x[-1] + 0.3], mean - sigma,
                        mean + sigma, color=color, alpha=0.12)
        ax.text(x.mean(), mean + 0.38, f"State {j + 1}: mean {mean:.2f}",
                ha="center", color=color, fontsize=10)
    ax.set(xlim=(0, 29), ylim=(1.5, 5.8), ylabel=r"Inferred $|q|/Q_*$",
           xlabel="Acquisition order (not elapsed time)")
    ax.grid(axis="y", alpha=0.18)
    ax.text(0.02, 0.96, "Chosen noisy readings; shaded mean ± 0.08",
            transform=ax.transAxes, va="top", fontsize=10)
    delta = np.diff(means)
    jumps.axhline(0, color="0.6", lw=0.8)
    jumps.errorbar([1, 2, 3], delta, yerr=np.sqrt(2) * sigma, fmt="o",
                   color="#343a40", capsize=5)
    for j, d in enumerate(delta):
        jumps.text(j + 1, d + 0.25, f"{d:+.2f}", ha="center")
    jumps.set(xticks=[1, 2, 3], xticklabels=["1 → 2", "2 → 3", "3 → 4"],
              xlim=(0.5, 3.5), ylim=(-1.5, 2.75),
              ylabel=r"Change in $|q|/Q_*$", xlabel="Transition between state means")
    jumps.grid(axis="y", alpha=0.18)
    fig.suptitle("Simulated charge states of one oil drop", fontsize=16)
    fig.text(0.5, 0.015, r"Arbitrary reference charge $Q_*$; no historical data or numerical estimate of $e$",
             ha="center", fontsize=10)
    fig.tight_layout(rect=(0, 0.04, 1, 0.95), h_pad=1.5)
    fig.savefig(Path(__file__).stem + ".png", dpi=130, facecolor="white")
    plt.close(fig)


if __name__ == "__main__":
    main()