Chapter 8: Nonsquare matrices as transformations between dimensionsยถ
1. The 2D Cross Product: Signed Areaยถ
Definitionยถ
For 2D vectors \(v = \begin{bmatrix} v_1 \\ v_2 \end{bmatrix}\) and \(w = \begin{bmatrix} w_1 \\ w_2 \end{bmatrix}\):
This is a number (not a vector).
Geometric Meaningยถ
Magnitude: Area of parallelogram spanned by \(v\) and \(w\)
Sign:
Positive if \(v\) is to the right of \(w\) (counterclockwise)
Negative if \(v\) is to the left of \(w\) (clockwise)
Why It Mattersยถ
The sign tells us about orientation - crucial for:
Determining if a point is left/right of a line
Testing if polygons are clockwise or counterclockwise
Computer graphics and collision detection
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.patches import FancyArrowPatch, Polygon
from mpl_toolkits.mplot3d import Axes3D, proj3d
import seaborn as sns
sns.set_style("whitegrid")
plt.rcParams['figure.figsize'] = (10, 8)
def draw_vector(ax, origin, vector, color='blue', label=None):
"""Draw a 2D vector."""
arrow = FancyArrowPatch(
origin, origin + vector,
mutation_scale=20, lw=2.5, arrowstyle='-|>', color=color, label=label
)
ax.add_patch(arrow)
def visualize_2d_cross_product():
"""Visualize 2D cross product as signed area."""
# Two vectors
v = np.array([3, 1])
w = np.array([1, 2])
# Compute cross product (determinant)
cross = v[0] * w[1] - v[1] * w[0]
fig, axes = plt.subplots(1, 2, figsize=(14, 6))
# Left: Positive cross product
ax1 = axes[0]
ax1.set_xlim(-0.5, 4)
ax1.set_ylim(-0.5, 3)
ax1.set_aspect('equal')
ax1.grid(True, alpha=0.3)
ax1.axhline(y=0, color='k', linewidth=0.5)
ax1.axvline(x=0, color='k', linewidth=0.5)
# Draw parallelogram
parallelogram = Polygon(
[np.array([0, 0]), v, v + w, w],
fill=True, alpha=0.3, color='cyan', edgecolor='blue', linewidth=2
)
ax1.add_patch(parallelogram)
# Draw vectors
draw_vector(ax1, np.array([0, 0]), v, color='red', label='v')
draw_vector(ax1, np.array([0, 0]), w, color='blue', label='w')
ax1.set_title(f'v ร w = {cross:.1f} (Positive)\nv is RIGHT of w',
fontsize=13, fontweight='bold')
ax1.legend(fontsize=11)
ax1.set_xlabel('x')
ax1.set_ylabel('y')
# Right: Negative cross product (swap order)
ax2 = axes[1]
ax2.set_xlim(-0.5, 4)
ax2.set_ylim(-0.5, 3)
ax2.set_aspect('equal')
ax2.grid(True, alpha=0.3)
ax2.axhline(y=0, color='k', linewidth=0.5)
ax2.axvline(x=0, color='k', linewidth=0.5)
# Same parallelogram but w ร v
cross_neg = w[0] * v[1] - w[1] * v[0]
parallelogram2 = Polygon(
[np.array([0, 0]), w, w + v, v],
fill=True, alpha=0.3, color='orange', edgecolor='red', linewidth=2
)
ax2.add_patch(parallelogram2)
# Draw vectors in opposite order
draw_vector(ax2, np.array([0, 0]), w, color='blue', label='w')
draw_vector(ax2, np.array([0, 0]), v, color='red', label='v')
ax2.set_title(f'w ร v = {cross_neg:.1f} (Negative)\nw is LEFT of v',
fontsize=13, fontweight='bold')
ax2.legend(fontsize=11)
ax2.set_xlabel('x')
ax2.set_ylabel('y')
plt.tight_layout()
plt.show()
print(f"Vector v = {v}")
print(f"Vector w = {w}")
print(f"\nv ร w = {v[0]}ร{w[1]} - {v[1]}ร{w[0]} = {cross}")
print(f"w ร v = {cross_neg}")
print(f"\nNotice: w ร v = -(v ร w)")
print(f"\nArea of parallelogram = |{cross}| = {abs(cross)}")
visualize_2d_cross_product()
2. Connection to Determinantsยถ
Why the Determinant Formula?ยถ
Remember: the determinant measures how a transformation scales areas.
The matrix \(\begin{bmatrix} v_1 & w_1 \\ v_2 & w_2 \end{bmatrix}\) represents a transformation that:
Moves \(\hat{i}\) to \(v\)
Moves \(\hat{j}\) to \(w\)
The unit square (area = 1) becomes the parallelogram spanned by \(v\) and \(w\).
Therefore: det = area scaling factor = area of parallelogram!
def demonstrate_cross_as_determinant():
"""Show why cross product = determinant."""
v = np.array([2, 3])
w = np.array([4, 1])
# Matrix with v and w as columns
A = np.column_stack([v, w])
det = np.linalg.det(A)
# Cross product
cross = v[0] * w[1] - v[1] * w[0]
print("Matrix where i-hat โ v and j-hat โ w:")
print(A)
print(f"\nDeterminant = {det:.4f}")
print(f"Cross product v ร w = {cross:.4f}")
print(f"\nThey're the same! โ")
print(f"\nMeaning: Unit square (area 1) โ parallelogram (area {abs(cross):.1f})")
demonstrate_cross_as_determinant()
3. The 3D Cross Product: A New Vectorยถ
Big Differenceยถ
In 3D, the cross product gives a vector, not a number!
For \(v = \begin{bmatrix} v_1 \\ v_2 \\ v_3 \end{bmatrix}\) and \(w = \begin{bmatrix} w_1 \\ w_2 \\ w_3 \end{bmatrix}\):
Geometric Propertiesยถ
Length = area of parallelogram
Direction = perpendicular to both \(v\) and \(w\)
Orientation = right-hand rule
Mnemonic: Fake Determinantยถ
Expand along first row (pretending basis vectors are numbers):
def compute_cross_product_3d(v, w):
"""Compute 3D cross product."""
return np.array([
v[1] * w[2] - v[2] * w[1],
v[2] * w[0] - v[0] * w[2],
v[0] * w[1] - v[1] * w[0]
])
def demonstrate_3d_cross_product():
"""Demonstrate 3D cross product computation."""
v = np.array([2, 0, 0]) # Along x-axis
w = np.array([0, 2, 0]) # Along y-axis
# Compute using numpy
cross_np = np.cross(v, w)
# Compute manually
cross_manual = compute_cross_product_3d(v, w)
# Length should be area of parallelogram
area = np.linalg.norm(cross_np)
print("Example: v along x-axis, w along y-axis")
print(f"v = {v}")
print(f"w = {w}")
print(f"\nv ร w = {cross_manual}")
print(f"NumPy result: {cross_np}")
print(f"\nLength = {area} (area of parallelogram)")
print(f"Direction: along z-axis (perpendicular to both!)")
# Verify perpendicularity
print(f"\nVerification:")
print(f"(v ร w) ยท v = {cross_np @ v} (should be 0)")
print(f"(v ร w) ยท w = {cross_np @ w} (should be 0)")
print(f"Perpendicular to both? โ")
demonstrate_3d_cross_product()
4. The Right-Hand Ruleยถ
Which Way Does It Point?ยถ
Since there are two directions perpendicular to a plane, we need a convention:
Right-Hand Rule:
Point your right index finger along \(v\)
Point your right middle finger along \(w\)
Your thumb points in the direction of \(v \times w\)
Important Propertiesยถ
\(v \times w = -(w \times v)\) (anti-commutative)
\(v \times v = 0\) (parallel vectors)
\(\hat{i} \times \hat{j} = \hat{k}\)
\(\hat{j} \times \hat{k} = \hat{i}\)
\(\hat{k} \times \hat{i} = \hat{j}\)
class Arrow3D(FancyArrowPatch):
"""3D arrow for matplotlib."""
def __init__(self, xs, ys, zs, *args, **kwargs):
super().__init__((0,0), (0,0), *args, **kwargs)
self._verts3d = xs, ys, zs
def do_3d_projection(self, renderer=None):
xs3d, ys3d, zs3d = self._verts3d
xs, ys, zs = proj3d.proj_transform(xs3d, ys3d, zs3d, self.axes.M)
self.set_positions((xs[0],ys[0]),(xs[1],ys[1]))
return np.min(zs)
def draw_3d_vector(ax, origin, vector, color='blue', label=None):
"""Draw a 3D vector."""
arrow = Arrow3D(
[origin[0], origin[0] + vector[0]],
[origin[1], origin[1] + vector[1]],
[origin[2], origin[2] + vector[2]],
mutation_scale=20, lw=2.5, arrowstyle='-|>', color=color, label=label
)
ax.add_artist(arrow)
def visualize_right_hand_rule():
"""Visualize the right-hand rule."""
fig = plt.figure(figsize=(14, 6))
# Example 1: i ร j = k
ax1 = fig.add_subplot(121, projection='3d')
i_hat = np.array([1, 0, 0])
j_hat = np.array([0, 1, 0])
k_hat = np.cross(i_hat, j_hat)
origin = np.array([0, 0, 0])
draw_3d_vector(ax1, origin, i_hat, color='red', label='รฎ (index finger)')
draw_3d_vector(ax1, origin, j_hat, color='blue', label='ฤต (middle finger)')
draw_3d_vector(ax1, origin, k_hat, color='green', label='kฬ = รฎรฤต (thumb)')
ax1.set_xlim([0, 1.5])
ax1.set_ylim([0, 1.5])
ax1.set_zlim([0, 1.5])
ax1.set_xlabel('X')
ax1.set_ylabel('Y')
ax1.set_zlabel('Z')
ax1.set_title('รฎ ร ฤต = kฬ\nRight-hand rule', fontsize=12, fontweight='bold')
ax1.legend(fontsize=10)
# Example 2: Custom vectors
ax2 = fig.add_subplot(122, projection='3d')
v = np.array([1, 1, 0])
w = np.array([0, 1, 1])
cross = np.cross(v, w)
draw_3d_vector(ax2, origin, v, color='red', label=f'v = {v}')
draw_3d_vector(ax2, origin, w, color='blue', label=f'w = {w}')
draw_3d_vector(ax2, origin, cross, color='purple', label=f'vรw = {cross}')
ax2.set_xlim([-2, 2])
ax2.set_ylim([-2, 2])
ax2.set_zlim([-2, 2])
ax2.set_xlabel('X')
ax2.set_ylabel('Y')
ax2.set_zlabel('Z')
ax2.set_title('v ร w perpendicular to both', fontsize=12, fontweight='bold')
ax2.legend(fontsize=10)
plt.tight_layout()
plt.show()
print("Verification:")
print(f"v ร w = {cross}")
print(f"(v ร w) ยท v = {cross @ v} โ")
print(f"(v ร w) ยท w = {cross @ w} โ")
print(f"||v ร w|| = {np.linalg.norm(cross):.4f}")
visualize_right_hand_rule()
5. Applications of Cross Productsยถ
1. Finding Perpendicular Vectorsยถ
Need a vector perpendicular to \(v\) and \(w\)? Just compute \(v \times w\)!
2. Computing Normal Vectors (Graphics)ยถ
For a triangle with vertices A, B, C:
Edge 1: \(v = B - A\)
Edge 2: \(w = C - A\)
Normal: \(n = v \times w\) (perpendicular to surface)
Used for:
Lighting calculations
Backface culling
Surface orientation
3. Torque in Physicsยถ
\(r\): position vector from pivot
\(F\): applied force
\(\tau\): torque (rotational force)
4. Angular Momentumยถ
\(r\): position
\(p\): linear momentum
\(L\): angular momentum
def demonstrate_applications():
"""Show practical applications."""
print("Application 1: Triangle Normal (Computer Graphics)")
print("="*50)
# Triangle vertices
A = np.array([0, 0, 0])
B = np.array([1, 0, 0])
C = np.array([0, 1, 0])
# Edges
edge1 = B - A
edge2 = C - A
# Normal
normal = np.cross(edge1, edge2)
normal_unit = normal / np.linalg.norm(normal)
print(f"Triangle vertices: A={A}, B={B}, C={C}")
print(f"Edge 1 (AB) = {edge1}")
print(f"Edge 2 (AC) = {edge2}")
print(f"Normal = {normal}")
print(f"Unit normal = {normal_unit}")
print(f"This tells us the surface faces the {normal} direction\n")
print("Application 2: Torque (Physics)")
print("="*50)
# Wrench example
r = np.array([0.3, 0, 0]) # 30cm wrench (horizontal)
F = np.array([0, 50, 0]) # 50N force (downward)
torque = np.cross(r, F)
print(f"Wrench length: r = {r} m (horizontal)")
print(f"Applied force: F = {F} N (downward)")
print(f"Torque: ฯ = r ร F = {torque} Nยทm")
print(f"Magnitude: ||ฯ|| = {np.linalg.norm(torque)} Nยทm")
print(f"Direction: perpendicular to both (causes rotation about z-axis)\n")
print("Application 3: Area of Triangle")
print("="*50)
# Triangle from earlier
area_parallelogram = np.linalg.norm(normal)
area_triangle = area_parallelogram / 2
print(f"Cross product length = parallelogram area = {area_parallelogram}")
print(f"Triangle area = {area_triangle}")
print(f"Quick formula: Area = ||v ร w|| / 2")
demonstrate_applications()
6. Properties of Cross Productsยถ
Anti-Commutativityยถ
Order matters! Swapping reverses direction.
Distributiveยถ
Scalar Multiplicationยถ
Not Associative!ยถ
In general (unlike matrix multiplication).
Perpendicular Testยถ
If \(v \times w = 0\), then \(v\) and \(w\) are parallel.
def demonstrate_properties():
"""Demonstrate cross product properties."""
v = np.array([1, 2, 3])
w = np.array([4, 5, 6])
u = np.array([7, 8, 9])
print("Property 1: Anti-commutativity")
vw = np.cross(v, w)
wv = np.cross(w, v)
print(f"v ร w = {vw}")
print(f"w ร v = {wv}")
print(f"v ร w = -(w ร v)? {np.allclose(vw, -wv)}\n")
print("Property 2: Distributive")
left = np.cross(u, v + w)
right = np.cross(u, v) + np.cross(u, w)
print(f"u ร (v + w) = {left}")
print(f"u ร v + u ร w = {right}")
print(f"Equal? {np.allclose(left, right)}\n")
print("Property 3: Scalar multiplication")
c = 3
left = np.cross(c * v, w)
middle = c * np.cross(v, w)
right = np.cross(v, c * w)
print(f"(3v) ร w = {left}")
print(f"3(v ร w) = {middle}")
print(f"v ร (3w) = {right}")
print(f"All equal? {np.allclose(left, middle) and np.allclose(middle, right)}\n")
print("Property 4: Parallel vectors โ zero")
v_parallel = 2 * v
cross_parallel = np.cross(v, v_parallel)
print(f"v = {v}")
print(f"2v = {v_parallel}")
print(f"v ร (2v) = {cross_parallel}")
print(f"Is zero? {np.allclose(cross_parallel, 0)}")
demonstrate_properties()
7. Maximum Area: Perpendicular Vectorsยถ
Interesting Observationยถ
For fixed lengths \(\|v\|\) and \(\|w\|\), the cross product magnitude is maximum when vectors are perpendicular.
Why?
\(\sin(\theta)\) is maximum when \(\theta = 90ยฐ\)
Parallelogram area = base ร height
Height is maximum when perpendicular!
def visualize_angle_effect():
"""Show how angle affects cross product magnitude."""
v = np.array([2, 0, 0])
v_len = np.linalg.norm(v)
w_len = 1.5
# Vary angle
angles = np.linspace(0, 180, 100)
magnitudes = []
for angle_deg in angles:
angle_rad = np.radians(angle_deg)
# w at this angle in xy-plane
w = w_len * np.array([np.cos(angle_rad), np.sin(angle_rad), 0])
cross = np.cross(v, w)
magnitudes.append(np.linalg.norm(cross))
fig, axes = plt.subplots(1, 2, figsize=(14, 5))
# Left: Plot magnitude vs angle
ax1 = axes[0]
ax1.plot(angles, magnitudes, 'b-', linewidth=2.5)
ax1.axvline(x=90, color='r', linestyle='--', linewidth=2, label='90ยฐ (perpendicular)', alpha=0.7)
ax1.axhline(y=v_len * w_len, color='r', linestyle='--', linewidth=1, alpha=0.5)
ax1.set_xlabel('Angle between vectors (degrees)', fontsize=12)
ax1.set_ylabel('||v ร w|| (magnitude)', fontsize=12)
ax1.set_title('Cross Product Magnitude vs Angle', fontsize=13, fontweight='bold')
ax1.grid(True, alpha=0.3)
ax1.legend(fontsize=11)
# Right: Theoretical formula
ax2 = axes[1]
theoretical = v_len * w_len * np.sin(np.radians(angles))
ax2.plot(angles, theoretical, 'g-', linewidth=2.5, label='||v|| ||w|| sin(ฮธ)')
ax2.plot(angles, magnitudes, 'b--', linewidth=2, label='Actual ||v ร w||', alpha=0.7)
ax2.set_xlabel('Angle (degrees)', fontsize=12)
ax2.set_ylabel('Magnitude', fontsize=12)
ax2.set_title('Formula: ||v ร w|| = ||v|| ||w|| sin(ฮธ)', fontsize=13, fontweight='bold')
ax2.grid(True, alpha=0.3)
ax2.legend(fontsize=11)
plt.tight_layout()
plt.show()
print(f"||v|| = {v_len}")
print(f"||w|| = {w_len}")
print(f"\nMaximum ||v ร w|| = {v_len * w_len} (at 90ยฐ)")
print(f"Minimum ||v ร w|| = 0 (at 0ยฐ or 180ยฐ)")
print(f"\nPerpendicular vectors โ maximum area!")
visualize_angle_effect()
8. Summary and Key Takeawaysยถ
Main Ideasยถ
2D Cross Product
Returns a number (signed area)
Formula: \(v \times w = v_1 w_2 - v_2 w_1 = \det(A)\)
Sign indicates orientation
3D Cross Product
Returns a vector (perpendicular)
Length = area of parallelogram
Direction = right-hand rule
Computation
Use determinant formula
Or โfakeโ determinant with \(\hat{i}, \hat{j}, \hat{k}\)
Or just
np.cross(v, w)!
Geometric Meaning
Measures area (2D and 3D)
Finds perpendicular directions (3D)
Tests orientation
Formula Summaryยถ
2D: $\(v \times w = v_1 w_2 - v_2 w_1\)$
3D: $\(v \times w = \begin{bmatrix} v_2 w_3 - v_3 w_2 \\ v_3 w_1 - v_1 w_3 \\ v_1 w_2 - v_2 w_1 \end{bmatrix}\)$
Magnitude: $\(\|v \times w\| = \|v\| \|w\| \sin(\theta)\)$
Key Propertiesยถ
โ \(v \times w = -(w \times v)\) (anti-commutative)
โ \((v \times w) \perp v\) and \((v \times w) \perp w\)
โ \(v \times w = 0\) โบ \(v\) parallel to \(w\)
โ Distributive: \(u \times (v + w) = u \times v + u \times w\)
โ NOT associative
Applicationsยถ
๐จ Computer graphics (surface normals)
๐ง Physics (torque, angular momentum)
๐ Geometry (area calculations)
๐ฎ Game development (collision detection)
๐ Robotics (orientation)
The Deep Connectionยถ
Cross products connect:
Determinants (area scaling)
Geometry (perpendicularity)
Orientation (handedness)
Just like dot products revealed duality, cross products reveal the rich geometry hidden in matrix operations!
9. Practice Exercisesยถ
Exercise 1: 2D Cross Productยถ
Compute \(v \times w\) for:
a) \(v = [3, 2], w = [1, 4]\)
b) \(v = [5, -2], w = [1, 3]\)
Which has larger area?
# Your code here
v1 = np.array([3, 2])
w1 = np.array([1, 4])
v2 = np.array([5, -2])
w2 = np.array([1, 3])
cross1 = v1[0] * w1[1] - v1[1] * w1[0]
cross2 = v2[0] * w2[1] - v2[1] * w2[0]
print(f"a) v ร w = {cross1}, area = {abs(cross1)}")
print(f"b) v ร w = {cross2}, area = {abs(cross2)}")
print(f"\nLarger area: {'a' if abs(cross1) > abs(cross2) else 'b'}")
Exercise 2: Find Perpendicularยถ
Find a vector perpendicular to both:
\(v = [1, 0, 1]\)
\(w = [0, 1, 1]\)
# Your code here
v = np.array([1, 0, 1])
w = np.array([0, 1, 1])
perp = np.cross(v, w)
print(f"v = {v}")
print(f"w = {w}")
print(f"\nv ร w = {perp}")
print(f"\nVerification:")
print(f"perp ยท v = {perp @ v}")
print(f"perp ยท w = {perp @ w}")
print(f"Perpendicular to both? โ")
Exercise 3: Triangle Areaยถ
Find the area of a triangle with vertices:
A = (0, 0, 0)
B = (3, 0, 0)
C = (0, 4, 0)
# Your code here
A = np.array([0, 0, 0])
B = np.array([3, 0, 0])
C = np.array([0, 4, 0])
edge1 = B - A
edge2 = C - A
cross = np.cross(edge1, edge2)
area_parallelogram = np.linalg.norm(cross)
area_triangle = area_parallelogram / 2
print(f"Vertices: A={A}, B={B}, C={C}")
print(f"Edge AB = {edge1}")
print(f"Edge AC = {edge2}")
print(f"\nCross product = {cross}")
print(f"Triangle area = {area_triangle}")
Further Readingยถ
Vector triple product: \((u \times v) \times w\)
Scalar triple product: \(u \cdot (v \times w)\) (volume of parallelepiped)
Quaternions: 4D extension for 3D rotations
Exterior algebra: Generalization to higher dimensions
Next: Change of basis - understanding coordinates from different perspectives!