import math def rotation_2d(theta): c, s = math.cos(theta), math.sin(theta) return [[c, -s], [s, c]] def rotation_3d_z(theta): c, s = math.cos(theta), math.sin(theta) return [[c, -s, 0], [s, c, 0], [0, 0, 1]] def rotation_3d_x(theta): c, s = math.cos(theta), math.sin(theta) return [[1, 0, 0], [0, c, -s], [0, s, c]] def rotation_3d_y(theta): c, s = math.cos(theta), math.sin(theta) return [[c, 0, s], [0, 1, 0], [-s, 0, c]] def scaling_2d(sx, sy): return [[sx, 0], [0, sy]] def shearing_2d(kx, ky): return [[1, kx], [ky, 1]] def reflection_x(): return [[1, 0], [0, -1]] def reflection_y(): return [[-1, 0], [0, 1]] def mat_vec_mul(matrix, vector): return [ sum(matrix[i][j] * vector[j] for j in range(len(vector))) for i in range(len(matrix)) ] def mat_mul(a, b): rows_a, cols_b = len(a), len(b[0]) cols_a = len(a[0]) return [ [sum(a[i][k] * b[k][j] for k in range(cols_a)) for j in range(cols_b)] for i in range(rows_a) ] def det_2x2(m): return m[0][0] * m[1][1] - m[0][1] * m[1][0] def det_3x3(m): return ( m[0][0] * (m[1][1] * m[2][2] - m[1][2] * m[2][1]) - m[0][1] * (m[1][0] * m[2][2] - m[1][2] * m[2][0]) + m[0][2] * (m[1][0] * m[2][1] - m[1][1] * m[2][0]) ) def eigenvalues_2x2(matrix): a, b = matrix[0] c, d = matrix[1] trace = a + d det = a * d - b * c discriminant = trace ** 2 - 4 * det if discriminant < 0: real = trace / 2 imag = (-discriminant) ** 0.5 / 2 return (complex(real, imag), complex(real, -imag)) sqrt_disc = discriminant ** 0.5 return ((trace + sqrt_disc) / 2, (trace - sqrt_disc) / 2) def eigenvector_2x2(matrix, eigenvalue): a, b = matrix[0] c, d = matrix[1] if abs(b) > 1e-10: v = [b, eigenvalue - a] elif abs(c) > 1e-10: v = [eigenvalue - d, c] else: if abs(a - eigenvalue) < 1e-10: v = [1, 0] else: v = [0, 1] mag = (v[0] ** 2 + v[1] ** 2) ** 0.5 return [v[0] / mag, v[1] / mag] def fmt(v, decimals=4): if isinstance(v, list): return [round(x, decimals) for x in v] return round(v, decimals) def demo_basic_transformations(): print("=" * 60) print("BASIC TRANSFORMATIONS") print("=" * 60) point = [1.0, 0.0] theta = math.pi / 4 rotated = mat_vec_mul(rotation_2d(theta), point) print(f"\nRotate (1,0) by 45 deg: {fmt(rotated)}") scaled = mat_vec_mul(scaling_2d(2, 3), [1.0, 1.0]) print(f"Scale (1,1) by (2,3): {fmt(scaled)}") sheared = mat_vec_mul(shearing_2d(1, 0), [1.0, 1.0]) print(f"Shear (1,1) kx=1: {fmt(sheared)}") reflected = mat_vec_mul(reflection_y(), [2.0, 1.0]) print(f"Reflect (2,1) across y-axis: {fmt(reflected)}") reflected_x = mat_vec_mul(reflection_x(), [2.0, 1.0]) print(f"Reflect (2,1) across x-axis: {fmt(reflected_x)}") def demo_unit_square(): print("\n" + "=" * 60) print("TRANSFORMATIONS ON A UNIT SQUARE") print("=" * 60) square = [[0, 0], [1, 0], [1, 1], [0, 1]] labels = ["origin", "right", "top-right", "top"] print("\nOriginal square:") for label, pt in zip(labels, square): print(f" {label}: {pt}") transforms = [ ("Rotate 45 deg", rotation_2d(math.pi / 4)), ("Scale (2, 0.5)", scaling_2d(2, 0.5)), ("Shear kx=0.5", shearing_2d(0.5, 0)), ("Reflect y-axis", reflection_y()), ] for name, matrix in transforms: print(f"\n{name}:") for label, pt in zip(labels, square): result = mat_vec_mul(matrix, pt) print(f" {label}: {pt} -> {fmt(result)}") print(f" det = {fmt(det_2x2(matrix))}") def demo_composition(): print("\n" + "=" * 60) print("COMPOSITION OF TRANSFORMATIONS") print("=" * 60) R = rotation_2d(math.pi / 2) S = scaling_2d(2, 0.5) rotate_then_scale = mat_mul(S, R) scale_then_rotate = mat_mul(R, S) point = [1.0, 0.0] result1 = mat_vec_mul(rotate_then_scale, point) result2 = mat_vec_mul(scale_then_rotate, point) print(f"\nPoint: {point}") print(f"Rotate 90 then scale (2, 0.5): {fmt(result1)}") print(f"Scale (2, 0.5) then rotate 90: {fmt(result2)}") print("Order matters.") print(f"\ndet(R) = {fmt(det_2x2(R))}") print(f"det(S) = {fmt(det_2x2(S))}") print(f"det(S @ R) = {fmt(det_2x2(rotate_then_scale))}") print(f"det(S) * det(R) = {fmt(det_2x2(S) * det_2x2(R))}") print("Determinant of composition = product of determinants.") def demo_3d_rotations(): print("\n" + "=" * 60) print("3D ROTATIONS") print("=" * 60) point = [1.0, 0.0, 0.0] theta = math.pi / 2 rz = mat_vec_mul(rotation_3d_z(theta), point) rx = mat_vec_mul(rotation_3d_x(theta), point) ry = mat_vec_mul(rotation_3d_y(theta), point) print(f"\nPoint: {point}") print(f"Rotate 90 around z: {fmt(rz)}") print(f"Rotate 90 around x: {fmt(rx)}") print(f"Rotate 90 around y: {fmt(ry)}") print(f"\ndet(Rz) = {fmt(det_3x3(rotation_3d_z(theta)))}") print(f"det(Rx) = {fmt(det_3x3(rotation_3d_x(theta)))}") print(f"det(Ry) = {fmt(det_3x3(rotation_3d_y(theta)))}") print("All rotation determinants = 1 (volume preserved).") def demo_eigenvalues_from_scratch(): print("\n" + "=" * 60) print("EIGENVALUES AND EIGENVECTORS (FROM SCRATCH, 2x2)") print("=" * 60) matrices = [ ("Symmetric", [[2, 1], [1, 2]]), ("Upper triangular", [[3, 1], [0, 2]]), ("Scaling", [[3, 0], [0, 5]]), ("Rotation 90", [[0, -1], [1, 0]]), ] for name, A in matrices: vals = eigenvalues_2x2(A) print(f"\n{name}: {A}") print(f" Eigenvalues: {vals[0]}, {vals[1]}") if all(isinstance(v, (int, float)) for v in vals): for val in vals: vec = eigenvector_2x2(A, val) result = mat_vec_mul(A, vec) scaled = [val * vec[0], val * vec[1]] print(f" lambda={fmt(val)}, v={fmt(vec)}") print(f" A @ v = {fmt(result)}") print(f" l * v = {fmt(scaled)}") else: print(" Complex eigenvalues: pure rotation, no real eigenvectors.") def demo_eigendecomposition(): print("\n" + "=" * 60) print("EIGENDECOMPOSITION (2x2, FROM SCRATCH)") print("=" * 60) A = [[3, 1], [0, 2]] vals = eigenvalues_2x2(A) v0 = eigenvector_2x2(A, vals[0]) v1 = eigenvector_2x2(A, vals[1]) V = [[v0[0], v1[0]], [v0[1], v1[1]]] D = [[vals[0], 0], [0, vals[1]]] det_v = det_2x2(V) V_inv = [ [V[1][1] / det_v, -V[0][1] / det_v], [-V[1][0] / det_v, V[0][0] / det_v], ] reconstructed = mat_mul(mat_mul(V, D), V_inv) print(f"\nA = {A}") print(f"Eigenvalues: {fmt(vals[0])}, {fmt(vals[1])}") print(f"V (eigenvectors as columns):") for row in V: print(f" {fmt(row)}") print(f"D (eigenvalues on diagonal):") for row in D: print(f" {fmt(row)}") print(f"Reconstructed A = V @ D @ V^-1:") for row in reconstructed: print(f" {fmt(row)}") def demo_determinant_meaning(): print("\n" + "=" * 60) print("DETERMINANT AS VOLUME SCALING FACTOR") print("=" * 60) cases = [ ("Rotation 45 deg", rotation_2d(math.pi / 4)), ("Scale (2, 3)", scaling_2d(2, 3)), ("Shear kx=1", shearing_2d(1, 0)), ("Reflect y-axis", reflection_y()), ("Singular [[1,2],[2,4]]", [[1, 2], [2, 4]]), ] print() for name, m in cases: d = det_2x2(m) if d == 0: meaning = "space collapses, irreversible" elif d < 0: meaning = "orientation flipped" elif abs(d - 1.0) < 1e-10: meaning = "area preserved" else: meaning = f"area scaled by {abs(d):.1f}x" print(f"det({name}) = {fmt(d):>8} ({meaning})") def demo_numpy_comparison(): print("\n" + "=" * 60) print("NUMPY COMPARISON") print("=" * 60) try: import numpy as np except ImportError: print("\nNumPy not installed. Skipping.") return theta = math.pi / 4 R = np.array([[math.cos(theta), -math.sin(theta)], [math.sin(theta), math.cos(theta)]]) point = np.array([1.0, 0.0]) print(f"\nRotate (1,0) by 45 deg: {R @ point}") A = np.array([[2, 1], [1, 2]], dtype=float) eigenvalues, eigenvectors = np.linalg.eig(A) print(f"\nA = {A.tolist()}") print(f"Eigenvalues (numpy): {eigenvalues}") print(f"Eigenvectors (numpy, columns):\n{eigenvectors}") for i in range(len(eigenvalues)): v = eigenvectors[:, i] lam = eigenvalues[i] print(f" A @ v{i} = {A @ v}, lambda * v{i} = {lam * v}") B = np.array([[3, 1], [0, 2]], dtype=float) vals, vecs = np.linalg.eig(B) D = np.diag(vals) V = vecs reconstructed = V @ D @ np.linalg.inv(V) print(f"\nEigendecomposition of {B.tolist()}:") print(f" Reconstructed: {reconstructed.tolist()}") Rz = np.array(rotation_3d_z(math.pi / 2)) point_3d = np.array([1.0, 0.0, 0.0]) print(f"\n3D rotate (1,0,0) 90 deg around z: {np.round(Rz @ point_3d, 4)}") cov = np.array([[2.0, 1.0], [1.0, 3.0]]) vals, vecs = np.linalg.eig(cov) print(f"\nCovariance matrix: {cov.tolist()}") print(f"Principal components (eigenvectors): columns of\n{vecs}") print(f"Variance along each (eigenvalues): {vals}") print("PCA picks the eigenvectors with the largest eigenvalues.") if __name__ == "__main__": demo_basic_transformations() demo_unit_square() demo_composition() demo_3d_rotations() demo_eigenvalues_from_scratch() demo_eigendecomposition() demo_determinant_meaning() demo_numpy_comparison()