codex/gouy-four-step-interferometry.py

gouy-four-step-interferometry.py
"""Synthetic phase-stepped Gaussian interferometry; save the PNG to cwd."""

from pathlib import Path

import matplotlib.pyplot as plt
import numpy as np


def frames(zeta):
    zeta = np.asarray(zeta)
    phase = -np.arctan(zeta)
    signal = 1 / (1 + zeta**2)
    background = 1 + signal
    amplitude = 2 * np.sqrt(signal)
    steps = np.arange(4) * np.pi / 2
    return background[..., None] + amplitude[..., None] * np.cos(phase[..., None] + steps)


def recover(intensities):
    return np.arctan2(intensities[..., 3] - intensities[..., 1],
                      intensities[..., 0] - intensities[..., 2])


def main():
    zeta = np.linspace(-3, 3, 601)
    intensities = frames(zeta)
    np.testing.assert_allclose(recover(intensities), -np.arctan(zeta), atol=1e-14)
    np.testing.assert_allclose(frames(1), [2.5, 2.5, .5, .5], atol=1e-14)
    np.testing.assert_allclose(frames(-1), [2.5, .5, .5, 2.5], atol=1e-14)
    assert np.all(intensities >= -1e-14)
    np.testing.assert_allclose(intensities[:, 0], intensities[::-1, 0], atol=1e-14)

    plt.rcParams.update({"font.size": 11, "figure.facecolor": "white", "axes.facecolor": "white"})
    fig, axes = plt.subplots(1, 2, figsize=(9.6, 4.2), dpi=100)
    colors = ["#276FBF", "#BD5C18", "#518548", "#8E4C96"]
    labels = ["δ = 0", "δ = π/2", "δ = π", "δ = 3π/2"]
    for i, (color, label) in enumerate(zip(colors, labels)):
        axes[0].plot(zeta, intensities[:, i], color=color, linewidth=1.8, label=label)
    axes[0].set_title("Four phase-stepped intensities", fontsize=12)
    axes[0].set_ylabel("Normalized detector intensity")
    axes[0].set_ylim(-.12, 4.25)
    axes[0].legend(fontsize=9, loc="upper right", framealpha=.95)

    axes[1].plot(zeta, -np.arctan(zeta), color="#343434", linewidth=2,
                 label="Model: −atan(z/zR)")
    sample = np.linspace(-3, 3, 25)
    axes[1].scatter(sample, recover(frames(sample)), marker="o", s=24,
                    facecolor="white", edgecolor="#276FBF", zorder=3,
                    label="Retrieved from four frames")
    for location in [-1, 1]:
        axes[1].axvline(location, color="#777777", linestyle=":", linewidth=.8)
    axes[1].set_yticks([-np.pi / 2, -np.pi / 4, 0, np.pi / 4, np.pi / 2],
                      ["−π/2", "−π/4", "0", "π/4", "π/2"])
    axes[1].set_ylim(-1.65, 1.65)
    axes[1].set_title("Recovered relative phase", fontsize=12)
    axes[1].set_ylabel("Phase (rad)")
    axes[1].legend(fontsize=9, loc="upper right")
    for ax in axes:
        ax.set_xlim(-3, 3)
        ax.set_xlabel("Axial coordinate z/zR")
        ax.grid(alpha=.18)
        ax.spines[["top", "right"]].set_visible(False)
    fig.suptitle("Synthetic Gaussian/reference interferometry", fontsize=13, y=.98)
    fig.text(.5, .025, "The δ = 0 frame is identical at z = ±zR; the quadratures determine the phase sign.",
             ha="center", fontsize=10)
    fig.subplots_adjust(left=.075, right=.98, top=.84, bottom=.18, wspace=.30)
    output = Path(__file__).with_suffix(".png").name
    fig.savefig(output, dpi=100, facecolor="white")
    plt.close(fig)
    print(f"{output}: 960×420; all phase-retrieval and frame-symmetry checks passed")


if __name__ == "__main__":
    main()