Structure of each exercise:
Problem + hint (MyST
::::{exercise}format)An expandable complete solution (
:::{solution} :class: dropdown)Python numerical verification (with execution output)
Note. Some verification code blocks are shared by several exercises (for example, orthogonal bases and the generalized Pythagorean theorem).
import numpy as np
from itertools import combinations
np.set_printoptions(precision=4, suppress=True, linewidth=100)
# ── 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(u, v):
"""Projection vector of v onto the direction of u"""
return np.dot(u, v) / np.dot(u, u) * u
def para_area(u, v):
"""Area of the parallelogram spanned by u, v"""
return np.sqrt(np.dot(u,u)*np.dot(v,v) - np.dot(u,v)**2)
def triple(u, v, w):
"""Signed volume D(u,v,w) = u·(v×w)"""
return np.dot(u, np.cross(v, w))
Basic Vector Operations¶
# ============================================================
print_header("Exercise 1 | Computing a Linear Combination")
# ============================================================
print_step(1, "Define the vectors a, b, c ∈ ℝ⁴")
a = np.array([2, 0, -1, 3], float)
b = np.array([1, 4, 2, -1], float)
c = np.array([-1, 2, 0, 1], float)
print("a =", a)
print("b =", b)
print("c =", c)
print_step(2, "Compute 2a - 3b + c")
result = 2*a - 3*b + c
print("2a - 3b + c =", result)
compare_print("‖2a - 3b + c‖", f"{np.linalg.norm(result):.6f}", f"2√66 = {2*np.sqrt(66):.6f}")
# ============================================================
print_header("Exercise 2 | The Average of Cyclically Shifted Vectors (check with n=4)")
# ============================================================
print_step(1, "Generate the cyclically shifted vectors a_0, …, a_{n-1}")
n = 4
vecs = [np.roll(np.arange(1, n+1, dtype=float), -i) for i in range(n)]
for i, v in enumerate(vecs):
print(f" a_{i} =", v)
print_step(2, "Compute the average vector A")
A_avg = np.mean(vecs, axis=0)
compare_print("average vector A", A_avg, f"(n+1)/2 times the all-ones vector = {(n+1)/2} * [1,1,1,1]")
print_step(3, "Check that all the vectors are equidistant from A")
dists = [np.linalg.norm(A_avg - v) for v in vecs]
print(" ‖A - a_i‖ =", [f"{d:.4f}" for d in dists])
compare_print("theoretical value √(n(n²−1)/12)", f"{dists[0]:.6f}", f"{np.sqrt(n*(n**2-1)/12):.6f}")
# ============================================================
print_header("Exercise 3 | The Fourth Vertex of a Parallelogram")
# ============================================================
print_step(1, "Define the three vertices A, B, C ∈ ℝ³")
A = np.array([2, -1, 3], float)
B = np.array([1, 4, -2], float)
C = np.array([-2, 6, 1], float)
print_step(2, "Compute the three possible fourth vertices D (by choice of diagonals)")
D0 = A + C - B # diagonal AC
D1 = A + B - C # diagonal AB
D2 = B + C - A # diagonal BC
for label, D in zip(["D0 (diagonal AC)", "D1 (diagonal AB)", "D2 (diagonal BC)"], [D0, D1, D2]):
print(f" {label}: D =", D)
print_step(3, "Check the side lengths in the case D0 (|AB| = |CD0|)")
print(" |AB| =", np.linalg.norm(B-A))
print(" |CD0|=", np.linalg.norm(D0-C))
print(" equal?", np.isclose(np.linalg.norm(B-A), np.linalg.norm(D0-C)))
# ============================================================
print_header("Exercise 4 | Existence of Linear Combinations")
# ============================================================
print_step(1, "(a) s(1,2)+t(3,−1)=(4,1) — unique solution")
M = np.array([[1,3],[2,-1]], float)
st = np.linalg.solve(M, [4,1])
print(" [s, t] =", st)
compare_print("check M @ [s,t]", M @ st, "[4, 1]")
print_step(2, "(b) x = 3u — infinitely many solutions")
u, x = np.array([1,2,3],float), np.array([3,6,9],float)
print(" x = 3u?", np.allclose(3*u, x), "(s+2t=3, infinitely many solutions)")
print_step(3, "(c) check that there is no solution")
u = np.array([1,0,2,-1],float); v = np.array([0,1,-1,3],float)
x = np.array([2,3,0,8],float)
print(" s=2,t=3 → su+tv =", 2*u+3*v)
print(" target x =", x, "→ no solution")
print_step(4, "(d) least-squares residual → no solution")
u = np.array([1,2,0,-1],float); v = np.array([2,4,1,0],float)
x = np.array([1,2,2,0],float)
coef, *_ = np.linalg.lstsq(np.column_stack([u,v]), x, rcond=None)
residual = np.linalg.norm(np.column_stack([u,v]) @ coef - x)
compare_print("residual ‖Ac−x‖", f"{residual:.6f}", "> 0 → no solution")
# ============================================================
print_header("Exercise 5 | Rewriting Systems of Equations as Linear Combinations of Vectors")
# ============================================================
print_step(1, "(a) x₀(1,2)+x₁(2,−1) = (3,1)")
A_mat = np.array([[1,2],[2,-1]], float)
x_sol = np.linalg.solve(A_mat, [3,1])
print(" [x₀, x₁] =", x_sol)
compare_print("check A·x", A_mat @ x_sol, "[3, 1]")
print_step(2, "(b) 3×3 system")
A_mat = np.array([[1,1,1],[2,0,3],[-1,2,1]], float)
x_sol = np.linalg.lstsq(A_mat, [4,5,1], rcond=None)[0]
print(" solution =", x_sol)
compare_print("check A·x", A_mat @ x_sol, "[4, 5, 1]")
Parametric Equations of Lines and Division Points¶
# ============================================================
print_header("Exercise 6 | Division Points of a Segment")
# ============================================================
print_step(1, "(1) ℝ²: A=(1,2), B=(5,6), internal division 2:1")
A, B = np.array([1,2],float), np.array([5,6],float)
P = (1*A + 2*B) / 3
print(" P =", P)
print_step(2, "(2) ℝ²: A=(2,−1), B=(6,3), external division 3:1")
A, B = np.array([2,-1],float), np.array([6,3],float)
P = (-1*A + 3*B) / 2
print(" P =", P)
print_step(3, "(3a) ℝ³: A=(1,0,2), B=(4,3,−1), internal division 3:5")
A, B = np.array([1,0,2],float), np.array([4,3,-1],float)
P = (5*A + 3*B) / 8
print(" P =", P)
print_step(4, "(3b) external division point Q: QA:AB = 5:2")
Q = (5*B - 3*A) / 2
print(" Q =", Q)
t_A = np.dot(A-Q, B-Q) / np.dot(B-Q, B-Q)
ratio = np.linalg.norm(Q-A) / np.linalg.norm(A-B)
assert np.allclose(A, Q + t_A*(B-Q))
assert np.isclose(ratio, 5/2) and not (0 <= t_A <= 1)
print(" QA/AB =", ratio, "; parameter of A on the line QB =", t_A, "(external division)")
# ============================================================
print_header("Exercise 7 | Division Points on a Parametrized Line")
# ============================================================
print_step(1, "(1) ℝ⁴: internal division 1:4, t = 1/5")
A = np.array([0,-10,1,-3],float); B = np.array([5,0,6,7],float)
P = A + (1/5)*(B - A)
print(" P =", P)
print_step(2, "(2) ℝ⁴: external division 4:3, t = 4")
A = np.array([2,1,-1,0],float); B = np.array([6,3,2,4],float)
Q = A + 4*(B - A)
print(" Q =", Q)
print_step(3, "(3) internal division 1:k: behavior as k approaches its extremes")
for k in [0.1, 1, 10, 1000]:
t = 1/(1+k)
A_ = np.ones(3); B_ = 2*np.ones(3)
P = A_ + t*(B_ - A_)
print(f" k={k:6}: P[0] = {P[0]:.4f} (theory: (2+k)/(1+k) = {(2+k)/(1+k):.4f})")
print(" k→∞: approaches A=(1,…,1); k→0: approaches B=(2,…,2)")
The Centroid of a Triangle¶
# ============================================================
print_header("Exercise 8 | Discovering the Centroid in ℝ⁴")
# ============================================================
print_step(1, "Define the three vertices and compute the midpoints of the sides")
A = np.array([1,2,0,-1],float)
B = np.array([3,0,2, 1],float)
C = np.array([-1,4,1, 3],float)
MC = (A+B)/2; MA = (B+C)/2; MB = (A+C)/2
print(" MA =", MA, " MB =", MB, " MC =", MC)
print_step(2, "Check that MC is the midpoint of AB (A and B are equidistant from MC)")
compare_print("|A−MC| vs |B−MC|",
f"{np.linalg.norm(A-MC):.6f}",
f"|B−MC| = {np.linalg.norm(B-MC):.6f} (should be equal)")
print_step(3, "Centroid G = (A+B+C)/3")
G = (A+B+C)/3
print(" G =", G)
print(" ratio of the distances to the centroid |A−G|/|MA−G| =",
np.linalg.norm(A-G)/np.linalg.norm(MA-G))
compare_print("|A−G|/|MA−G|",
f"{np.linalg.norm(A-G)/np.linalg.norm(MA-G):.6f}", "2")
print_step(4, "The three points C, G, MC are collinear: λ = 2/3")
diff = MC - C
lam = np.dot(G-C, diff) / np.dot(diff, diff)
print(f" λ = {lam:.6f} error = {np.linalg.norm((G-C) - lam*(MC-C)):.2e}")
# ============================================================
print_header("Exercise 9 | General Proof for the Centroid in ℝⁿ (numerical check)")
# ============================================================
print_step(1, "Random checks of collinearity and the ratio for n = 3, 5, 10")
rng = np.random.default_rng(42)
for n in [3, 5, 10]:
A = rng.standard_normal(n); B = rng.standard_normal(n); C = rng.standard_normal(n)
G = (A+B+C)/3
MA = (B+C)/2
err = np.linalg.norm((G-A) - (2/3)*(MA-A))
ratio = np.linalg.norm(A-G) / np.linalg.norm(MA-G)
print(f" n={n:2d}: collinearity error={err:.2e} |A−G|/|MA−G| = {ratio:.6f} (should be 2)")
Inner Products, Norms, and Inequalities¶
# ============================================================
print_header("Exercise 10 | Computing Inner Products")
# ============================================================
print_step(1, "(a) (1,2,−1,3)·(1,0,1,0)")
val_a = np.dot([1,2,-1,3],[1,0,1,0])
compare_print("(a) inner product", val_a, "1·1 + 2·0 + (−1)·1 + 3·0 = 0")
print_step(2, "(b) using linearity v·(u+w)")
u = np.array([66,24,19]); v = np.array([23,-17,12]); w = np.array([-66,-23,-19])
val_b = np.dot(v,u) + np.dot(v,w)
compare_print("(b) v·u + v·w = v·(u+w)", val_b, f"{np.dot(v, u+w)}")
print_step(3, "(c) (a+b)·(a−b) = ‖a‖²−‖b‖²")
a_ = np.array([3,4]); b_ = np.array([5,12])
val_c = np.dot(a_+b_, a_-b_)
compare_print("(c) difference-of-squares formula", val_c, f"‖a‖²−‖b‖² = {np.dot(a_,a_)-np.dot(b_,b_)}")
print_step(4, "(d) scalars can be factored out")
val_d = np.dot(5*np.array([1/3,-1/3,2/3]), 3*np.array([1/5,1/5,1/5]))
compare_print("(d) 5·3·(1/3,…)·(1/5,…)", val_d, "5·3·(1/15−1/15+2/15)")
# ============================================================
print_header("Exercise 11 | Unit Vectors")
# ============================================================
print_step(1, "Define u = (1,1,2,−1) and compute ‖u‖")
u = np.array([1,1,2,-1], float)
norm_u = np.linalg.norm(u)
compare_print("‖u‖", f"{norm_u:.6f}", f"√7 = {np.sqrt(7):.6f}")
print_step(2, "Compute the unit vector û = u / ‖u‖")
u_hat = u / norm_u
print(" û =", u_hat)
compare_print("‖û‖ (should be 1)", f"{np.linalg.norm(u_hat):.6f}", "1")
# ============================================================
print_header("Exercise 12 | Cauchy–Schwarz and the Triangle Inequality")
# ============================================================
print_step(1, "Define the vectors u, v ∈ ℝ⁴")
u = np.array([1, 2, 1,-1], float)
v = np.array([2,-1, 0, 3], float)
print(" u =", u, " v =", v)
print_step(2, "Check the Cauchy–Schwarz inequality |u·v| ≤ ‖u‖‖v‖")
cs_lhs = abs(np.dot(u,v))
cs_rhs = np.linalg.norm(u)*np.linalg.norm(v)
compare_print("|u·v|", f"{cs_lhs:.4f}", f"‖u‖‖v‖ = {cs_rhs:.4f} ✓ {cs_lhs<=cs_rhs}")
print_step(3, "Check the triangle inequality ‖u+v‖ ≤ ‖u‖+‖v‖")
tri_lhs = np.linalg.norm(u+v)
tri_rhs = np.linalg.norm(u)+np.linalg.norm(v)
compare_print("‖u+v‖", f"{tri_lhs:.4f}", f"‖u‖+‖v‖ = {tri_rhs:.4f} ✓ {tri_lhs<=tri_rhs}")
# ============================================================
print_header("Exercise 13 | Characterizing the Zero Vector by Inner Products")
# ============================================================
print_step(1, "Nonzero vector: u·u = ‖u‖² > 0")
rng = np.random.default_rng(1)
u = rng.standard_normal(5)
compare_print("u·u = ‖u‖²", f"{np.dot(u,u):.6f}", "> 0 (nonzero vector)")
print_step(2, "Zero vector: 0·0 = 0, confirming uniqueness")
compare_print("0·0", np.dot(np.zeros(5), np.zeros(5)), "0 (necessary and sufficient condition: u=0)")
# ============================================================
print_header("Exercise 14 | The Law of Cosines and the Inner Product")
# ============================================================
print_step(1, "Define the vectors A, B ∈ ℝ⁴")
A = np.array([1,2,3,4],float); B = np.array([1,-1,0,2],float)
print_step(2, "Check ‖A−B‖² = ‖A‖² + ‖B‖² − 2A·B")
lhs = np.linalg.norm(A-B)**2
rhs = np.dot(A,A) + np.dot(B,B) - 2*np.dot(A,B)
compare_print("‖A−B‖²", f"{lhs:.4f}", f"‖A‖²+‖B‖²−2A·B = {rhs:.4f} ✓ {np.isclose(lhs,rhs)}")
print_step(3, "Compute the angle cosθ (Cauchy–Schwarz guarantees ∈[−1,1])")
cos_t = np.dot(A,B)/(np.linalg.norm(A)*np.linalg.norm(B))
compare_print("cosθ", f"{cos_t:.6f}", "∈ [−1, 1]")
Orthogonal Projection¶
# ============================================================
print_header("Exercise 15 | Computing Projection Vectors")
# ============================================================
print_step(1, "Compute the projection vector and the scalar projection for each part")
cases = [
("(a) v=(3,4), u=(1,1)",
np.array([3.,4.]), np.array([1.,1.])),
("(b) v=(−2,1), u=(3/5,4/5)",
np.array([-2.,1.]), np.array([3/5,4/5])),
("(c) u=(1,1,1), v=(3,0,−3)",
np.array([3.,0.,-3.]), np.array([1.,1.,1.])),
("(d) u=(½,−½,½,−½), v=(0,6,8,0)",
np.array([0.,6.,8.,0.]), np.array([.5,-.5,.5,-.5])),
]
for label, v, u in cases:
p = proj(u, v)
scalar = np.dot(u,v)/np.linalg.norm(u)
print(f" {label}")
print(f" projection vector = {p} scalar projection = {scalar:.4f}")
# ============================================================
print_header("Exercise 16 | Projection onto a Line through the Origin and Distance")
# ============================================================
print_step(1, "(1) v=(3,4), line direction u=(1,1)")
v, u = np.array([3.,4.]), np.array([1.,1.])
r = v - proj(u,v)
compare_print("distance ‖v−proj‖", f"{np.linalg.norm(r):.6f}", f"1/√2 = {1/np.sqrt(2):.6f}")
print_step(2, "(2) v=(1,2,−3), line direction u=(1,0,1)")
v, u = np.array([1.,2.,-3.]), np.array([1.,0.,1.])
r = v - proj(u,v)
compare_print("residual·u (should be 0)", f"{np.dot(r,u):.2e}", "0")
print_step(3, "(3) v=(1,2,1,0) ∈ ℝ⁴, direction u=(1,0,1,0)")
v, u = np.array([1.,2.,1.,0.]), np.array([1.,0.,1.,0.])
r = v - proj(u,v)
compare_print("residual·u (should be 0)", f"{np.dot(r,u):.2e}", "0")
# ============================================================
print_header("Exercise 17 | General Properties of Projection")
# ============================================================
print_step(1, "Randomly generate u, v ∈ ℝ⁴ and compute the projection and the residual")
rng = np.random.default_rng(7)
u = rng.standard_normal(4); v = rng.standard_normal(4)
p = proj(u, v); r = v - p
print_step(2, "Check p ⊥ r")
compare_print("p·r (should be ≈0)", f"{np.dot(p,r):.2e}", "0")
print_step(3, "Check the distance formula ‖v−proj_u(v)‖")
dist1 = np.sqrt(np.dot(u,u)*np.dot(v,v) - np.dot(u,v)**2) / np.linalg.norm(u)
dist2 = np.linalg.norm(r)
compare_print("value of the distance formula", f"{dist1:.6f}", f"direct computation ‖r‖ = {dist2:.6f} ✓ {np.isclose(dist1,dist2)}")
# ============================================================
print_header("Exercise 18 | Distance from a Point to a Line")
# ============================================================
print_step(1, "(1) (2,3) to the line through the origin with direction (1,−1)")
p = np.array([2.,3.]); u = np.array([1.,-1.])
compare_print("distance", f"{np.linalg.norm(p - proj(u,p)):.6f}", f"5√2/2 = {5*np.sqrt(2)/2:.6f}")
print_step(2, "(2) (3,1) to the line through (−1,2) with direction (1,1)")
p = np.array([3.,1.]) - np.array([-1.,2.]); u = np.array([1.,1.])
compare_print("distance", f"{np.linalg.norm(p - proj(u,p)):.6f}", f"5√2/2 = {5*np.sqrt(2)/2:.6f}")
print_step(3, "(3) (1,2,3) to the line through the origin with direction (1,0,1)")
p = np.array([1.,2.,3.]); u = np.array([1.,0.,1.])
compare_print("distance", f"{np.linalg.norm(p - proj(u,p)):.6f}", f"√6 = {np.sqrt(6):.6f}")
print_step(4, "(4) (2,1,0) to the line {x+y+z=0, y=1}")
base = np.array([-1.,1.,0.]); u = np.array([1.,0.,-1.])
p = np.array([2.,1.,0.]) - base
compare_print("distance", f"{np.linalg.norm(p - proj(u,p)):.6f}", f"3√2/2 = {3*np.sqrt(2)/2:.6f}")
print_step(5, "(5) P=(1,1,1,1) ∈ ℝ⁴, direction (1,0,0,1)")
p = np.array([1.,1.,1.,1.]); u = np.array([1.,0.,0.,1.])
compare_print("distance", f"{np.linalg.norm(p - proj(u,p)):.6f}", f"√2 = {np.sqrt(2):.6f}")
# ============================================================
print_header("Exercise 19 | Projection onto a Plane")
# ============================================================
print_step(1, "ℝ³: Σ = span{(1,−1,0),(0,1,−1)}, v=(0,0,4)")
u0 = np.array([1.,-1.,0.]); u1 = np.array([0.,1.,-1.])
n = np.cross(u0, u1)
v = np.array([0.,0.,4.])
proj_sigma = v - proj(n, v)
print(" normal vector n =", n)
print(" proj_Σ(v) =", proj_sigma)
compare_print("residual·n (should be 0)", f"{np.dot(proj_sigma, n):.2e}", "0")
print_step(2, "ℝ³: distance from v=(3,−1,2) to the plane x+2y−z=0")
n = np.array([1.,2.,-1.]); v = np.array([3.,-1.,2.])
ps = v - proj(n,v)
compare_print("distance ‖v−proj_Σ‖", f"{np.linalg.norm(v-ps):.6f}", "|n·v|/‖n‖")
print_step(3, "ℝ⁴: Σ = span{(1,0,1,0),(0,1,0,1)}, v=(1,1,1,1)")
u0 = np.array([1.,0.,1.,0.]); u1 = np.array([0.,1.,0.,1.])
v = np.array([1.,1.,1.,1.])
G_mat = np.array([[np.dot(u0,u0), np.dot(u0,u1)],
[np.dot(u1,u0), np.dot(u1,u1)]])
b_ = np.array([np.dot(v,u0), np.dot(v,u1)])
ab = np.linalg.solve(G_mat, b_)
ps4 = ab[0]*u0 + ab[1]*u1
print(" proj_Σ(v) =", ps4)
compare_print("residual ⊥ u0,u1",
f"‖r·u0‖={abs(np.dot(v-ps4,u0)):.2e}, ‖r·u1‖={abs(np.dot(v-ps4,u1)):.2e}", "0, 0")
# ============================================================
print_header("Exercise 20 | Distance from a Point to a Plane")
# ============================================================
print_step(1, "(1) P=(2,1,3) to the plane x−2y+2z=0 given by its normal vector")
P = np.array([2.,1.,3.]); n = np.array([1.,-2.,2.])
compare_print("distance |n·P|/‖n‖", f"{abs(np.dot(P,n))/np.linalg.norm(n):.6f}", "2")
print_step(2, "(2) P=(1,2,1) to span{(0,1,1),(0,1,−1)}")
u0=np.array([0.,1.,1.]); u1=np.array([0.,1.,-1.]); P=np.array([1.,2.,1.])
n = np.cross(u0,u1)
compare_print("distance", f"{abs(np.dot(P,n))/np.linalg.norm(n):.6f}", "1")
print_step(3, "(3) the origin to the affine plane (2,1,−1)+s(1,1,1)+t(1,−1,0)")
u0=np.array([1.,1.,1.]); u1=np.array([1.,-1.,0.]); a=np.array([2.,1.,-1.])
n = np.cross(u0,u1)
dist = abs(np.dot(np.zeros(3)-a, n))/np.linalg.norm(n)
compare_print("distance", f"{dist:.6f}", f"5/√6 = {5/np.sqrt(6):.6f}")
print_step(4, "(4) P=(2,1,1,1) ∈ ℝ⁴ to span{(1,1,0,0),(0,0,1,1)}")
u0=np.array([1.,1.,0.,0.]); u1=np.array([0.,0.,1.,1.]); P=np.array([2.,1.,1.,1.])
G_mat=np.array([[np.dot(u0,u0),np.dot(u0,u1)],[np.dot(u1,u0),np.dot(u1,u1)]])
ab=np.linalg.solve(G_mat,[np.dot(P,u0),np.dot(P,u1)])
ps=ab[0]*u0+ab[1]*u1
compare_print("distance", f"{np.linalg.norm(P-ps):.6f}", f"√2/2 = {np.sqrt(2)/2:.6f}")
Orthogonal Bases¶
# ============================================================
print_header("Exercise 21–23 | Orthogonal Bases")
# ============================================================
print_step(1, "ℝ²: expansion of v=(3,1) in the Hadamard orthogonal basis")
u0=np.array([1,1])/np.sqrt(2); u1_=np.array([1,-1])/np.sqrt(2)
v=np.array([3.,1.])
c0,c1=np.dot(v,u0),np.dot(v,u1_)
print(f" c₀ = {c0:.4f}, c₁ = {c1:.4f}")
compare_print("reconstruction error", np.allclose(c0*u0+c1*u1_,v), "True")
compare_print("Parseval's identity", np.isclose(c0**2+c1**2, np.dot(v,v)), "True")
print_step(2, "ℝ³: check of the orthonormal matrix U")
U=np.array([[2/3,2/3,1/3],[1/3,-2/3,2/3],[-2/3,1/3,2/3]])
print(" UU^T ="); print(np.round(U@U.T,8))
v=np.array([2.,0.,0.])
c=U@v
compare_print("Parseval (ℝ³)", np.isclose(np.dot(c,c),np.dot(v,v)), "True")
print_step(3, "ℝ⁴: Hadamard matrix (scaled by ½)")
H=np.array([[1,1,1,1],[1,1,-1,-1],[1,-1,1,-1],[1,-1,-1,1]])/2.0
print(" HH^T ="); print(np.round(H@H.T,8))
v=np.array([3.,0.,4.,0.])
c=H@v
compare_print("Parseval (ℝ⁴)", np.isclose(np.dot(c,c),np.dot(v,v)), "True")
The Cross Product¶
# ============================================================
print_header("Exercise 24 | Basic Properties of the Cross Product")
# ============================================================
print_step(1, "Compute u×v and check perpendicularity")
u = np.array([1.,2.,3.]); v = np.array([1.,0.,1.])
w = np.cross(u, v)
print(" u×v =", w)
compare_print("(u×v)·u (should be 0)", f"{np.dot(w,u):.2e}", "0")
compare_print("(u×v)·v (should be 0)", f"{np.dot(w,v):.2e}", "0")
print_step(2, "Check the area formula ‖u×v‖² = ‖u‖²‖v‖²−(u·v)²")
lhs = np.dot(w,w)
rhs = np.dot(u,u)*np.dot(v,v) - np.dot(u,v)**2
compare_print("‖u×v‖²", f"{lhs:.4f}", f"‖u‖²‖v‖²−(u·v)² = {rhs:.4f} ✓ {np.isclose(lhs,rhs)}")
print_step(3, "The area agrees with para_area")
compare_print("‖u×v‖", f"{np.linalg.norm(w):.6f}", f"para_area(u,v) = {para_area(u,v):.6f}")
For each of the following cross product identities, prove it if it is true, and give a counterexample if it is false.
Solution to Exercise 25
1. True. Every component of is .
2. False. Counterexample: , : , . In fact (anticommutativity).
3. True. Expand each component directly: and so on equals . This holds for all three components, so distributivity over addition holds.
4. False. Take and . The left-hand side is ; the right-hand side is . The two sides are not equal, so the cross product is not associative.
5. True. is the signed volume, which has cyclic symmetry: , and exchanging two adjacent vectors changes the sign. ✓.
# ============================================================
print_header("Exercise 25 | Deciding Cross Product Identities")
# ============================================================
u=np.array([1.,0.,0.]); v=np.array([0.,1.,0.]); w=np.array([0.,0.,1.])
print_step(1, "(1) u×u = 0 (alternating property)")
compare_print("u×u", np.cross(u,u), "[0, 0, 0]")
print_step(2, "(2) u×v ≠ v×u (counterexample showing antisymmetry)")
print(" u×v =", np.cross(u,v), " v×u =", np.cross(v,u))
compare_print("u×v = v×u? (should be False)", np.allclose(np.cross(u,v), np.cross(v,u)), "False")
print_step(3, "(3) distributivity u×(v+w) = u×v + u×w")
compare_print("distributivity holds?", np.allclose(np.cross(u,v+w), np.cross(u,v)+np.cross(u,w)), "True")
print_step(4, "(4) the cross product is not associative (counterexample)")
u4 = np.array([1.,0.,0.]); v4 = u4.copy(); w4 = np.array([0.,1.,0.]) # counterexample: u=v=e₀, w=e₁
print(" u×(v×w) =", np.cross(u4,np.cross(v4,w4)))
print(" (u×v)×w =", np.cross(np.cross(u4,v4),w4))
compare_print("associativity holds? (should be False)",
np.allclose(np.cross(u4,np.cross(v4,w4)), np.cross(np.cross(u4,v4),w4)), "False")
print_step(5, "(5) cyclic symmetry: D(u,v,w) = −D(w,v,u)")
compare_print("D(u,v,w) vs −D(w,v,u)",
f"{triple(u,v,w):.4f}", f"{-triple(w,v,u):.4f}")
# ============================================================
print_header("Exercise 26 | Cross Product Computations")
# ============================================================
u=np.array([1.,2.,3.]); v=np.array([3.,2.,1.])
print_step(1, "(1) u×v")
print(" u×v =", np.cross(u,v))
print_step(2, "(2) u×(u+v) (should equal u×v)")
compare_print("u×(u+v)", np.cross(u,u+v), f"u×v = {np.cross(u,v)}")
print_step(3, "(3) u×(u×v) (double cross product, BAC–CAB)")
print(" u×(u×v) =", np.cross(u,np.cross(u,v)))
print_step(4, "(4) v·(u×v) (should be 0)")
compare_print("v·(u×v)", f"{np.dot(v,np.cross(u,v)):.2e}", "0")
Prove the vector triple product formula:
Hint. Expand component 0 directly, then cycle the indices to obtain components 1 and 2; at no point do you need to divide by any inner product.
Solution to Exercise 27
# ============================================================
print_header("Exercise 27 | Checking the BAC–CAB Formula")
# ============================================================
print_step(1, "Randomly generate u, v, w ∈ ℝ³")
rng = np.random.default_rng(0)
u=rng.standard_normal(3); v=rng.standard_normal(3); w=rng.standard_normal(3)
print(" u =", u)
print(" v =", v)
print(" w =", w)
print_step(2, "Check u×(v×w) = (u·w)v − (u·v)w")
lhs = np.cross(u, np.cross(v,w))
rhs = np.dot(u,w)*v - np.dot(u,v)*w
print(" left-hand side u×(v×w) =", lhs)
print(" right-hand side (u·w)v−(u·v)w =", rhs)
compare_print("left-hand side ≈ right-hand side?", np.allclose(lhs,rhs), "True")
In , consider the triangle spanned by linearly independent vectors and , and let . Starting from , prove that the three sides of this triangle satisfy the law of sines.
Hint. Expanding gives ... Then derive the result from the equation .
Solution to Exercise 28
Step 1. Expanding, , so , that is, (anticommutativity, already known).
Step 2. The triangle has vertices ; let . Let denote the angles opposite the sides of lengths , respectively; that is, , , .
Step 3. Using the common area ,
Step 4. Dividing by the product of the three side lengths (all nonzero) and taking reciprocals, we obtain
Three non-collinear points , , in space determine three different parallelograms (there are three choices for the fourth vertex). Use the cross product formula to prove that these three parallelograms all have the same area.
Hint. The three possible fourth vertices are , , , and the corresponding parallelograms are formed from different combinations of side vectors.
Solution to Exercise 29
Let and . For the three parallelograms we may take the adjacent sides , , , respectively. The three cross products are , , in turn, so their norms are equal.
More directly, the areas in the three cases are each , since the area of each parallelogram equals twice the area of the triangle determined by the three points . As there is only one triangle, the three areas are equal, all being .
In , consider , , .
Compute .
Use the formula base areaheight to compute the volume of the parallelepiped spanned by the three vectors.
Hint. The base is the parallelogram spanned by and , with area ; the height is the scalar projection of in the direction of the normal vector, .
Solution to Exercise 30
Step 1. .
.
Step 2. The base area is , the height is , and the volume is .
Check: the volume is ✓.
# ============================================================
print_header("Exercise 30 | Volume of a Parallelepiped")
# ============================================================
print_step(1, "Define the three vectors u, v, w ∈ ℝ³")
u=np.array([1.,1.,1.]); v=np.array([-1.,0.,1.]); w=np.array([0.,0.,2.])
print_step(2, "Compute v×w (normal vector of the base)")
vxw = np.cross(v, w)
print(" v×w =", vxw)
print_step(3, "Compute the volume (base area × height vs scalar triple product)")
p = proj(vxw, u)
vol_geo = np.linalg.norm(vxw)*np.linalg.norm(p)
vol_trip = abs(triple(u,v,w))
compare_print("base area×height", f"{vol_geo:.6f}", f"|u·(v×w)| = {vol_trip:.6f}")
This exercise is in fact a three-dimensional preview of the determinant axioms of Chapter 7: the following five properties (normalization, multilinearity, antisymmetry, cyclic symmetry, and the degeneracy criterion) are exactly the axiomatic framework that Chapter 7 uses to define the determinant. Once you understand them thoroughly in familiar three-dimensional geometry, the abstract definition in Chapter 7 will come with a ready-made geometric anchor.
Let for . Prove:
(linearity in the first argument)
(cyclic symmetry)
are linearly dependent if and only if
Hint. For part 5: linear dependence is equivalent to one vector being expressible as a linear combination of the other two; substitute this and use properties 1–3.
Solution to Exercise 31
1. , so .
2. (by the linearity of the inner product).
3. Expand directly: . After exchanging the first two arguments, the six terms cancel one by one against the original expression, so ; the sign change under exchanging the last two arguments comes directly from the anticommutativity of the cross product.
4. By repeated exchanges using property 3: .
5. () If the vectors are linearly dependent, take a set of relation coefficients that are not all zero, solve for a vector whose coefficient is nonzero, and use the fact that the scalar triple product changes only by a sign under permutations; after reordering we may assume , and then ( whenever a vector is repeated). () If the vectors are linearly independent, then geometrically the parallelepiped has nonzero volume, so .
# ============================================================
print_header("Exercise 31 | Properties of the Signed Volume D(u,v,w)")
# ============================================================
print_step(1, "Baseline check: D(e₀,e₁,e₂) = 1")
e = np.eye(3)
compare_print("D(e₀,e₁,e₂)", triple(e[0],e[1],e[2]), "1")
print_step(2, "Linearity: D(2u+3v, a, b) = 2D(u,a,b) + 3D(v,a,b)")
rng=np.random.default_rng(5)
u=rng.standard_normal(3); v=rng.standard_normal(3)
a=rng.standard_normal(3); b=rng.standard_normal(3)
lhs = triple(2*u+3*v,a,b)
rhs = 2*triple(u,a,b)+3*triple(v,a,b)
compare_print("linearity", f"{lhs:.6f}", f"2D+3D = {rhs:.6f}")
print_step(3, "Antisymmetry: D(u,v,a) = −D(v,u,a)")
compare_print("antisymmetry",
f"D(u,v,a)={triple(u,v,a):.6f}",
f"−D(v,u,a)={-triple(v,u,a):.6f}")
print_step(4, "Cyclic symmetry: D(u,v,a)=D(v,a,u)=D(a,u,v)")
print(f" D(u,v,a)={triple(u,v,a):.6f} D(v,a,u)={triple(v,a,u):.6f} D(a,u,v)={triple(a,u,v):.6f}")
Decide whether the four points , , , are coplanar.
Hint. Taking the first point as the base point, form three difference vectors and compute the signed volume . means the points are coplanar.
Solution to Exercise 32
# ============================================================
print_header("Exercise 32 | Deciding Whether Four Points Are Coplanar")
# ============================================================
print_step(1, "Define the four points P₀,P₁,P₂,P₃ ∈ ℝ³")
P0=np.array([1.,2.,1.]); P1=np.array([3.,1.,2.])
P2=np.array([2.,3.,0.]); P3=np.array([4.,2.,3.])
print_step(2, "Compute the signed volume D(P₁−P₀, P₂−P₀, P₃−P₀)")
d = triple(P1-P0, P2-P0, P3-P0)
compare_print("value of D (D=0 ⟺ coplanar)", f"{d:.6f}", "≠ 0 → the four points are not coplanar")
print(" verdict:", "coplanar" if abs(d)<1e-10 else "not coplanar")
In , consider the parallelogram spanned by and (with vertices , , , ). Prove that the two diagonals are perpendicular if and only if this parallelogram is a rhombus ().
Hint. The two diagonal vectors are and ; compute their inner product.
Solution to Exercise 33
The two diagonals are perpendicular .
So this is equivalent to , that is, , that is, the parallelogram is a rhombus. ■
# ============================================================
print_header("Exercise 33 | The Perpendicular-Diagonals Condition for a Rhombus")
# ============================================================
print_step(1, "Check (u+v)·(u−v) = ‖u‖²−‖v‖² for three shapes")
cases = [
("square u=(1,0), v=(0,1)", np.array([1.,0.]), np.array([0.,1.])),
("rectangle u=(2,0), v=(0,1)", np.array([2.,0.]), np.array([0.,1.])),
("rhombus u=(1,0), v=(cos60°,sin60°)", np.array([1.,0.]), np.array([.5,np.sqrt(3)/2])),
]
for label, u, v in cases:
dot = np.dot(u+v, u-v)
is_rhombus = np.isclose(np.linalg.norm(u), np.linalg.norm(v))
is_perp = np.isclose(dot, 0)
print(f" {label}")
print(f" (u+v)·(u−v)={dot:.4f} rhombus={is_rhombus} diagonals perpendicular={is_perp}")
There is no cross product in ; derive the area formula for a parallelogram by following the guided steps below.
The vectors , :
(a) Compute .
(b) Compute the height .
(c) Compute the area of the parallelogram.
For linearly independent vectors , prove in the same way that
Starting from area , derive the formula above again using the angle definition from the inner product.
If , check that the area formula gives 0.
If , find the area and explain its geometric meaning.
If (a rhombus), check that the area of the rhombus is ( are the lengths of the diagonals).
Hint. In part 2, the base is and the height is ; then expand.
Solution to Exercise 34
Part 1. , so . Then , the height is , and the area is .
Part 2.
Part 3. (using ).
Part 4. and . Substituting: ✓.
Part 5. , so the area is , the product of the two side lengths—exactly the area formula for a rectangle; if the two sides have equal length, it is a square.
Part 6. Let , with diagonals , :
The area is ✓.
# ============================================================
print_header("Exercise 34 | The Area of a Parallelogram in ℝ⁴")
# ============================================================
print_step(1, "A concrete example: when OP⊥OQ, area = ‖OP‖‖OQ‖")
OP=np.array([1.,0.,1.,0.]); OQ=np.array([0.,1.,0.,1.])
compare_print("area", f"{para_area(OP,OQ):.6f}", "‖OP‖‖OQ‖ = 2 (OP⊥OQ)")
print_step(2, "General vectors: area formula vs ‖u‖‖v‖sinθ")
rng=np.random.default_rng(3)
u=rng.standard_normal(4); v=rng.standard_normal(4)
A1 = para_area(u,v)
cos_t = np.clip(np.dot(u,v)/(np.linalg.norm(u)*np.linalg.norm(v)),-1,1)
A2 = np.linalg.norm(u)*np.linalg.norm(v)*abs(np.sin(np.arccos(cos_t)))
compare_print("area formula", f"{A1:.6f}", f"‖u‖‖v‖sinθ = {A2:.6f} ✓ {np.isclose(A1,A2)}")
print_step(3, "Area for parallel vectors = 0")
compare_print("area when v=3u", f"{para_area(u,3*u):.2e}", "≈ 0")
print_step(4, "Area of a rhombus = pq/2 (product of the diagonals / 2)")
theta=np.pi/4; a=3.0
u_=a*np.array([1.,0.,0.,0.]); v_=a*np.array([np.cos(theta),np.sin(theta),0.,0.])
p=np.linalg.norm(u_+v_); q=np.linalg.norm(u_-v_)
compare_print("rhombus area", f"{para_area(u_,v_):.4f}", f"pq/2 = {p*q/2:.4f} ✓ {np.isclose(para_area(u_,v_),p*q/2)}")
The Geometry of Planes and Lines¶
In , two planes are defined to be perpendicular when their normal vectors are perpendicular to each other. Decide whether each of the following pairs of planes is perpendicular.
The plane and the plane .
The plane and the plane (given parametrically) .
The plane and the plane .
Hint. Take the two direction vectors from the parametric equation; the normal vector is their cross product.
Solution to Exercise 35
Part 1. , , . Not perpendicular.
Part 2. (the normal vector of ). The normal vector of the second plane: . . Not perpendicular.
Part 3. The normal vector of the first plane is .
The normal vector of the second plane: .
. Perpendicular. ✓
# ============================================================
print_header("Exercise 35 | Are Two Planes Perpendicular?")
# ============================================================
print_step(1, "(1) normal vectors n₀=(1,0,3), n₁=(3,−1,0)")
n0=np.array([1,0,3]); n1=np.array([3,-1,0])
compare_print("n₀·n₁", np.dot(n0,n1), "≠ 0 → not perpendicular")
print_step(2, "(2) n₀=(1,0,0), normal vector of the second plane = (0,1,0)×(1,1,1)")
n0_=np.array([1.,0.,0.])
n1_=np.cross(np.array([0.,1.,0.]),np.array([1.,1.,1.]))
print(" n₁ =", n1_)
compare_print("n₀·n₁", np.dot(n0_,n1_), "≠ 0 → not perpendicular")
print_step(3, "(3) normal vectors of the two parametrized planes")
n0_=np.cross(np.array([1.,-1.,0.]),np.array([0.,0.,1.]))
n1_=np.cross(np.array([1.,0.,-1.]),np.array([0.,1.,1.]))
print(" n₀ =", n0_, " n₁ =", n1_)
compare_print("n₀·n₁ (should be 0 → perpendicular)", np.dot(n0_,n1_), "0 → perpendicular ✓")
In , let the plane be and the line . Find a plane such that and , and express by an equation.
Let the plane be (that is, the plane ) and the line . Find a plane such that and .
Let the plane be and the line :
(a) Find a plane such that and .
(b) Is the plane satisfying these conditions unique?
Hint. The normal vector of is perpendicular both to the direction vector of (since ) and to the normal vector of (since ). Hence the two direction vectors of can be taken to be the direction vector of and the normal vector of .
Solution to Exercise 36
Part 1. contains , so the direction vector of is a direction vector of . means that the normal vector of is also a direction vector of (or lies in ). The normal vector of :
passes through the point on the line, so its equation is , that is, , or .
Part 2. The direction of is , and the normal vector of is . Since , their cross product is , and a second direction vector of must be chosen separately. contains : it passes through the point with direction , so one direction of is . Moreover, requires the normal vector of to be perpendicular to , that is, the normal vector of is horizontal and is a vertical plane. Taking the other direction vector to be (a horizontal direction), , and .
Part 3(a). , , and the direction of is . As above, any vertical plane containing works ( contains and its directions include ). For example, taking the horizontal direction vector : , ; or taking gives .
Part 3(b). Not unique. The line is perpendicular to the plane (the direction of ), so every plane containing is perpendicular to ; hence there are infinitely many planes satisfying the conditions (rotating about ).
# ============================================================
print_header("Exercise 36 | A Plane Containing a Line and Perpendicular to a Given Plane")
# ============================================================
print_step(1, "Part 1: P: x+2y−z=3, L has direction (1,1,0) and passes through (1,0,1)")
nP=np.array([1.,2.,-1.]); dL=np.array([1.,1.,0.])
nQ=np.cross(dL,nP)
pt=np.array([1.,0.,1.])
d=np.dot(nQ,pt)
print(" normal vector of Q, nQ =", nQ)
print(f" equation of the plane: {nQ[0]:.0f}x + {nQ[1]:.0f}y + {nQ[2]:.0f}z = {d:.0f}")
print_step(2, "Check that L lies in Q (nQ⊥dL) and Q⊥P (nQ⊥nP)")
compare_print("nQ·dL (should be 0)", f"{np.dot(nQ,dL):.2e}", "0")
compare_print("nQ·nP (should be 0)", f"{np.dot(nQ,nP):.2e}", "0")
Consider the two lines and .
Verify that and are skew lines (they have different directions and do not intersect).
Find two parallel planes , (), and express them by equations.
Compute the distance between and (that is, the distance between and ).
Hint. The common normal vector is ; the distance between the two planes is , where is a point on .
Solution to Exercise 37
Step 1 (checking that the lines are skew). and , so the directions are not parallel ✓. If the lines intersected, then , which gives (component 0), (component 1), and (component 2, a contradiction) ✓; hence the lines are skew.
Step 2 (parallel planes). The common normal vector is .
passes through with normal vector : .
passes through with normal vector : .
Step 3 (distance). The distance between the parallel planes and is .
# ============================================================
print_header("Exercise 37 | The Distance between Skew Lines")
# ============================================================
print_step(1, "Define the direction vectors and base points of the two skew lines L₀, L₁")
d0=np.array([1.,0.,0.]); d1=np.array([0.,1.,0.])
p0=np.array([1.,0.,0.]); p1=np.array([0.,1.,1.])
print_step(2, "Compute the common normal vector n = d₀×d₁")
n=np.cross(d0,d1)
print(" n =", n)
print_step(3, "Distance between the skew lines = |n·(p₁−p₀)| / ‖n‖")
dist=abs(np.dot(n,p1-p0))/np.linalg.norm(n)
compare_print("distance", f"{dist:.6f}", "= 1 (the distance between the planes z=0 and z=1)")
Generalized Area Formulas¶
Let , . The area of the parallelogram satisfies
Let , be the projections of onto the -plane; find the area of the projected parallelogram.
Similarly, find (the -plane) and (the -plane).
Verify that .
Hint. After projection, we still have a parallelogram spanned by two vectors, and its area is the absolute value of the corresponding determinant in the original formula.
Solution to Exercise 38
Step 1. .
Step 2. , .
Step 3. By the cross product area formula:
This is the “generalized Pythagorean theorem”: the square of the area of a parallelogram equals the sum of the squares of the areas of its projections onto the coordinate planes.
# ============================================================
print_header("Exercise 38–39 | The Generalized Pythagorean Theorem (Cauchy–Binet)")
# ============================================================
rng=np.random.default_rng(99)
print_step(1, "Check in ℝ³: A² = Σ_{i<j} A_{ij}²")
u=rng.standard_normal(3); v=rng.standard_normal(3)
A2=para_area(u,v)**2
Aij2=sum((u[i]*v[j]-u[j]*v[i])**2 for i,j in combinations(range(3),2))
compare_print("A² (ℝ³)", f"{A2:.6f}", f"ΣA_ij² = {Aij2:.6f} ✓ {np.isclose(A2,Aij2)}")
print_step(2, "Check in ℝ⁴: A² = Σ_{i<j} A_{ij}²")
u4=rng.standard_normal(4); v4=rng.standard_normal(4)
A2_4=para_area(u4,v4)**2
Aij2_4=sum((u4[i]*v4[j]-u4[j]*v4[i])**2 for i,j in combinations(range(4),2))
compare_print("A² (ℝ⁴)", f"{A2_4:.6f}", f"ΣA_ij² = {Aij2_4:.6f} ✓ {np.isclose(A2_4,Aij2_4)}")
Let , , and let the area of the parallelogram be .
Check that the projection of onto the -plane is .
Define to be the area of the parallelogram projected onto the -plane. Check that
Check the generalized Pythagorean theorem ( terms in all).
Hint. Expand directly and compare with the sum of the squares of the six determinants.
Solution to Exercise 39
Step 1. The orthogonal projection operator onto the -plane keeps components 0 and 1 and sets the rest to 0, so the projection is ✓.
Step 2. After projecting onto the -plane, the square of the area of the parallelogram spanned by the two projected vectors is ; this holds for all six terms.
Step 3. Expand directly:
(This is the four-dimensional version of Lagrange’s identity.) Hence the generalized Pythagorean theorem holds in , and likewise in .
✓ All 39 exercises completed.