import numpy as np
np.set_printoptions(precision=4, suppress=True, linewidth=100)
import matplotlib.pyplot as plt
# Fonts: the English edition needs no CJK font, so nothing is downloaded when _lang == 'en';
# the Chinese editions use this same block to fetch Noto Sans TC/SC where no CJK font is installed
import os, urllib.request
import matplotlib.font_manager as fm
_lang = 'en'
_cjk = ['Microsoft JhengHei', 'PingFang TC', 'Noto Sans CJK TC', 'Noto Sans TC']
_have = {f.name for f in fm.fontManager.ttflist}
if _lang != 'en' and not _have & set(_cjk):
_font = os.path.join(os.path.expanduser('~'), '.cache', 'fonts', 'NotoSansTC.ttf')
try:
if not os.path.exists(_font):
os.makedirs(os.path.dirname(_font), exist_ok=True)
urllib.request.urlretrieve('https://github.com/google/fonts/raw/main/ofl/notosanstc/NotoSansTC%5Bwght%5D.ttf', _font + '.part')
os.replace(_font + '.part', _font)
fm.fontManager.addfont(_font)
_have.add('Noto Sans TC')
except OSError as err:
print('Could not download the CJK font; Chinese text in figures may not display:', err)
plt.rcParams['font.family'] = [f for f in _cjk if f in _have] + ['DejaVu Sans']
plt.rcParams['axes.unicode_minus'] = False
# ── Visual hierarchy helpers ─────────────────────────────────────────────
def print_header(title):
print("=" * 60)
print(f" {title}")
print("=" * 60)
def print_step(step, desc):
print(f"\n▶ Step {step}: {desc}")
print("-" * 40)
def compare_print(label, actual, expected_desc):
print(f"\n[{label}]")
print(f" Computed value: {actual}")
print(f" Expected value: {expected_desc}")
# ── Math utilities ─────────────────────────────────────────────
def proj_vec(u, v):
"""Projection vector of v onto the direction of u"""
u = np.asarray(u, float); v = np.asarray(v, float)
return (np.dot(u, v) / np.dot(u, u)) * u
def proj_mat(u):
"""Projection matrix P = uu^T / (u^T u)"""
u = np.asarray(u, float).reshape(-1, 1)
return u @ u.T / (u.T @ u)[0, 0]
def reflect_mat_2d(n):
"""Reflection matrix I − 2nn^T in ℝ², where n is a unit normal vector"""
n = np.asarray(n, float); n = n / np.linalg.norm(n)
return np.eye(2) - 2 * np.outer(n, n)
def reflect_mat_3d(n):
"""Reflection matrix I − 2nn^T in ℝ³"""
n = np.asarray(n, float); n = n / np.linalg.norm(n)
return np.eye(3) - 2 * np.outer(n, n)
def rodrigues(u, theta):
"""Rodrigues rotation matrix"""
u = np.asarray(u, float); u = u / np.linalg.norm(u)
K = np.array([[0, -u[2], u[1]], [u[2], 0, -u[0]], [-u[1], u[0], 0]])
return np.eye(3) + np.sin(theta)*K + (1 - np.cos(theta))*(K @ K)
# ── Complex number → matrix mapping ──────────────────────────
def M(z):
"""The 2×2 matrix M(z) = [[a,-b],[b,a]] corresponding to the complex number z = a + bi"""
a, b = z.real, z.imag
return np.array([[a, -b], [b, a]])
# ── Skew-symmetric matrix of a vector ─────────────────────
def skew(v):
"""The skew-symmetric matrix [v]× corresponding to the 3D vector v = (a,b,c)"""
a, b, c = v
return np.array([[0, -c, b],
[c, 0, -a],
[-b, a, 0]])
print("Helper functions loaded")
Matrix Multiplication and Matrix Powers¶
# ============================================================
print_header("Matrix Multiplication")
# ============================================================
print_step(1, "Exercise 1: (AB)C = 0 (AB times the zero matrix)")
A1 = np.array([[12,-8],[7,-24]]); B1 = np.array([[11,13],[-21,-30]]); C1 = np.zeros((2,2))
result = (A1 @ B1) @ C1
compare_print("(AB)C", result, "zero matrix O")
print_step(2, "Exercise 2: check distributivity AB+AC = A(B+C)")
A2 = np.array([[33,-18],[27,-243]])
B2 = np.array([[4,7],[-6,12]]); C2 = np.array([[-3,-7],[6,-11]])
lhs = A2@B2 + A2@C2; rhs = A2@(B2+C2)
print(" AB+AC ="); print(lhs)
compare_print("AB+AC = A(B+C)?", np.allclose(lhs, rhs), "True")
print_step(3, "Exercise 3: the products AB and BA of rectangular matrices")
A3 = np.array([[2,-1,3],[0,4,-2]]); B3 = np.array([[1,0],[-1,2],[3,1]])
print(" AB ="); print(A3@B3)
print(" BA ="); print(B3@A3)
print_step(4, "Exercise 4: AB and BA (a counterexample to commutativity)")
A4 = np.array([[1,0,2],[-1,3,1],[2,1,0]]); B4 = np.array([[2,1,0],[0,-1,3],[1,2,1]])
print(" AB ="); print(A4@B4)
print(" BA ="); print(B4@A4)
compare_print("AB = BA?", np.allclose(A4@B4, B4@A4), "False (square matrices do not commute in general)")
print_step(5, "Exercise 5: I₃A = A (multiplication by the identity matrix)")
A5 = np.array([[2,-1,3],[1,0,2],[-1,4,1]]); I3 = np.eye(3)
compare_print("I₃A = A?", np.allclose(I3@A5, A5), "True")
print_step(6, "Exercise 6: diagonal matrix × all-ones matrix (AB and BA)")
A6 = np.diag([-2,0,2,4]); B6 = np.ones((4,4))
print(" AB ="); print(A6@B6)
print(" BA ="); print(B6@A6)
print_step(7, "Exercise 7: constant matrices of order n, aJ·bJ = nabJ")
n=4; a,b=3,2
An=a*np.ones((n,n)); Bn=b*np.ones((n,n))
compare_print(f"AB = nab·J (n={n},a={a},b={b})",
np.allclose(An@Bn, n*a*b*np.ones((n,n))), "True")
# ============================================================
print_header("Matrix Powers")
# ============================================================
print_step(1, "Exercise 8: the matrix polynomial A²−7A+12I = 0")
A = np.array([[-2,-3],[10,9]])
result = A@A - 7*A + 12*np.eye(2)
compare_print("A²−7A+12I", result, "zero matrix O")
print_step(2, "Exercise 9: powers of a rotation matrix (A = clockwise 90°)")
A = np.array([[0,1],[-1,0]])
print(" A² ="); print(A@A)
print(" A³ ="); print(A@A@A)
print(" A⁴ ="); print(np.linalg.matrix_power(A,4))
compare_print("A⁴", np.linalg.matrix_power(A,4), "I (rotation by 360° = identity)")
print_step(3, "Exercise 10: the nilpotent matrix A²=0")
A = np.array([[1,1,1],[0,0,0],[-1,-1,-1]])
compare_print("A²", A@A, "zero matrix O (index of nilpotency = 2)")
print_step(4, "Exercise 11: powers of a diagonal matrix Aᵏ = diag(λ₀ᵏ,…)")
lam = np.array([2.0, -1.0, 3.0]); A = np.diag(lam); k=5
print(f" A^{k} ="); print(np.linalg.matrix_power(A,k))
compare_print(f"diag(λᵢ^{k})", np.diag(lam**k), "same as A^k")
# ============================================================
print_header("The Cyclic Property of the Trace")
# ============================================================
np.random.seed(42)
print_step(1, "Verify tr(AB) = tr(BA) (3×4 and 4×3 matrices)")
A = np.random.randn(3, 4)
B = np.random.randn(4, 3)
trAB = np.trace(A @ B)
trBA = np.trace(B @ A)
compare_print("tr(AB)", f"{trAB:.6f}", f"tr(BA) = {trBA:.6f}")
print(f" equal? {np.isclose(trAB, trBA)}")
print_step(2, "Verify the cyclic property for three matrices")
A = np.random.randn(3, 3)
B = np.random.randn(3, 3)
C = np.random.randn(3, 3)
trABC = np.trace(A @ B @ C)
trBCA = np.trace(B @ C @ A)
trCAB = np.trace(C @ A @ B)
trACB = np.trace(A @ C @ B) # not a cyclic permutation (usually unequal)
print(f" tr(ABC) = {trABC:.6f}")
print(f" tr(BCA) = {trBCA:.6f}")
print(f" tr(CAB) = {trCAB:.6f}")
print(f" tr(ACB) = {trACB:.6f} ← not a cyclic permutation (unequal in general)")
print(f"\n tr(ABC)=tr(BCA)? {np.isclose(trABC, trBCA)}")
print(f" tr(ABC)=tr(CAB)? {np.isclose(trABC, trCAB)}")
print(f" tr(ABC)=tr(ACB)? {np.isclose(trABC, trACB)} ← a nonzero difference is expected")
print_step(3, "AB ≠ BA, but tr(AB) = tr(BA)")
# verify that for square matrices AB≠BA but the traces are equal
A2 = np.array([[1.,2.],[3.,4.]])
B2 = np.array([[0.,1.],[1.,0.]])
print(f" A·B =\n{A2@B2}")
print(f" B·A =\n{B2@A2}")
print(f" A·B ≠ B·A? {not np.allclose(A2@B2, B2@A2)}")
print(f" tr(AB) = {np.trace(A2@B2):.4f} = tr(BA) = {np.trace(B2@A2):.4f}? {np.isclose(np.trace(A2@B2), np.trace(B2@A2))}")
Linear Mappings and Matrix Representations¶
# ============================================================
print_header("Linear Mappings and Matrix Representations")
# ============================================================
print_step(1, "Exercise 13(2): T_A(3,−2), A ∈ ℝ²×²")
A = np.array([[2,-1],[-3,4]])
result = A @ np.array([3,-2])
print(" T_A(3,−2) =", result)
print_step(2, "Exercise 14(2): T_A(−1,2,6), A ∈ ℝ²×³")
A = np.array([[2,1,0],[0,3,-1]])
result = A @ np.array([-1,2,6])
print(" T_A(−1,2,6) =", result)
print_step(3, "Exercise 15: finding a preimage")
A = np.array([[1,1,1],[0,1,1],[0,0,1]])
b = np.array([5,9,4])
u = np.linalg.solve(A, b)
print(" preimage u =", u)
compare_print("check Au", A@u, b)
print_step(4, "Exercise 16: kernel check")
A = np.array([[1,2,3],[2,4,6]])
u = np.array([3,0,-1])
compare_print("Au (should be 0)", A@u, "[0, 0]")
print_step(5, "Exercise 17: building the matrix from the images of the basis vectors, ℝ²→ℝ³")
A = np.array([[3,2],[-1,5],[7,-2]])
result = A @ np.array([4,-3])
print(" T(4,−3) =", result)
print_step(6, "Exercise 18: building the matrix from the images of the basis vectors, ℝ²→ℝ⁴")
Te0 = np.array([3,-1,7,5]); Te01 = np.array([2,5,-2,6])
Te1 = Te01 - Te0
A = np.column_stack([Te0, Te1])
result = A @ np.array([-1,1])
print(" T(−1,1) =", result)
print_step(7, "Exercise 21(2): powers of the rotation by 90° about the z axis")
A_rot = np.array([[0,-1,0],[1,0,0],[0,0,1]])
compare_print("A_rot⁴", np.linalg.matrix_power(A_rot, 4), "I (rotation by 4×90°=360°)")
print_step(8, "Exercise 22(2): the shift operator A_shift⁴")
A_sh = np.array([[0,0,0,0],[1,0,0,0],[0,1,0,0],[0,0,1,0]])
compare_print("A_shift⁴", np.linalg.matrix_power(A_sh,4), "zero matrix (nilpotent)")
Scaling Mappings¶
# ============================================================
print_header("Scaling Mappings")
# ============================================================
print_step(1, "Exercise 23(a): scaling diag(2,3) in ℝ²")
A = np.diag([2.0, 3.0])
print(" T(−3,4) =", A @ np.array([-3,4]))
compare_print("area scale factor |det(A)|", abs(np.linalg.det(A)), "6 (area of the unit square × 6)")
print(" A⁻¹ ="); print(np.linalg.inv(A))
print_step(2, "Exercise 23(b): scaling diag(0.1,2,4) in ℝ³")
A3 = np.diag([0.1, 2.0, 4.0])
print(" T(−2,1,3) =", A3 @ np.array([-2,1,3]))
compare_print("volume scale factor |det(A)|", abs(np.linalg.det(A3)), "0.8")
print_step(3, "Exercise 25(1): composition of scaling mappings S₁∘S₀")
A1 = np.diag([2.0, 3.0]); A2 = np.diag([4.0, 0.5])
composed = np.diag(A2 @ A1)
compare_print("scale factors of S₁∘S₀", composed, "diag(8, 1.5)")
print_step(4, "Exercise 26: area-preserving scaling, det = 1")
compare_print("det(diag(1000, 0.001))", np.linalg.det(np.diag([1000, 1/1000])), "1")
Projection Mappings¶
# ============================================================
print_header("Projection Mappings")
# ============================================================
print_step(1, "Exercise 27: projection onto the direction (3,4), checking idempotence")
v = np.array([3.0, 4.0]); u = v / np.linalg.norm(v)
P = np.outer(u, u)
a = np.array([5.0, 0])
Ta = P @ a
print(" T(5,0) =", Ta)
compare_print("(a−Ta)⊥Ta", f"{np.dot(Ta, a-Ta):.2e}", "0")
compare_print("P² = P (idempotence)", np.allclose(P@P, P), "True")
print_step(2, "Exercise 28: projection onto the direction (1,−2,−2) (ℝ³)")
v = np.array([1.0, -2, -2]); u = v / np.linalg.norm(v)
P = np.outer(u, u)
print(" P ="); print(P)
compare_print("det(P) (should be 0, rank 1)", f"{np.linalg.det(P):.2e}", "0")
compare_print("P² = P", np.allclose(P@P, P), "True")
print_step(3, "Exercise 30(4): projection onto the xy-plane")
P_xy = np.diag([1.0, 1, 0])
compare_print("det(P_xy) (rank 2)", np.linalg.det(P_xy), "0")
print_step(4, "Exercise 31: projection onto the plane x+y+z=0 (perpendicular to the normal vector n=(1,1,1)/√3)")
n = np.array([1.0, 1, 1]) / np.sqrt(3)
P_plane = np.eye(3) - np.outer(n, n)
print(" P_plane ="); print(np.round(P_plane, 4))
compare_print("P² = P (idempotent)", np.allclose(P_plane@P_plane, P_plane), "True")
print(" T(1,0,0) =", P_plane @ np.array([1,0,0]))
print_step(5, "Exercise 32: distance from the point (6,8) to the line span{(1,2)}")
v = np.array([1.0, 2]); u = v / np.linalg.norm(v)
P = np.outer(u, u)
a = np.array([6.0, 8])
Ta = P @ a
dist = np.linalg.norm(a - Ta)
compare_print("distance ‖a−P(a)‖", f"{dist:.6f}", f"4/√5 = {4/np.sqrt(5):.6f}")
print_step(6, "Exercise 33: distance between parallel lines")
v = np.array([1.0, 0, 3]); u = v / np.linalg.norm(v)
P = np.outer(u, u)
p = np.array([2.0, 2, 1])
dist = np.linalg.norm(p - P @ p)
compare_print("distance", f"{dist:.6f}", f"√26/2 = {np.sqrt(26)/2:.6f}")
print_step(7, "Exercise 34: composition of two projection matrices (P₀P₁ = P₁P₀)")
P0 = np.diag([1.0,1,0]); P1 = np.diag([1.0,0,1])
print(" P₁P₀ ="); print(P1@P0)
print(" P₀P₁ ="); print(P0@P1)
compare_print("P₀P₁ = P₁P₀?", np.allclose(P0@P1, P1@P0), "True (diagonal matrices commute)")
Reflection Mappings¶
# ============================================================
print_header("Reflection Mappings")
# ============================================================
print_step(1, "Exercise 35: reflection matrix in ℝ² for the normal vector (3,4)")
N = np.array([3.0, 4]); n = N / np.linalg.norm(N)
R = reflect_mat_2d(n)
print(" R ="); print(R)
compare_print("‖T(e₀)‖", f"{np.linalg.norm(R[:,0]):.4f}", "1 (length-preserving)")
compare_print("T(e₀)·T(e₁) (should be 0)", f"{np.dot(R[:,0], R[:,1]):.2e}", "0 (angle-preserving)")
compare_print("det(R)", f"{np.linalg.det(R):.4f}", "−1 (reflection reverses orientation)")
v = np.array([-1.0, 1])
compare_print("‖v‖=‖T(v)‖", np.isclose(np.linalg.norm(v), np.linalg.norm(R@v)), "True")
print_step(2, "Exercise 36: reflection matrix for the line y=x")
n_yx = np.array([1.0, -1]) / np.sqrt(2)
R_yx = reflect_mat_2d(n_yx)
print(" R_{y=x} ="); print(R_yx)
compare_print("R_{y=x}(1,0) = (0,1)?", R_yx @ np.array([1,0]), np.array([0,1]))
print_step(3, "Exercise 39: finding reflection matrices (mapping u=(15,20) to each target)")
u_vec = np.array([15.0, 20])
targets = [np.array([24.0,10]), np.array([20.0,15]),
np.array([-20.0,-15]), np.array([24.0,7])]
for i, t in enumerate(targets):
if np.isclose(np.linalg.norm(t), np.linalg.norm(u_vec)):
axis_dir = u_vec + t; axis_dir = axis_dir / np.linalg.norm(axis_dir)
n_axis = np.array([-axis_dir[1], axis_dir[0]])
R_t = reflect_mat_2d(n_axis)
print(f" ({i+1}) R·u =", R_t @ u_vec,
" target:", t, " ✓" if np.allclose(R_t@u_vec, t) else " ✗")
else:
print(f" ({i+1}) ‖target‖≠‖u‖, no reflection matrix exists")
print_step(4, "Exercise 41: reflection across the plane x+2y+2z=0 in ℝ³")
n3 = np.array([1.0, 2, 2]) / 3
R3 = reflect_mat_3d(n3)
print(" R3 ="); print(np.round(R3, 4))
compare_print("T(1,2,2) (the normal vector is mapped to its negative)", R3 @ np.array([1.0,2,2]),
"−(1,2,2)")
compare_print("T(2,0,−1) (vectors in the plane are fixed)", R3 @ np.array([2.0,0,-1]),
np.array([2.0,0,-1]))
compare_print("det(R3)", f"{np.linalg.det(R3):.4f}", "−1")
compare_print("R3² = I", np.allclose(R3@R3, np.eye(3)), "True (a reflection is self-inverse, i.e., an involution)")
print_step(5, "Exercise 42(3): reflection across a line in ℝ³ = Rodrigues(u,π)")
u = np.array([1.0, 0, 0])
R_line = 2*np.outer(u,u) - np.eye(3)
R_rod_pi = rodrigues(u, np.pi)
compare_print("R_line = Rodrigues(u,π)?", np.allclose(R_line, R_rod_pi), "True")
Rotation Mappings¶
# ============================================================
print_header("Rotation Mappings")
# ============================================================
print_step(1, "Exercise 43: rotation by π/3 — the matrix and isometry")
theta = np.pi/3
R = np.array([[np.cos(theta), -np.sin(theta)],
[np.sin(theta), np.cos(theta)]])
print(" R_{π/3} ="); print(R)
v = np.array([-1.0, 1])
Tv = R @ v
compare_print("‖v‖ = ‖T(v)‖", np.isclose(np.linalg.norm(v), np.linalg.norm(Tv)), "True (rotations preserve length)")
angle = np.arccos(np.dot(v,Tv)/(np.linalg.norm(v)*np.linalg.norm(Tv)))
compare_print("angle between v and T(v)", f"{angle:.6f}", f"π/3 = {np.pi/3:.6f}")
print_step(2, "Exercise 46: Rodrigues rotation (axis (1,1,1)/√3, θ=π/2)")
u_ax = np.array([1,1,1]) / np.sqrt(3)
R_rod = rodrigues(u_ax, np.pi/2)
print(" R ="); print(np.round(R_rod, 4))
compare_print("det(R)", f"{np.linalg.det(R_rod):.4f}", "1 (rotation matrix)")
compare_print("R^T R = I", np.allclose(R_rod.T @ R_rod, np.eye(3)), "True (orthogonal matrix)")
v = np.array([1.0, -1, 0])
compare_print("v ⊥ axis of rotation", np.isclose(np.dot(v, u_ax), 0), "True")
Tv = R_rod @ v
angle = np.arccos(np.clip(np.dot(v,Tv)/(np.linalg.norm(v)*np.linalg.norm(Tv)), -1, 1))
compare_print("angle between v and T(v)", f"{angle:.4f}", f"π/2 = {np.pi/2:.4f}")
print_step(3, "Exercise 49: rebuild the matrix from the images and find the angle and axis of rotation")
Re0 = np.array([2,2,1])/3; Re1 = np.array([-1,2,-2])/3
Re2 = np.cross(Re0, Re1)
R3 = np.column_stack([Re0, Re1, Re2])
print(" R3 ="); print(np.round(R3, 4))
compare_print("det(R3)", f"{np.linalg.det(R3):.4f}", "1")
trace = np.trace(R3)
theta_found = np.arccos((trace - 1) / 2)
compare_print("angle of rotation θ", f"{theta_found:.4f}", f"π/3 = {np.pi/3:.4f}")
eigvals, eigvecs = np.linalg.eig(R3)
axis_idx = np.argmin(np.abs(eigvals.real - 1.0))
axis = eigvecs[:, axis_idx].real
axis = axis / np.linalg.norm(axis)
print(" axis of rotation =", np.round(axis, 4))
# ============================================================
print_header("Basics of the Vector Representation of Complex Numbers")
# ============================================================
zs = [3+4j, -2+1j, 5j, -3+0j]
labels = ['z_0 = 3+4i', 'z_1 = -2+i', 'z_2 = 5i', 'z_3 = -3']
print_step(1, "Complex numbers → 2D vectors")
for lbl, z in zip(labels, zs):
print(f" {lbl} → ({z.real:+.1f}, {z.imag:+.1f}) |modulus| = {abs(z):.4f}")
print_step(2, "Verify complex addition = vector addition")
z0, z1 = zs[0], zs[1]
sum_complex = z0 + z1
v0 = np.array([z0.real, z0.imag])
v1 = np.array([z1.real, z1.imag])
sum_vec = v0 + v1
compare_print("z0+z1 (complex)", f"({sum_complex.real}+{sum_complex.imag}i)",
f"vector sum {sum_vec}")
print_step(3, "Plot in the complex plane")
fig, ax = plt.subplots(figsize=(5, 5))
colors = ['#57068C', '#006385', '#2AD2C9', '#E37222']
for z, lbl, c in zip(zs, ['$z_0$','$z_1$','$z_2$','$z_3$'], colors):
ax.annotate('', xy=(z.real, z.imag), xytext=(0,0),
arrowprops=dict(arrowstyle='->', color=c, lw=2))
ax.text(z.real+0.1, z.imag+0.1, lbl, color=c, fontsize=12)
ax.axhline(0, color='gray', lw=0.8); ax.axvline(0, color='gray', lw=0.8)
ax.set_xlim(-4, 5); ax.set_ylim(-1, 6)
ax.set_xlabel('Re'); ax.set_ylabel('Im')
ax.set_title('Complex numbers in the complex plane', fontsize=13)
ax.set_aspect('equal'); ax.grid(True, alpha=0.3)
plt.tight_layout(); plt.show()
# ============================================================
print_header("The Rotation Property of the Imaginary Unit")
# ============================================================
Mi = M(1j) # M(i)
print_step(1, "M(i) acting on the standard basis")
e0 = np.array([1, 0]); e1 = np.array([0, 1])
print(f" M(i)·e_0 = {Mi @ e0} (expected [0, 1])")
print(f" M(i)·e_1 = {Mi @ e1} (expected [-1, 0])")
print_step(2, "Powers of M(i)")
for k in range(1, 5):
Mk = np.linalg.matrix_power(Mi, k)
print(f" M(i)^{k} =\n{Mk}\n (corresponds to i^{k} = {1j**k:.0f})")
print_step(3, "Multiplying points on the unit circle by i")
thetas = np.linspace(0, 2*np.pi, 8, endpoint=False)
for th in thetas[:3]:
v = np.array([np.cos(th), np.sin(th)])
v_rot = Mi @ v
compare_print(f"θ={np.degrees(th):.0f}°",
np.round(v_rot, 4),
f"[cos{np.degrees(th)+90:.0f}°, sin{np.degrees(th)+90:.0f}°] = {np.round([np.cos(th+np.pi/2), np.sin(th+np.pi/2)],4)}")
# ============================================================
print_header("Exploring the Matrix Representation of Complex Numbers")
# ============================================================
print_step(1, "Write down the matrix representations")
zs3 = [1+1j, 2-3j, np.sqrt(3)+1j]
names = ['M(1+i)', 'M(2-3i)', 'M(√3+i)']
for name, z in zip(names, zs3):
print(f" {name} =\n{M(z)}\n")
print_step(2, "The action of M(1+i) on e_0")
Mz = M(1+1j)
e0 = np.array([1., 0.])
result = Mz @ e0
print(f" M(1+i) · e_0 = {result}")
print(f" length of the output vector = {np.linalg.norm(result):.4f} (expected √2 ≈ {np.sqrt(2):.4f})")
print(f" angle of the output vector = {np.degrees(np.arctan2(result[1], result[0])):.1f}° (expected 45°)")
print_step(3, "Polar-form check")
z = 1+1j
print(f" |1+i| = {abs(z):.4f} (≈ √2 = {np.sqrt(2):.4f})")
print(f" arg(1+i) = {np.degrees(np.angle(z)):.1f}°")
# ============================================================
print_header("Verifying Complex Multiplication with Matrices")
# ============================================================
z0, z1 = 1+1j, 2-1j
print_step(1, "Compute M(z0·z1) and M(z0)·M(z1)")
product_complex = z0 * z1
Mprod = M(product_complex)
Mmul = M(z0) @ M(z1)
print(f" z0·z1 = {product_complex}")
compare_print("M(z0·z1)", Mprod, "M(z0)·M(z1)")
print(f"\n M(z0)·M(z1) =\n{Mmul}")
print(f"\n equal? {np.allclose(Mprod, Mmul)}")
print_step(2, "Geometric check: modulus and argument")
print(f" |z0|={abs(z0):.4f}, arg(z0)={np.degrees(np.angle(z0)):.1f}°")
print(f" |z1|={abs(z1):.4f}, arg(z1)={np.degrees(np.angle(z1)):.1f}°")
print(f" |z0·z1|={abs(product_complex):.4f} (expected {abs(z0)*abs(z1):.4f})")
print(f" arg(z0·z1)={np.degrees(np.angle(product_complex)):.1f}° (expected {np.degrees(np.angle(z0)+np.angle(z1)):.1f}°)")
# ============================================================
print_header("Decomposition into Rotation and Scaling")
# ============================================================
z = 2 * np.exp(1j * np.pi / 3) # 2e^{iπ/3}
print_step(1, "The matrix representation M(z)")
Mz = M(z)
print(f" z = 2·e^{{iπ/3}} = {z:.4f}")
print(f" M(z) =\n{Mz}")
print_step(2, "Action on the standard basis")
e0 = np.array([1., 0.])
result = Mz @ e0
print(f" M(z)·e_0 = {np.round(result, 4)}")
print(f" length = {np.linalg.norm(result):.4f} (expected 2.0)")
print(f" angle = {np.degrees(np.arctan2(result[1], result[0])):.1f}° (expected 60°)")
print_step(3, "Comparison with the rotation + scaling decomposition")
r, theta = abs(z), np.angle(z)
R = np.array([[np.cos(theta), -np.sin(theta)],
[np.sin(theta), np.cos(theta)]])
compare_print("r·R_θ", r * R, "M(z)")
print(f" equal? {np.allclose(Mz, r * R)}")
# ============================================================
print_header("The Matrix Representation of the Complex Conjugate")
# ============================================================
z = 3 + 4j
zbar = z.conjugate()
print_step(1, "M(z) and M(z̄)")
print(f" M(z) =\n{M(z)}")
print(f" M(z̄) =\n{M(zbar)}")
compare_print("M(z̄) vs M(z)^T", M(zbar), M(z).T)
print(f" M(z̄) == M(z)^T? {np.allclose(M(zbar), M(z).T)}")
print_step(2, "M(z)·M(z̄) = |z|²·I")
product = M(z) @ M(zbar)
expected = abs(z)**2 * np.eye(2)
compare_print("M(z)·M(z̄)", product, f"{abs(z)**2:.0f}·I")
print(f" equal? {np.allclose(product, expected)}")
print_step(3, "Geometric check: the arguments cancel")
print(f" arg(z) = {np.degrees(np.angle(z)):.2f}°")
print(f" arg(z̄) = {np.degrees(np.angle(zbar)):.2f}° (they cancel → total 0°)")
print(f" |z|·|z̄| = {abs(z)*abs(zbar):.4f} (= |z|² = {abs(z)**2:.4f})")
# ============================================================
print_header("Matrix Realizations of Complex Functions")
# ============================================================
print_step(1, "Checking complex division")
z0, z1 = 3+2j, 1-1j
w = z0 / z1
Mw_direct = M(w)
Mw_matrix = M(z0) @ np.linalg.inv(M(z1))
compare_print("M(z0/z1) (directly)", Mw_direct, "M(z0)·M(z1)^{-1}")
print(f" M(z0)·M(z1)^{{-1}} =\n{np.round(Mw_matrix, 4)}")
print(f" equal? {np.allclose(Mw_direct, Mw_matrix)}")
print_step(2, "Powers of complex numbers: (1+i)^8")
z = 1+1j
for n in [2, 4, 8]:
Mpow_direct = M(z**n)
Mpow_matrix = np.linalg.matrix_power(M(z), n)
ok = np.allclose(Mpow_direct, Mpow_matrix)
print(f" n={n}: M(z^n)==M(z)^n? {ok} z^n={z**n:.2f}")
print_step(3, "Matrix check of z²=-1")
for z_sol, name in [(1j, 'i'), (-1j, '-i')]:
mat = np.linalg.matrix_power(M(z_sol), 2)
print(f" M({name})² =\n{mat} (should be -I? {np.allclose(mat, -np.eye(2))})")
# ============================================================
print_header("Matrix Analysis of Mappings of the Complex Plane")
# ============================================================
print_step(1, "Matrices of the three mappings")
# f(z) = (1+i)z
print(" matrix of f(z) = (1+i)z:")
print(M(1+1j))
# f(z) = z̄ (reflection)
M_conj = np.array([[1, 0], [0, -1]])
print("\n matrix of f(z) = z̄ (reflection):")
print(M_conj)
# f(z) = iz + 1 is affine
print("\n f(z) = iz+1 is an affine mapping; matrix of its linear part:")
print(M(1j))
print_step(2, "Rotate by 45°, then enlarge by √2: two methods")
# complex multiplication
z_op = np.sqrt(2) * np.exp(1j * np.pi / 4) # = 1+i
print(f" complex multiplier = {z_op:.4f} (≈ 1+i = {1+1j})")
M_complex = M(z_op)
# composition of matrices
theta = np.pi / 4
R45 = np.array([[np.cos(theta), -np.sin(theta)],
[np.sin(theta), np.cos(theta)]])
M_matrix = np.sqrt(2) * R45
compare_print("M(1+i) (complex method)", M_complex, "√2·R₄₅° (matrix method)")
print(f" √2·R₄₅° =\n{np.round(M_matrix, 4)}")
print(f" equal? {np.allclose(M_complex, M_matrix)}")
print_step(3, "Fixed-point analysis")
A = M(1+1j) - np.eye(2)
print(f" M(1+i) - I =\n{A}")
print(f" det(M(1+i) - I) = {np.linalg.det(A):.4f} (nonzero → unique fixed point 0)")
# ============================================================
print_header("The Correspondence between Vectors and Skew-Symmetric Matrices")
# ============================================================
u0 = np.array([1., 0., 0.])
u1 = np.array([0., 1., 0.])
u2 = np.array([0., 0., 1.])
v = np.array([2., -1., 3.])
print_step(1, "Compute the skew-symmetric matrix of each vector")
for name, vec in [('u0', u0), ('u1', u1), ('u2', u2), ('v=(2,-1,3)', v)]:
print(f" [{name}]× =\n{skew(vec)}\n")
print_step(2, "Verify skew-symmetry: [v]× + [v]×^T = 0")
Sv = skew(v)
result = Sv + Sv.T
print(f" [v]× + [v]×^T =\n{result}")
print(f" zero matrix? {np.allclose(result, 0)}")
print_step(3, "Verify linearity")
Su0_Su1 = skew(u0) + skew(u1)
S_u0_u1 = skew(u0 + u1)
print(f" [u0+u1]× == [u0]×+[u1]×? {np.allclose(S_u0_u1, Su0_Su1)}")
compare_print("[2v]× vs 2[v]×", skew(2*v), 2*skew(v))
print(f" [2v]× == 2[v]×? {np.allclose(skew(2*v), 2*skew(v))}")
# ============================================================
print_header("The Relation between Matrix-Vector Multiplication and the Cross Product")
# ============================================================
pairs = [
(np.array([1.,0.,0.]), np.array([0.,1.,0.])),
(np.array([1.,2.,3.]), np.array([4.,5.,6.])),
(np.array([2.,-1.,3.]), np.array([1.,1.,-2.])),
]
for i, (u, v) in enumerate(pairs):
print_step(i+1, f"u={u}, v={v}")
mat_result = skew(u) @ v
cross_result = np.cross(u, v)
print(f" [u]×·v = {mat_result}")
print(f" u × v = {cross_result}")
print(f" equal? {np.allclose(mat_result, cross_result)}")
# ============================================================
print_header("The Geometric Meaning of Skew-Symmetric Matrices")
# ============================================================
u = np.array([0., 0., 1.])
Su = skew(u)
print_step(1, "[u]×·u = 0 (null space)")
print(f" [u]×·u = {Su @ u} (should be the zero vector)")
print_step(2, "Images of the standard basis")
for i, e in enumerate([np.eye(3)[:,j] for j in range(3)]):
print(f" [u]×·e_{i} = {Su @ e}")
print_step(3, "Determinant and comparison")
det_Su = np.linalg.det(Su)
print(f" det([u]×) = {det_Su:.4f} (singular matrix; the mapping is not invertible)")
# compare with the matrix of the rotation by 90° about the z axis
Rz90 = np.array([[0,-1,0],[1,0,0],[0,0,1]], dtype=float)
print(f"\n rotation by 90° about the z axis:\n{Rz90}")
print(f" [u]×:\n{Su}")
print(f" they differ in entry (2,2): the rotation matrix keeps the z axis, while [u]× collapses it")
# ============================================================
print_header("Matrix Commutators and the Cross Product")
# ============================================================
def commutator(A, B):
return A @ B - B @ A
pairs = [
(np.array([1.,0.,0.]), np.array([0.,1.,0.])),
(np.array([1.,1.,0.]), np.array([0.,1.,1.])),
]
for i, (u, v) in enumerate(pairs):
print_step(i+1, f"u={u}, v={v}")
Su, Sv = skew(u), skew(v)
comm = commutator(Su, Sv)
cross_skew = skew(np.cross(u, v))
print(f" [[u]×,[v]×] =\n{comm}")
print(f" [u×v]× =\n{cross_skew}")
print(f" equal? {np.allclose(comm, cross_skew)}")
# ============================================================
print_header("Rodrigues' Rotation Formula")
# ============================================================
def rodrigues(u, theta):
"""Rotation by theta radians about the unit axis u (Rodrigues' formula)"""
K = skew(u / np.linalg.norm(u))
return np.eye(3) + np.sin(theta)*K + (1-np.cos(theta))*(K@K)
print_step(1, "Check: rotation by 90° about the z axis")
u_z = np.array([0., 0., 1.])
K = skew(u_z)
K2 = K @ K
R90 = rodrigues(u_z, np.pi/2)
print(f" K² =\n{K2}")
print(f" R(z,90°) =\n{np.round(R90, 4)}")
standard_Rz90 = np.array([[0,-1,0],[1,0,0],[0,0,1]], float)
print(f" = standard matrix of the rotation by 90° about the z axis? {np.allclose(R90, standard_Rz90)}")
print_step(2, "Small-angle approximation (θ=0.1 rad)")
theta_s = 0.1
R_exact = rodrigues(u_z, theta_s)
R_order1 = np.eye(3) + theta_s * K
R_order2 = np.eye(3) + theta_s * K + (theta_s**2/2) * K2
print(f" exact:\n{np.round(R_exact, 6)}")
print(f" first-order approximation error: {np.max(np.abs(R_exact - R_order1)):.2e}")
print(f" second-order approximation error: {np.max(np.abs(R_exact - R_order2)):.2e}")
print_step(3, "Rotation by 60° about the axis (1,1,1)/√3")
u_111 = np.ones(3) / np.sqrt(3)
R60 = rodrigues(u_111, np.pi/3)
print(f" R =\n{np.round(R60, 4)}")
v = np.array([1., 0., 0.])
print(f" R·v = {np.round(R60 @ v, 4)}")
print_step(4, "Verify the key properties")
print(f" R·u = {np.round(R60 @ u_111, 4)} (should be ≈ u = {np.round(u_111,4)})")
print(f" R^T·R = I? {np.allclose(R60.T @ R60, np.eye(3))}")
print(f" det(R) = {np.linalg.det(R60):.6f} (should be 1)")
print_step(5, "Numerical check of the differential equation (ω=(0,0,2), Δt=0.01)")
omega = np.array([0., 0., 2.])
Kw = skew(omega)
dt = 0.01
R0 = np.eye(3)
R_approx = R0 + dt * Kw @ R0
R_exact_dt = rodrigues(u_z, 2 * dt) # rotation about the z axis by ω·dt = 2*0.01 rad
print(f" error of the approximate solution: {np.max(np.abs(R_approx - R_exact_dt)):.2e}")
Determinants¶
# ============================================================
print_header("Determinants")
# ============================================================
print_step(1, "Exercise 62: basic determinant computations")
mats = [
np.array([[3,5],[2,7]]),
np.array([[-4,6],[2,-3]]),
np.array([[-3,0],[79,4]]),
np.array([[1,3,-9],[0,4,23],[0,0,9]]),
np.array([[3,-7,9],[1,-1,3],[-2,2,6]]),
np.array([[1,2,3],[6,6,6],[7,8,9]]),
]
for i, m in enumerate(mats):
print(f" det_{i+1} = {np.linalg.det(m):.4f}")
print_step(2, "Exercise 63: det(3A) vs 3·det(A) (scale factor = cⁿ)")
A = np.array([[1,4],[2,5]])
compare_print("det(3A)", f"{np.linalg.det(3*A):.4f}",
f"3²·det(A) = {9*np.linalg.det(A):.4f}")
print_step(3, "Exercise 64: determinant of a product, det(AB) = det(A)·det(B)")
A = np.array([[2,1],[3,-1]]); B = np.array([[1,4],[0,2]])
compare_print("det(AB)", f"{np.linalg.det(A@B):.4f}",
f"det(A)·det(B) = {np.linalg.det(A)*np.linalg.det(B):.4f}")
print_step(4, "Exercise 65: det(C) = det(C^T)")
C = np.array([[1,0,2],[-1,3,1],[2,1,0]])
compare_print("det(C) vs det(C^T)",
f"{np.linalg.det(C):.4f}", f"{np.linalg.det(C.T):.4f}")
print_step(5, "Exercise 66: area of a parallelogram = |det[u,v]|")
u, v = np.array([1,3]), np.array([4,-2])
area = abs(np.linalg.det(np.column_stack([u,v])))
print(f" original area = {area:.4f}")
compare_print("area spanned by 8u, 5v", 40*area, "40 × original area")
print_step(6, "Exercise 67: volume of a parallelepiped = |det[a,b,c]|")
volume_matrix = np.column_stack([np.array([2,0,0]), np.array([0,4,0]), np.array([0,0,1])])
compare_print("volume |det|", f"{abs(np.linalg.det(volume_matrix)):.4f}", "8 (axis-aligned cuboid)")
print_step(7, "Exercise 71: cross product and scalar triple product, a·(b×c) = det[a,b,c]")
b, c = np.array([3,0,4]), np.array([5,6,0])
bxc = np.cross(b, c)
a = np.array([1,2,0])
volume_matrix = np.column_stack([a,b,c])
print(" b×c =", bxc)
compare_print("a·(b×c) vs det[a,b,c]",
f"{np.dot(a,bxc):.4f}", f"{np.linalg.det(volume_matrix):.4f}")
Systems of Linear Equations¶
# ============================================================
print_header("Systems of Linear Equations")
# ============================================================
print_step(1, "Exercise 72: 2×2 system (elimination)")
A = np.array([[2,1],[1,1]]); b = np.array([5,3])
x = np.linalg.solve(A, b)
print(" solution x =", x)
compare_print("check Ax", A@x, b)
print_step(2, "Exercise 73: 2×2 system (inverse-matrix method)")
A = np.array([[3,-1],[1,2]]); b = np.array([7,4])
x = np.linalg.solve(A, b)
print(" solution x =", x)
compare_print("check Ax", A@x, b)
print_step(3, "Exercise 74: 3×3 system (with the inverse matrix)")
A = np.array([[1,1,1],[1,2,1],[1,1,2]]); b = np.array([6,8,9])
x = np.linalg.solve(A, b)
print(" solution x =", x)
compare_print("check Ax", A@x, b)
print(" A⁻¹ ="); print(np.linalg.inv(A))
print_step(4, "Exercise 75: 3×3 system (inverse-matrix method)")
A = np.array([[2,1,0],[1,1,1],[0,1,3]]); b = np.array([5,5,4])
x = np.linalg.solve(A, b)
print(" solution x =", x)
compare_print("check Ax", A@x, b)