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}\):

\[\begin{split} v \times w = v_1 w_2 - v_2 w_1 = \det\begin{bmatrix} v_1 & w_1 \\ v_2 & w_2 \end{bmatrix} \end{split}\]

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}\):

\[\begin{split} 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} \end{split}\]

Geometric Propertiesยถ

  1. Length = area of parallelogram

  2. Direction = perpendicular to both \(v\) and \(w\)

  3. Orientation = right-hand rule

Mnemonic: Fake Determinantยถ

\[\begin{split} v \times w = \det\begin{bmatrix} \hat{i} & \hat{j} & \hat{k} \\ v_1 & v_2 & v_3 \\ w_1 & w_2 & w_3 \end{bmatrix} \end{split}\]

Expand along first row (pretending basis vectors are numbers):

\[ = \hat{i}(v_2 w_3 - v_3 w_2) - \hat{j}(v_1 w_3 - v_3 w_1) + \hat{k}(v_1 w_2 - v_2 w_1) \]
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:

  1. Point your right index finger along \(v\)

  2. Point your right middle finger along \(w\)

  3. 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ยถ

\[ \tau = r \times F \]
  • \(r\): position vector from pivot

  • \(F\): applied force

  • \(\tau\): torque (rotational force)

4. Angular Momentumยถ

\[ L = r \times p \]
  • \(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ยถ

\[ v \times w = -(w \times v) \]

Order matters! Swapping reverses direction.

Distributiveยถ

\[ u \times (v + w) = u \times v + u \times w \]

Scalar Multiplicationยถ

\[ (cv) \times w = c(v \times w) = v \times (cw) \]

Not Associative!ยถ

\[ u \times (v \times w) \neq (u \times v) \times w \]

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?

\[ \|v \times w\| = \|v\| \|w\| \sin(\theta) \]
  • \(\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ยถ

  1. 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

  2. 3D Cross Product

    • Returns a vector (perpendicular)

    • Length = area of parallelogram

    • Direction = right-hand rule

  3. Computation

    • Use determinant formula

    • Or โ€œfakeโ€ determinant with \(\hat{i}, \hat{j}, \hat{k}\)

    • Or just np.cross(v, w)!

  4. 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:

  1. Determinants (area scaling)

  2. Geometry (perpendicularity)

  3. 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!