codexlock-in-quadratures-and-window-response.py
lock-in-quadratures-and-window-response.py
"""Original I/Q geometry and exact finite-window rejection.
Python 3.14, NumPy 2.3.5, Matplotlib 3.10.7. Run from _media; PNG goes to CWD.
"""
from pathlib import Path
import matplotlib.pyplot as plt
import numpy as np
def main():
plt.rcParams.update({"font.size": 11, "axes.spines.top": False,
"axes.spines.right": False})
fig, (ax, bx) = plt.subplots(1, 2, figsize=(10, 4.8), layout="constrained",
facecolor="white", width_ratios=[1, 1.25])
blue, orange = "#1663a6", "#c45111"
phi = np.pi / 3
x, y = 0.2, 0.2 * np.sqrt(3)
circle = np.linspace(0, 2 * np.pi, 500)
ax.plot(0.4 * np.cos(circle), 0.4 * np.sin(circle), color="#c9cdd1", lw=1.3)
ax.axhline(0, color="#7c8288", lw=0.8)
ax.axvline(0, color="#7c8288", lw=0.8)
ax.annotate("", xy=(x, y), xytext=(0, 0),
arrowprops={"arrowstyle": "->", "lw": 2.4, "color": blue})
ax.plot([x, x], [0, y], ls="--", color=blue)
ax.plot([0, x], [y, y], ls="--", color=blue)
arc = np.linspace(0, phi, 100)
ax.plot(0.12 * np.cos(arc), 0.12 * np.sin(arc), color=blue)
ax.text(0.13, 0.07, "60°", color=blue, fontsize=11)
ax.text(0.012, y + 0.012, "Y = 0.346410 V", fontsize=10)
ax.text(0.15, -0.062, "X = 0.200 V", fontsize=10)
ax.text(-0.21, 0.21, "R = 0.400 V", color=blue, fontsize=11)
ax.annotate("", xy=(x + 0.1, y), xytext=(x, y),
arrowprops={"arrowstyle": "->", "lw": 2, "color": orange})
ax.plot([0, x + 0.1], [0, y], ls=":", lw=2, color=orange)
ax.text(0.25, 0.41, "+0.100 V\nsynchronized", color=orange, ha="center", fontsize=10)
ax.set(xlim=(-0.45, 0.48), ylim=(-0.45, 0.48), xlabel="X (V)", ylabel="Y (V)")
ax.set_aspect("equal")
ax.set_title("Two coordinates retain amplitude and phase", fontsize=12)
u = np.linspace(-3, 3, 2401)
# u = (f - f0) T, f0 T = 4; psi = 0. np.sinc is sin(pi u)/(pi u).
difference = np.exp(1j * np.pi * u) * np.sinc(u)
sum_term = np.exp(-1j * np.pi * (8 + u)) * np.sinc(8 + u)
response = np.abs(difference + sum_term)
bx.plot(u, response, color=blue, lw=2, label="Exact real-input response")
bx.plot(u, abs(difference), color=orange, ls="--", lw=1.7,
label="Difference-frequency term only")
example = 32 / (17 * np.pi)
bx.scatter([0.5], [example], color=blue, zorder=4, s=30)
bx.annotate("u = 0.5\nexact: 0.599172", xy=(0.5, example), xytext=(1.1, 0.76),
arrowprops={"arrowstyle": "->", "color": blue}, fontsize=10)
bx.set(xlim=(-3, 3), ylim=(0, 1.12), xlabel=r"Detuning $u=(f-f_0)T$",
ylabel=r"Recovered amplitude / input amplitude $|Z_T|/B$")
bx.set_title("Four reference cycles give finite rejection", fontsize=12)
bx.legend(loc="upper right", fontsize=9)
bx.grid(alpha=0.2)
for a in (ax, bx):
a.set_facecolor("white")
fig.get_layout_engine().set(rect=(0, 0.09, 1, 0.91))
fig.text(0.5, 0.024, "Constructed model • peak convention • right panel: ψ = 0, f₀T = 4",
ha="center", fontsize=10)
fig.savefig(Path.cwd() / (Path(__file__).stem + ".png"), dpi=120,
facecolor="white", transparent=False)
plt.close(fig)
if __name__ == "__main__":
main()