codex/error-function-diffusion-step-profiles.py

error-function-diffusion-step-profiles.py
"""Generate exact whole-line diffusion fronts from unit-step initial data."""
from math import erfc, sqrt
from pathlib import Path
import matplotlib.pyplot as plt
import numpy as np


def front(x, time, diffusivity=1.0):
    return np.array([0.5 * erfc(float(v) / (2 * sqrt(diffusivity * time))) for v in x])


def main():
    times = (0.04, 0.25, 1.0)
    colors = ("#168c81", "#e38c23", "#7354aa")
    x = np.linspace(-4.5, 4.5, 1001)
    eta = np.linspace(-2.5, 2.5, 801)
    fig, axes = plt.subplots(1, 2, figsize=(12.2, 5.4), dpi=100, layout="constrained")
    fig.set_facecolor("white")
    axes[0].plot([-4.5, 0, 0, 4.5], [1, 1, 0, 0], color="gray", linestyle="--", linewidth=1.5, label="Initial unit step")
    for time, color in zip(times, colors):
        axes[0].plot(x, front(x, time), color=color, linewidth=2.2, label=f"t = {time:g}")
        # Evaluate each curve in physical coordinates before normalizing the axis.
        physical = 2 * sqrt(time) * eta
        axes[1].plot(eta, front(physical, time), color=color, linewidth=2.3, linestyle={0.04: "-", 0.25: "--", 1.0: ":"}[time], label=f"t = {time:g}")
    axes[0].set(xlabel="Position x (model length units)", ylabel="Normalized level u", title="Width grows as √t, with D = 1")
    axes[1].set(xlabel=r"Similarity variable $\eta=x/(2\sqrt{Dt})$", ylabel="Normalized level u", title="All times collapse onto ½ erfc(η)")
    for ax in axes:
        ax.axvline(0, color="#bbbbbb", linewidth=0.8)
        ax.axhline(0.5, color="#bbbbbb", linewidth=0.8)
        ax.set_ylim(-0.04, 1.04)
        ax.grid(alpha=0.16)
        ax.legend(loc="upper right", framealpha=0.92)
    fig.suptitle("Diffusion smooths a discontinuity into an error-function front", fontsize=15)
    fig.savefig(Path(__file__).with_suffix(".png").name, facecolor="white")
    plt.close(fig)


if __name__ == "__main__":
    main()