import matplotlib.pyplot as plt
import numpy as np
Subspaces
'seaborn') plt.style.use(
Vectors are 1-Dimensional
Vectors represent lines, or 1D subspaces. Despite this, in Linear Algebra vectors are used to represent planes, albeit in an awkward way. Since vectors don’t have enough dimensions to represent a plane, we construct normal vectors to represent them.
Instead, why not just have 2D vectors? Seems simple, but how do you construct 2D vectors? First, consider the opposite case of constructing a lower dimensional subspace. The dot product is an operator that takes two vectors and returns a scalar. Numerically, this is just a sum of the products of corresponding elements of each vector:
For example, if we have \(v_1 = [5, 12]\), and \(v_2 = [-6, 8]\), the dot product is: \(v_1 \cdot v_2 = (5)(-6) + (12)(8) = 66\)
= np.array([5., 12.])
vec1 = np.array([-6., 8.])
vec2 np.dot(vec1, vec2)
66.0
But geometrically, you can interpret the dot product as the length of \(v_1\) multiplied by the length of the projection of \(v_2\) onto \(v_1\):
def calc_projection_vector(vec1: np.ndarray, vec2: np.ndarray) -> np.ndarray:
"""Calculate the projection vector of vec1 onto vec2"""
= np.linalg.norm(vec2)
vec2_magnitude = (np.dot(vec1, vec2) / vec2_magnitude)
projection_magnitude = vec2 / vec2_magnitude
projection_direction = projection_magnitude * projection_direction
projection_vec return projection_vec
calc_projection_vector
calc_projection_vector (vec1:numpy.ndarray, vec2:numpy.ndarray)
Calculate the projection vector of vec1 onto vec2
def plot_dot_product(vec1: np.ndarray, vec2: np.ndarray) -> plt.Figure:
"""Visualize the dot product"""
= plt.subplots(1, 1)
fig, ax = np.array([0, 0])
origin = ('r', 'b')
colors for color, vec in zip(colors, (vec1, vec2)):
*origin, *vec, scale=1, scale_units='xy', width=0.01, color=[color],
ax.quiver(=f'len={round(np.linalg.norm(vec), 2)}'
label
)try:
= calc_projection_vector(vec1, vec2)
projected_vector *origin, *projected_vector, scale=1, scale_units='xy', width=0.01, color=['g'],
ax.quiver(=f'len={round(np.linalg.norm(projected_vector), 2)}'
label
)= np.array([projected_vector, vec1]).T
projection_line *projection_line)
ax.plot(
except ZeroDivisionError as zerr:
print('Encountered zero division! Cannot plot projected vector')
f'Projection Vectors (Dot Product={np.dot(vec1, vec2)})')
ax.set_title('equal')
ax.set_aspect(-10., 10])
ax.set_xlim([return fig
plot_dot_product
plot_dot_product (vec1:numpy.ndarray, vec2:numpy.ndarray)
Visualize the dot product
= plot_dot_product(vec1, vec2)
fig =(1.04, 0.6))
plt.legend(bbox_to_anchor plt.show()
Lowering the dimensionality of the subspace has to do with projection. To get a higher dimensional subspace, we need to do the opposite of projection, i.e extension.
Bivectors
We can construct a 2D object by extending the two vectors into a parallelogram. This new 2D object is called a bivector, or a 2-vector. In math notation we write \(a \wedge b\).
Alternatively, we can decompose \(a\) and \(b\) into each orthogonal (perpendicular) basis. This is exactly like splitting a vector into it’s \(x\) and \(y\) components, although in the geometric algebra literature it’s common to refer to the bases as \(e_1\) and \(e_2\).
For example, \(a = \alpha_1 e_1 + \alpha_2 e_2\) and \(b = \beta_1 e_1 + \beta_2 e_2\), where the greek letters are scalars. You can see the full derivation in the Geometric Algebra Primer (Chapter 2.1.1), but know that \(a \wedge b = (\alpha_1 \beta_2 - \alpha_2 \beta_1) e_1 \wedge e_2\).
Note that people tend to abbreviate \(e_1 \wedge e_2\) as \(e_{12}\).
def plot_bivector(vec1: np.ndarray, vec2: np.ndarray) -> plt.Figure:
"""Plot a bivector given two vectors"""
= np.array([0., 0.])
origin = (vec1, vec2, -vec1, -vec2)
vectors = ('r', 'g', 'b', 'y')
colors = plt.subplots(1, 1)
fig, ax for color, vec in zip(colors, vectors):
*origin, *vec, scale=1, scale_units='xy', width=0.02, color=[color])
ax.quiver(+= vec
origin 'Bivector')
ax.set_title('equal')
ax.set_aspect(return fig
plot_bivector
plot_bivector (vec1:numpy.ndarray, vec2:numpy.ndarray)
Plot a bivector given two vectors
= plot_bivector(vec1, vec2)
fig plt.show()
# A bivector with the same magnitude, but opposite orientation
= plot_bivector(vec2, vec1)
fig plt.show()
Notice that orientation matters! We use positive and negative signs to denote orientation, so \(a \wedge b = - b \wedge a\)
We aren’t just restricted to bivectors. There are many vectors of arbitrary dimension, which we call blades:
- Scalars are 0-blades
- 2, 42, \(\tau\), 13.6, \(\pi\), etc
- The vectors we are used to from Linear Algebra are 1-blades
- \(v = 102e_1 + 4e_2 + 69e_3 - 30e_4\)
- Bivectors are 2-blades
- \(A = 420 e_1 \wedge e_2\) (‘A’ for area)
- Trivectors are 3-blades
- \(V = -256 e_1 \wedge e_2 \wedge e_3\) (‘v’ for volume)
- k-vectors are k-blades
- \(C = 1.618 e_1 \wedge e_2 \wedge ... \wedge e_k\) (no reason for ‘C’)
The number in front of the word blade, like 2 or \(k\), is the grade of the blade.
Note that you can have a k-dimensional blade existing in a n-dimensional space, as long as \(k \leq n\).
Readings
Geometric Algebra Primer (Suter, 2003)
- Chapter 2 Introduction
- Chapter 2.1 Bivectors
- Chapter 2.3 Blades
What’s Next?
Next, we look at representing basis blades on a computer.