codexcomplex-polar-multiplication.py
complex-polar-multiplication.py
"""Illustrate fixed complex multiplication as a rotation and uniform scale.
Tested with Python 3.14.4, NumPy 2.3.5, Matplotlib 3.10.7.
Write the matching opaque PNG to the current working directory.
"""
from pathlib import Path
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
import numpy as np
def arrow(ax, end, color, label, offset):
ax.annotate("", (end.real, end.imag), (0, 0),
arrowprops={"arrowstyle": "->", "color": color, "lw": 2.4})
ax.annotate(label, (end.real, end.imag), xytext=offset,
textcoords="offset points", color=color, fontsize=11,
bbox={"facecolor": "white", "edgecolor": "none", "alpha": .9})
def main():
plt.rcParams.update({"font.size": 11, "axes.titlesize": 13})
blue, orange, purple = "#2468a0", "#b65718", "#81449b"
z, multiplier = 3+4j, 1+1j
product = multiplier*z
assert product == -1+7j and 1j*z == -4+3j
assert np.isclose(abs(product), 5*np.sqrt(2))
assert np.isclose(np.angle(product)-np.angle(z), np.pi/4)
matrix = np.array([[1., -1.], [1., 1.]])
assert np.allclose(matrix.T @ matrix, 2*np.eye(2))
fig, axes = plt.subplots(1, 2, figsize=(9.4, 5.0), dpi=120,
constrained_layout=True)
fig.patch.set_facecolor("white")
left, right = axes
arrow(left, z, blue, r"$z=3+4i$", (8, -6))
arrow(left, 1j*z, purple, r"$iz=-4+3i$", (-22, 10))
arrow(left, product, orange, r"$(1+i)z=-1+7i$", (-38, 9))
arc = np.linspace(np.angle(z), np.angle(product), 150)
left.plot(2*np.cos(arc), 2*np.sin(arc), color=orange, lw=1.8)
left.annotate("", (2*np.cos(arc[-1]), 2*np.sin(arc[-1])),
(2*np.cos(arc[-8]), 2*np.sin(arc[-8])),
arrowprops={"arrowstyle": "->", "color": orange})
left.text(.2, 2.4, r"$\pi/4$", color=orange)
left.text(-5.5, -1.8, r"$|z|=|iz|=5$" + "\n" +
r"$|(1+i)z|=5\sqrt{2}$", fontsize=11)
left.set(title="One point: a quarter-turn or a scaled eighth-turn",
xlim=(-6, 6), ylim=(-3.2, 8.8), xticks=[-4, -1, 0, 3],
yticks=[0, 3, 4, 7])
t = np.linspace(-2, 2, 100)
for level in range(-2, 3):
for curve in (level+1j*t, t+1j*level):
right.plot(curve.real, curve.imag, color=blue, lw=1.0, alpha=.45)
transformed = multiplier*curve
right.plot(transformed.real, transformed.imag,
color=orange, lw=1.3, alpha=.7)
theta = np.linspace(0, 2*np.pi, 400)
right.plot(np.cos(theta), np.sin(theta), color=blue, lw=2)
right.plot(np.sqrt(2)*np.cos(theta), np.sqrt(2)*np.sin(theta),
color=orange, lw=2)
arrow(right, 1+0j, blue, r"$1$", (0, -23))
arrow(right, 1+1j, orange, r"$1+i$", (8, 0))
right.plot([], [], color=blue, label="Input grid and unit circle")
right.plot([], [], color=orange, label=r"Image under $z\mapsto(1+i)z$")
right.legend(loc="lower center", fontsize=9, framealpha=1)
right.set(title=r"Every point: rotation $\pi/4$, scale $\sqrt{2}$",
xlim=(-4.8, 4.8), ylim=(-4.8, 4.8),
xticks=[-4, -2, 0, 2, 4], yticks=[-4, -2, 0, 2, 4])
for ax in axes:
ax.set_aspect("equal")
ax.set_facecolor("white")
ax.axhline(0, color="#555555", lw=.9)
ax.axvline(0, color="#555555", lw=.9)
ax.set(xlabel="Real part", ylabel="Imaginary part")
ax.spines[["top", "right"]].set_visible(False)
fig.savefig(Path.cwd() / Path(__file__).with_suffix(".png").name,
facecolor="white", transparent=False)
plt.close(fig)
if __name__ == "__main__":
main()