codex/optical-trap-restoring-force.py

optical-trap-restoring-force.py
"""Local harmonic well, force and deterministic overdamped relaxation.

Write optical-trap-restoring-force.png to the working directory.
Uses the repository numpy/matplotlib dependencies; tested with Python 3.14.
"""

import numpy as np
import matplotlib

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


def main():
    q = np.linspace(-1.5, 1.5, 601)
    s = np.linspace(0, 4, 1601)
    stiffnesses = (1.0, 2.0, 4.0)
    colors = ("#2469a0", "#c76720", "#27805a")
    plt.rcParams.update({"font.size": 11, "figure.facecolor": "white", "axes.facecolor": "white"})
    fig, axes = plt.subplots(1, 3, figsize=(11.6, 4.9))
    fig.subplots_adjust(left=0.065, right=0.98, bottom=0.30, top=0.77, wspace=0.38)
    fig.suptitle("An optical trap acts like a spring near equilibrium", fontsize=17, y=0.97)
    fig.text(0.5, 0.88, "Local quadratic model • fixed drag • relative stiffness κ = 1, 2, 4", ha="center", fontsize=12)
    for kappa, color in zip(stiffnesses, colors):
        well = 0.5 * kappa * q**2
        force = -kappa * q
        position = np.exp(-kappa * s)
        # Independently differentiate sampled energy and trajectory in their interiors.
        np.testing.assert_allclose(-np.gradient(well, q)[1:-1], force[1:-1], atol=1e-11)
        np.testing.assert_allclose(np.gradient(position, s)[1:-1], -kappa * position[1:-1], rtol=2e-5)
        assert np.all(q * force <= 0)
        np.testing.assert_allclose(np.exp(-kappa * (1 / kappa)), np.exp(-1), atol=1e-15)
        axes[0].plot(q, well, color=color, lw=2.4, label=f"κ = {kappa:g}")
        axes[1].plot(q, force, color=color, lw=2.4)
        axes[2].plot(s, position, color=color, lw=2.4)
        axes[2].plot(1 / kappa, np.exp(-1), "o", color=color, ms=6)
    for ax in axes:
        ax.spines[["top", "right"]].set_visible(False)
        ax.grid(alpha=0.18)
    axes[0].set(title=r"Potential well: $U=\frac{1}{2}kx^2$", xlabel=r"Displacement $q=x/\ell$", ylabel=r"$U/(k_0\ell^2)$", ylim=(0, 4.7))
    axes[0].legend(frameon=False, fontsize=10)
    axes[0].axvline(0, color="#777777", lw=0.8, ls=":")
    axes[1].set(title=r"Restoring force: $F=-kx$", xlabel=r"Displacement $q=x/\ell$", ylabel=r"$F/(k_0\ell)$", ylim=(-6.5, 6.5))
    axes[1].axhline(0, color="#777777", lw=0.8)
    axes[1].axvline(0, color="#777777", lw=0.8, ls=":")
    axes[2].set(title=r"Relaxation: $\zeta\dot{x}=-kx$", xlabel=r"Time $s=t/(\zeta/k_0)$", ylabel=r"$x(t)/x(0)$", xlim=(0, 4), ylim=(0, 1.04))
    axes[2].axhline(np.exp(-1), color="#777777", lw=0.8, ls=":")
    fig.text(0.5, 0.115, "Stronger stiffness: steeper well, stronger restoring force, shorter relaxation time τ = ζ/k.", ha="center", fontsize=11)
    fig.text(0.5, 0.05, "Dots mark one relaxation time (x/x₀ = 1/e). Deterministic curves omit Brownian fluctuations.", ha="center", fontsize=10, color="#555555")
    fig.savefig("optical-trap-restoring-force.png", dpi=110, facecolor="white", transparent=False)
    plt.close(fig)
    drag = 6 * np.pi * 1e-3 * 0.5e-6
    print(f"Restoring force / relaxation checks passed; example ζ={drag:.9g} N s/m, τ={drag/1e-5:.9g} s")


if __name__ == "__main__":
    main()