codexcomplex-square-map.py
complex-square-map.py
"""Plot z -> z**2 globally and its local rotation/scaling at 1+i.
Run from _media; tested with Python 3.14.4, NumPy 2.3.5 and
Matplotlib 3.10.7. The opaque PNG is saved in the current directory.
"""
from pathlib import Path
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
import numpy as np
def draw_complex(ax, points, **kwargs):
ax.plot(points.real, points.imag, **kwargs)
def arrow(ax, endpoint, color, label, offset):
ax.annotate("", xy=(endpoint.real, endpoint.imag), xytext=(0, 0),
arrowprops={"arrowstyle": "->", "lw": 2.3, "color": color})
ax.annotate(label, xy=(endpoint.real, endpoint.imag), xytext=offset,
textcoords="offset points", color=color, fontsize=11)
def main():
plt.rcParams.update({"font.size": 11, "axes.titlesize": 13})
blue, orange, purple = "#2366a8", "#b95d16", "#844ba0"
fig, axes = plt.subplots(2, 2, figsize=(9.6, 7.8), dpi=120,
constrained_layout=True)
fig.patch.set_facecolor("white")
fig.suptitle(r"The complex square: global bending, local rotation and scaling",
fontsize=15)
source, target, local_source, local_target = axes.flat
t = np.linspace(-1.5, 1.5, 601)
for level in np.linspace(-1.5, 1.5, 7):
vertical = level + 1j*t
horizontal = t + 1j*level
for curve, color in ((vertical, blue), (horizontal, orange)):
draw_complex(source, curve, color=color, lw=1.2, alpha=.8)
draw_complex(target, curve**2, color=color, lw=1.2, alpha=.8)
source.set(title=r"Input grid in the $z$ plane", xlim=(-1.7, 1.7),
ylim=(-1.7, 1.7), xlabel=r"Re $z$", ylabel=r"Im $z$")
target.set(title=r"Exact image $w=z^2$", xlim=(-4.9, 4.9),
ylim=(-4.9, 4.9), xlabel=r"Re $w$", ylabel=r"Im $w$")
for ax, point, text_label in ((source, 1+1j, r"$z_0=1+i$"),
(target, 2j, r"$w_0=2i$")):
ax.scatter([point.real], [point.imag], color=purple, s=42, zorder=6)
ax.annotate(text_label, (point.real, point.imag), xytext=(7, 8),
textcoords="offset points", color=purple,
bbox={"facecolor": "white", "edgecolor": "none", "alpha": .9})
ax.scatter([0], [0], color="black", s=22, zorder=6)
target.annotate("critical point: derivative = 0", (0, 0), xytext=(-4.5, -3.8),
fontsize=10, arrowprops={"arrowstyle": "->", "color": "#444444"},
bbox={"facecolor": "white", "edgecolor": "none", "alpha": .9})
theta = np.linspace(0, 2*np.pi, 601)
h = .1*np.exp(1j*theta)
linear = 2*(1+1j)*h
exact = linear+h**2
draw_complex(local_source, h, color=purple, lw=2)
draw_complex(local_target, exact, color=purple, lw=2, label="exact displacement")
draw_complex(local_target, linear, color="black", lw=1.4, ls="--",
label=r"linear prediction $(2+2i)h$")
arrow(local_source, .1+0j, blue, r"$h=0.1$", (5, -5))
arrow(local_source, .1j, orange, r"$h=0.1i$", (5, 2))
arrow(local_target, .2+.2j, blue, r"$0.2+0.2i$", (0, 10))
arrow(local_target, -.2+.2j, orange, r"$-0.2+0.2i$", (-42, 10))
local_source.set(title=r"Small increments around $z_0=1+i$",
xlim=(-.17, .17), ylim=(-.17, .17),
xlabel=r"Re $h$", ylabel=r"Im $h$")
local_source.text(0, -.15, r"$|h|=0.1$", ha="center", fontsize=11)
local_target.set(title=r"Rotation $45^\circ$; scale $2\sqrt{2}$",
xlim=(-.44, .44), ylim=(-.44, .44),
xlabel=r"Re $(f(z_0+h)-f(z_0))$",
ylabel=r"Im $(f(z_0+h)-f(z_0))$")
local_target.legend(loc="lower center", fontsize=9, framealpha=1)
for ax in axes.flat:
ax.set_facecolor("white")
ax.set_aspect("equal", adjustable="box")
ax.axhline(0, color="#999999", lw=.7, zorder=0)
ax.axvline(0, color="#999999", lw=.7, zorder=0)
ax.spines[["top", "right"]].set_visible(False)
fig.savefig(Path.cwd() / "complex-square-map.png", facecolor="white",
transparent=False)
plt.close(fig)
if __name__ == "__main__":
main()