codexlinear-dependence-redundancy.py
linear-dependence-redundancy.py
"""Show redundancy and a complete family of representations; PNG goes to CWD.
Tested with Python 3.14.4, NumPy 2.3.5 and Matplotlib 3.10.7.
Run: make _media/linear-dependence-redundancy.png
"""
from pathlib import Path
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
import numpy as np
def arrow(ax, start, end, color, width=2.8, style="-"):
ax.annotate("", xy=end, xytext=start,
arrowprops=dict(arrowstyle="->", color=color, lw=width,
linestyle=style, shrinkA=0, shrinkB=0,
mutation_scale=16), zorder=4)
def main():
v1, v2, v3 = np.array([1., 1.]), np.array([1., -1.]), np.array([2., 0.])
basis = np.column_stack((v1, v2))
generators = np.column_stack((v1, v2, v3))
target = np.array([3., 1.])
relation = np.array([-1., -1., 1.])
assert np.allclose(v1 + v2, v3)
assert np.allclose(generators @ relation, [0, 0])
assert np.linalg.matrix_rank(basis) == 2
assert np.linalg.matrix_rank(generators) == 2
assert np.allclose(np.linalg.solve(basis, target), [2, 1])
t = np.linspace(-2, 3, 201)
family = np.vstack((2-t, 1-t, t))
assert np.allclose(generators @ family, target[:, None])
# Both coordinate equations force this family when c3 is selected freely.
assert np.allclose(family[0] + family[2], 2)
assert np.allclose(family[1] + family[2], 1)
assert np.allclose(generators @ [2, 1, 0], target)
assert np.allclose(generators @ [1, 0, 1], target)
assert np.allclose(generators @ [0, -1, 2], target)
assert np.allclose(1j * 1 + (-1) * 1j, 0)
assert np.linalg.matrix_rank([[1, 0], [0, 1]]) == 2
plt.rcParams.update({"font.size": 11, "figure.facecolor": "white"})
fig, axes = plt.subplots(1, 3, figsize=(11.4, 5.5), facecolor="white")
fig.subplots_adjust(left=.055, right=.98, bottom=.28, top=.75, wspace=.35)
blue, orange, purple, green = "#2466a3", "#c15a16", "#8753a1", "#217346"
for ax in axes[:2]:
ax.set_facecolor("white")
ax.set_aspect("equal")
ax.set_xlim(-.4, 4.)
ax.set_ylim(-1.5, 2.6)
ax.set_xticks([0, 1, 2, 3])
ax.set_yticks([-1, 0, 1, 2])
ax.grid(color="#eeeeee", lw=.8)
ax.axhline(0, color="#999999", lw=.8)
ax.axvline(0, color="#999999", lw=.8)
arrow(ax, [0, 0], target, green, width=2)
ax.plot(*target, "o", color=green, ms=5, zorder=5)
ax.text(2.75, 1.24, r"$w=(3,1)$", color=green)
ax.set_xlabel("x")
ax.set_ylabel("y", rotation=0, labelpad=4)
left, middle, right = axes
left.set_title("Independent pair", fontsize=13, pad=12)
arrow(left, [0, 0], 2*v1, blue)
arrow(left, 2*v1, target, orange)
arrow(left, [0, 0], v2, orange, width=1.8, style="--")
left.text(.18, 1.25, r"$2v_1$", color=blue)
left.text(2.6, 1.85, r"$+v_2$", color=orange)
left.text(.8, -1.3, r"$v_2$", color=orange)
middle.set_title("Add a redundant arrow", fontsize=13, pad=12)
arrow(middle, [0, 0], v1, blue)
arrow(middle, v1, target, purple)
arrow(middle, [0, 0], v3, purple, style="--", width=1.8)
middle.text(.1, .85, r"$v_1$", color=blue)
middle.text(1.8, 1.20, r"$+v_3$", color=purple)
middle.text(1.15, -.38, r"$v_3=(2,0)$", color=purple)
middle.text(.25, -1.15, r"$v_3=v_1+v_2$", color=purple)
right.set_facecolor("white")
right.set_title("A family of coefficients", fontsize=13, pad=12)
right.set_xlim(-.6, 2.6)
right.set_ylim(-1.9, 2.9)
right.set_xlabel(r"$t=c_3$")
right.set_ylabel("coefficient")
right.set_xticks([0, 1, 2])
right.set_yticks([-1, 0, 1, 2])
right.grid(color="#eeeeee", lw=.8)
for row, color, label in zip(family, [blue, orange, purple],
[r"$c_1=2-t$", r"$c_2=1-t$", r"$c_3=t$"]):
right.plot(t, row, color=color, lw=2, label=label)
for sample in [0, 1, 2]:
for value, color in zip([2-sample, 1-sample, sample], [blue, orange, purple]):
right.plot(sample, value, "o", color=color, ms=5)
right.legend(loc="upper center", bbox_to_anchor=(.5, -.27),
ncol=1, frameon=False, fontsize=10)
fig.suptitle("Redundancy changes the coefficients, not the target", fontsize=17, y=.97)
fig.text(.5, .87, r"$v_1=(1,1),\quad v_2=(1,-1),\quad v_3=(2,0)$",
ha="center", fontsize=13)
for ax, line in zip(axes[:2], [r"Unique: $(c_1,c_2)=(2,1)$",
r"Also: $(c_1,c_2,c_3)=(1,0,1)$"]):
box = ax.get_position()
fig.text((box.x0 + box.x1)/2, .145, line, ha="center", fontsize=11)
fig.text(.34, .045, r"$(2,1,0),\ (1,0,1),\ (0,-1,2)$ all give $(3,1)$",
ha="center", fontsize=11)
fig.savefig(Path.cwd() / (Path(__file__).stem + ".png"),
dpi=140, facecolor="white", transparent=False)
plt.close(fig)
print("Verified relation, ranks, 201 representations, and real/complex example.")
if __name__ == "__main__":
main()