codexrotational-diffusion-planar-orientation.py
rotational-diffusion-planar-orientation.py
"""Original planar rotation schematic and Brownian toy paths; Python 3.14.
Run from the output directory. Requires numpy 2.3.5 and matplotlib 3.10.7.
All paths are simulated, and all correlation curves are exact model curves.
"""
from pathlib import Path
import matplotlib.pyplot as plt
from matplotlib.patches import Ellipse, FancyArrowPatch
import numpy as np
def main():
plt.rcParams.update({"font.size": 11, "axes.spines.top": False,
"axes.spines.right": False, "figure.facecolor": "white"})
blue, orange, green = "#176b9b", "#c65b17", "#368366"
fig = plt.figure(figsize=(11.2, 7.4), dpi=100, layout="constrained")
grid = fig.add_gridspec(2, 2, height_ratios=(0.85, 1.25))
ax = fig.add_subplot(grid[0, 0])
ax.set(xlim=(-0.3, 4.4), ylim=(-0.9, 1.05), aspect="equal")
ax.axis("off")
ax.set_title("A marked end makes an arrow", loc="left", weight="bold")
for x, angle in [(0.55, 30), (3.1, 210)]:
ax.add_patch(Ellipse((x, 0.05), 1.45, 0.42, angle=angle,
facecolor="#dcecf5", edgecolor=blue, lw=1.7))
theta = np.deg2rad(angle)
end = (x + 0.55 * np.cos(theta), 0.05 + 0.55 * np.sin(theta))
ax.plot(*end, "o", color=orange, ms=8)
ax.add_patch(FancyArrowPatch((x, 0.05), end, arrowstyle="->",
mutation_scale=13, color=blue, lw=1.5))
ax.text(1.8, 0.05, r"$\ne$", ha="center", fontsize=23)
ax.text(0.55, -0.73, r"$\phi$", ha="center")
ax.text(3.1, -0.73, r"$\phi+\pi$", ha="center")
ax = fig.add_subplot(grid[0, 1])
ax.set(xlim=(-0.3, 4.4), ylim=(-0.9, 1.05), aspect="equal")
ax.axis("off")
ax.set_title("An unmarked long axis is a director", loc="left", weight="bold")
for x, angle in [(0.55, 30), (3.1, 210)]:
ax.add_patch(Ellipse((x, 0.05), 1.45, 0.42, angle=angle,
facecolor="#e2f0e9", edgecolor=green, lw=1.7))
theta = np.deg2rad(angle)
dx, dy = 0.58 * np.cos(theta), 0.58 * np.sin(theta)
ax.plot([x-dx, x+dx], [0.05-dy, 0.05+dy], color=green, lw=1.5)
ax.text(1.8, 0.05, "=", ha="center", fontsize=23)
ax.text(1.8, -0.73, r"$\phi\equiv\phi+\pi$ (same visible shape)", ha="center")
ax = fig.add_subplot(grid[1, 0])
dr, dt = 0.25, 0.01
t = np.arange(601) * dt
rng = np.random.default_rng(74319)
for color in (blue, orange, green):
phi = np.r_[0.0, np.cumsum(rng.normal(0, np.sqrt(2*dr*dt), len(t)-1))]
ax.plot(t, phi, lw=1.3, color=color)
ax.axhline(0, lw=0.7, color="#aaaaaa")
ax.set(xlabel="Time (s)", ylabel=r"Unwrapped angle $\phi(t)-\phi(0)$ (rad)",
xlim=(0, 6), title="Three simulated planar Brownian paths")
ax.text(0.04, 0.97, r"$D_r=0.25\ \mathrm{s}^{-1}$, $\Delta t=0.01$ s",
transform=ax.transAxes, va="top", fontsize=10,
bbox={"facecolor": "white", "edgecolor": "none", "alpha": 0.9})
ax = fig.add_subplot(grid[1, 1])
ax.plot(t, np.exp(-dr*t), color=blue, lw=2.4,
label=r"Arrow: $\langle\cos\Delta\phi\rangle=e^{-D_rt}$")
ax.plot(t, np.exp(-4*dr*t), color=green, lw=2.4,
label=r"Axis: $\langle\cos2\Delta\phi\rangle=e^{-4D_rt}$")
ax.axvline(2, color="#999999", lw=0.8, ls=":")
for factor, color, label in [(1, blue, "0.607"), (4, green, "0.135")]:
value = np.exp(-factor*dr*2)
ax.plot(2, value, "o", color=color)
ax.annotate(label, (2, value), (2.2, value+0.04), color=color)
ax.set(xlabel="Lag time (s)", ylabel="Orientational correlation",
xlim=(0, 6), ylim=(0, 1.08), title="Exact ensemble curves for the same planar model")
ax.legend(loc="upper right", fontsize=10, frameon=False)
fig.suptitle("Planar rotational diffusion: identify what you can see", fontsize=16, weight="bold")
fig.supxlabel("Toy simulations and analytic curves, not experimental measurements", fontsize=10)
fig.savefig(Path.cwd() / (Path(__file__).stem + ".png"), facecolor="white")
plt.close(fig)
if __name__ == "__main__":
main()