codexcirculant-fourier-modes.py
circulant-fourier-modes.py
"""Fourier eigenvectors of the four-node cycle; write the PNG in the CWD."""
from pathlib import Path
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
import numpy as np
def main():
nodes = np.arange(4)
column = np.array([2.0, -1.0, 0.0, -1.0])
laplacian = column[(nodes[:, None] - nodes[None, :]) % 4]
eigenvalues = np.fft.fft(column)
modes = np.exp(2j * np.pi * nodes[:, None] * nodes[None, :] / 4)
assert np.allclose(laplacian @ modes, modes * eigenvalues)
assert np.allclose(modes.conj().T @ modes, 4 * np.eye(4))
assert np.allclose(eigenvalues, [0, 2, 4, 2])
fig, axes = plt.subplots(2, 2, figsize=(11, 6), dpi=110)
fig.patch.set_facecolor("white")
for k, ax in enumerate(axes.flat):
real = np.round(modes[:, k].real, 12)
imag = np.round(modes[:, k].imag, 12)
ax.plot(nodes, real, "o-", color="#245f9b", lw=2,
markersize=7, label="Real part")
ax.plot(nodes, imag, "s--", color="#cb7025", lw=2,
markersize=6, label="Imaginary part")
ax.axhline(0, color="#777777", lw=0.7)
ax.set_title(f"Mode k = {k}: eigenvalue = {eigenvalues[k].real:g}",
fontsize=12, pad=9)
ax.set_xticks(nodes)
ax.set_ylim(-1.3, 1.3)
ax.set_xlabel("Node index n")
ax.set_ylabel("Component value")
ax.grid(alpha=0.18)
axes[0, 0].legend(loc="lower right", fontsize=9)
fig.suptitle("Fourier modes of the four-node cycle Laplacian", fontsize=16)
fig.text(0.5, 0.035,
"Entries have modulus 1; each vector has norm 2. "
"Lines only connect discrete samples.",
ha="center", fontsize=11)
fig.subplots_adjust(left=0.08, right=0.98, bottom=0.13,
top=0.87, hspace=0.57, wspace=0.25)
fig.savefig(Path.cwd() / "circulant-fourier-modes.png",
facecolor="white", transparent=False)
plt.close(fig)
if __name__ == "__main__":
main()