codexoptical-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()