Chapter 2: Linear combinations, span, and basis vectors¶
1. What is a Transformation?¶
Function vs Transformation¶
Function: Input → Output (abstract)
Transformation: Visualize inputs moving to outputs
The Movement Metaphor¶
Instead of thinking “this vector maps to that vector,” think:
“Space is moving and morphing, taking every vector along for the ride”
We’ll visualize transformations by watching:
Grid lines move
Vectors move
The entire 2D plane morphs
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.patches import FancyArrowPatch, Circle
from matplotlib.animation import FuncAnimation
from IPython.display import HTML
import seaborn as sns
sns.set_style('whitegrid')
plt.rcParams['figure.figsize'] = (10, 8)
np.set_printoptions(precision=3, suppress=True)
def draw_grid(ax, xlim=(-5, 5), ylim=(-5, 5), spacing=1, color='lightgray', alpha=0.5):
"""Draw a coordinate grid"""
for x in np.arange(xlim[0], xlim[1]+1, spacing):
ax.plot([x, x], ylim, color=color, alpha=alpha, linewidth=0.5)
for y in np.arange(ylim[0], ylim[1]+1, spacing):
ax.plot(xlim, [y, y], color=color, alpha=alpha, linewidth=0.5)
def draw_basis_vectors(ax, i_hat, j_hat, color_i='green', color_j='red'):
"""Draw basis vectors"""
arrow_props = dict(arrowstyle='->', mutation_scale=20, linewidth=3)
# i-hat (typically horizontal)
arrow_i = FancyArrowPatch((0, 0), tuple(i_hat),
color=color_i, **arrow_props)
ax.add_patch(arrow_i)
ax.text(i_hat[0]*1.1, i_hat[1]*1.1, 'î', fontsize=16,
color=color_i, fontweight='bold')
# j-hat (typically vertical)
arrow_j = FancyArrowPatch((0, 0), tuple(j_hat),
color=color_j, **arrow_props)
ax.add_patch(arrow_j)
ax.text(j_hat[0]*1.1, j_hat[1]*1.1, 'ĵ', fontsize=16,
color=color_j, fontweight='bold')
def setup_ax(ax, xlim=(-5, 5), ylim=(-5, 5), title=''):
"""Setup axis properties"""
ax.set_xlim(xlim)
ax.set_ylim(ylim)
ax.set_aspect('equal')
ax.axhline(y=0, color='k', linewidth=0.8)
ax.axvline(x=0, color='k', linewidth=0.8)
ax.grid(True, alpha=0.3)
ax.set_xlabel('x', fontsize=12)
ax.set_ylabel('y', fontsize=12)
if title:
ax.set_title(title, fontsize=14, fontweight='bold')
# Example: Visualizing a transformation as movement
fig, axes = plt.subplots(1, 2, figsize=(16, 7))
# Before transformation
setup_ax(axes[0], title='Before: Original Space')
draw_grid(axes[0])
draw_basis_vectors(axes[0], np.array([1, 0]), np.array([0, 1]))
# Sample vectors
v = np.array([2, 1])
arrow_v = FancyArrowPatch((0, 0), tuple(v),
color='blue', arrowstyle='->',
mutation_scale=20, linewidth=2)
axes[0].add_patch(arrow_v)
axes[0].text(v[0]+0.2, v[1]+0.2, 'v', fontsize=14, color='blue', fontweight='bold')
# After transformation (rotation by 45°)
theta = np.pi/4
rotation_matrix = np.array([
[np.cos(theta), -np.sin(theta)],
[np.sin(theta), np.cos(theta)]
])
setup_ax(axes[1], title='After: Rotated 45°')
# Transform and draw grid
grid_x = np.arange(-5, 6, 1)
grid_y = np.arange(-5, 6, 1)
# Vertical grid lines (transformed)
for x in grid_x:
points = np.array([[x, y] for y in grid_y])
transformed = points @ rotation_matrix.T
axes[1].plot(transformed[:, 0], transformed[:, 1],
'lightgray', alpha=0.5, linewidth=0.5)
# Horizontal grid lines (transformed)
for y in grid_y:
points = np.array([[x, y] for x in grid_x])
transformed = points @ rotation_matrix.T
axes[1].plot(transformed[:, 0], transformed[:, 1],
'lightgray', alpha=0.5, linewidth=0.5)
# Transformed basis vectors
i_transformed = rotation_matrix @ np.array([1, 0])
j_transformed = rotation_matrix @ np.array([0, 1])
draw_basis_vectors(axes[1], i_transformed, j_transformed)
# Transformed vector
v_transformed = rotation_matrix @ v
arrow_v_t = FancyArrowPatch((0, 0), tuple(v_transformed),
color='blue', arrowstyle='->',
mutation_scale=20, linewidth=2)
axes[1].add_patch(arrow_v_t)
axes[1].text(v_transformed[0]+0.2, v_transformed[1]+0.2, 'v (moved)',
fontsize=14, color='blue', fontweight='bold')
plt.tight_layout()
plt.show()
print("Key Insight: A transformation moves EVERY point in space.")
print("Watch the grid lines - they show how space itself is morphing!")
2. What Makes a Transformation “Linear”?¶
A transformation is linear if it satisfies TWO properties:
Property 1: Lines remain lines¶
No curving allowed!
Grid lines must stay parallel and evenly spaced
Property 2: Origin stays fixed¶
The point (0, 0) doesn’t move
Mathematical Definition¶
A transformation \(L\) is linear if:
\(L(\vec{v} + \vec{w}) = L(\vec{v}) + L(\vec{w})\) (additivity)
\(L(c\vec{v}) = cL(\vec{v})\) (homogeneity)
Consequence¶
If you know where \(\hat{i}\) and \(\hat{j}\) land, you know where every vector lands!
# Compare linear vs non-linear transformations
fig, axes = plt.subplots(2, 3, figsize=(18, 12))
# Create dense grid of points
x = np.linspace(-3, 3, 20)
y = np.linspace(-3, 3, 20)
X, Y = np.meshgrid(x, y)
points = np.stack([X.flatten(), Y.flatten()], axis=1)
# LINEAR TRANSFORMATIONS (TOP ROW)
linear_transforms = [
('Rotation', np.array([[0, -1], [1, 0]])),
('Shear', np.array([[1, 1], [0, 1]])),
('Scaling', np.array([[2, 0], [0, 0.5]]))
]
for idx, (name, matrix) in enumerate(linear_transforms):
ax = axes[0, idx]
setup_ax(ax, title=f'LINEAR: {name}')
# Original grid (light)
draw_grid(ax, color='lightblue', alpha=0.3)
# Transformed grid
transformed = points @ matrix.T
X_t = transformed[:, 0].reshape(X.shape)
Y_t = transformed[:, 1].reshape(Y.shape)
# Draw transformed grid lines
for i in range(len(x)):
ax.plot(X_t[i, :], Y_t[i, :], 'gray', alpha=0.6, linewidth=1)
ax.plot(X_t[:, i], Y_t[:, i], 'gray', alpha=0.6, linewidth=1)
# Mark origin
ax.plot(0, 0, 'ko', markersize=8)
# Transformed basis
i_new = matrix @ np.array([1, 0])
j_new = matrix @ np.array([0, 1])
draw_basis_vectors(ax, i_new, j_new)
# NON-LINEAR TRANSFORMATIONS (BOTTOM ROW)
def quadratic_transform(p):
return np.array([p[0]**2 - p[1]**2, 2*p[0]*p[1]]) * 0.2
def translation(p):
return p + np.array([1, 1])
def circular(p):
r = np.sqrt(p[0]**2 + p[1]**2)
if r < 0.1:
return p
theta = np.arctan2(p[1], p[0])
return np.array([r * np.cos(theta + 0.3), r * np.sin(theta + 0.3)])
nonlinear_transforms = [
('Curved Lines', quadratic_transform),
('Moved Origin', translation),
('Circular Warp', circular)
]
for idx, (name, func) in enumerate(nonlinear_transforms):
ax = axes[1, idx]
setup_ax(ax, title=f'NON-LINEAR: {name}')
# Original grid (light)
draw_grid(ax, color='lightblue', alpha=0.3)
# Apply non-linear transformation
transformed = np.array([func(p) for p in points])
X_t = transformed[:, 0].reshape(X.shape)
Y_t = transformed[:, 1].reshape(Y.shape)
# Draw transformed grid lines
for i in range(len(x)):
ax.plot(X_t[i, :], Y_t[i, :], 'gray', alpha=0.6, linewidth=1)
ax.plot(X_t[:, i], Y_t[:, i], 'gray', alpha=0.6, linewidth=1)
# Mark where origin moved to
origin_transformed = func(np.array([0, 0]))
ax.plot(origin_transformed[0], origin_transformed[1],
'ro', markersize=8, label='Origin moved here')
ax.plot(0, 0, 'ko', markersize=5, alpha=0.3, label='Original origin')
if idx == 1:
ax.legend()
plt.tight_layout()
plt.show()
print("LINEAR transformations:")
print(" ✓ Grid lines stay STRAIGHT and PARALLEL")
print(" ✓ Origin stays PUT")
print(" ✓ Can be represented by a MATRIX")
print("\nNON-LINEAR transformations:")
print(" ✗ Lines can CURVE")
print(" ✗ Origin can MOVE")
print(" ✗ Cannot be represented by a matrix")
3. Tracking Basis Vectors¶
The Key Insight¶
For linear transformations, you only need to know where \(\hat{i}\) and \(\hat{j}\) go!
Why?¶
Because any vector \(\vec{v} = \begin{bmatrix} x \\ y \end{bmatrix}\) is really: $\(\vec{v} = x\hat{i} + y\hat{j}\)$
After transformation: $\(L(\vec{v}) = L(x\hat{i} + y\hat{j}) = xL(\hat{i}) + yL(\hat{j})\)$
Example¶
If \(\hat{i}\) lands at \(\begin{bmatrix} 1 \\ 2 \end{bmatrix}\) and \(\hat{j}\) lands at \(\begin{bmatrix} 3 \\ 0 \end{bmatrix}\),
then \(\begin{bmatrix} 5 \\ 7 \end{bmatrix}\) lands at: $\(5\begin{bmatrix} 1 \\ 2 \end{bmatrix} + 7\begin{bmatrix} 3 \\ 0 \end{bmatrix} = \begin{bmatrix} 26 \\ 10 \end{bmatrix}\)$
# Demonstrate: tracking basis vectors determines everything
fig, axes = plt.subplots(1, 3, figsize=(18, 6))
# Original space
setup_ax(axes[0], title='Original Space')
draw_grid(axes[0])
draw_basis_vectors(axes[0], np.array([1, 0]), np.array([0, 1]))
v = np.array([3, 2])
arrow_v = FancyArrowPatch((0, 0), tuple(v),
color='purple', arrowstyle='->',
mutation_scale=20, linewidth=3)
axes[0].add_patch(arrow_v)
axes[0].text(v[0]+0.3, v[1]+0.3, 'v = 3î + 2ĵ',
fontsize=14, color='purple', fontweight='bold')
# Show decomposition
axes[0].arrow(0, 0, 3, 0, head_width=0.15, head_length=0.15,
fc='green', ec='green', alpha=0.5, width=0.05)
axes[0].arrow(3, 0, 0, 2, head_width=0.15, head_length=0.15,
fc='red', ec='red', alpha=0.5, width=0.05)
axes[0].text(1.5, -0.5, '3î', fontsize=12, color='green')
axes[0].text(3.3, 1, '2ĵ', fontsize=12, color='red')
# Define a transformation
i_new = np.array([1, 2])
j_new = np.array([3, 0])
matrix = np.column_stack([i_new, j_new])
# Show where basis vectors go
setup_ax(axes[1], xlim=(-1, 5), ylim=(-1, 4),
title='Where do î and ĵ land?')
draw_grid(axes[1], color='lightblue', alpha=0.2)
draw_basis_vectors(axes[1], np.array([1, 0]), np.array([0, 1]),
color_i='lightgreen', color_j='lightcoral')
draw_basis_vectors(axes[1], i_new, j_new)
axes[1].text(i_new[0]+0.3, i_new[1]+0.3, "î lands here\n[1, 2]",
fontsize=11, color='green', fontweight='bold')
axes[1].text(j_new[0]+0.3, j_new[1]+0.3, "ĵ lands here\n[3, 0]",
fontsize=11, color='red', fontweight='bold')
# Show where v lands
setup_ax(axes[2], xlim=(-1, 10), ylim=(-1, 8),
title='Where does v = 3î + 2ĵ land?')
draw_grid(axes[2], color='lightblue', alpha=0.2)
draw_basis_vectors(axes[2], i_new, j_new)
# v lands at 3*i_new + 2*j_new
v_transformed = 3 * i_new + 2 * j_new
# Show the calculation visually
axes[2].arrow(0, 0, 3*i_new[0], 3*i_new[1],
head_width=0.2, head_length=0.2,
fc='green', ec='green', alpha=0.5, width=0.08)
axes[2].arrow(3*i_new[0], 3*i_new[1], 2*j_new[0], 2*j_new[1],
head_width=0.2, head_length=0.2,
fc='red', ec='red', alpha=0.5, width=0.08)
arrow_vt = FancyArrowPatch((0, 0), tuple(v_transformed),
color='purple', arrowstyle='->',
mutation_scale=20, linewidth=3)
axes[2].add_patch(arrow_vt)
axes[2].text(v_transformed[0]+0.3, v_transformed[1]+0.3,
f'v lands at\n{v_transformed}',
fontsize=14, color='purple', fontweight='bold')
axes[2].text(1.5, 2.5, '3î', fontsize=12, color='green', fontweight='bold')
axes[2].text(4.5, 6.5, '2ĵ', fontsize=12, color='red', fontweight='bold')
plt.tight_layout()
plt.show()
print("The Magic Formula:")
print(f"Original: v = 3î + 2ĵ = {v}")
print(f"After transformation: v = 3(new î) + 2(new ĵ)")
print(f" = 3{i_new} + 2{j_new}")
print(f" = {v_transformed}")
print("\n🎯 The coordinates stay the same! Only the basis vectors moved.")
4. From Transformation to Matrix¶
The Matrix Recipe¶
To create a matrix for a linear transformation:
See where \(\hat{i} = \begin{bmatrix} 1 \\ 0 \end{bmatrix}\) lands → first column
See where \(\hat{j} = \begin{bmatrix} 0 \\ 1 \end{bmatrix}\) lands → second column
Example¶
If \(\hat{i} \rightarrow \begin{bmatrix} 3 \\ -2 \end{bmatrix}\) and \(\hat{j} \rightarrow \begin{bmatrix} 2 \\ 1 \end{bmatrix}\):
# Build matrices from transformations
def transformation_to_matrix(i_new, j_new):
"""Convert transformed basis vectors to matrix"""
return np.column_stack([i_new, j_new])
# Example transformations
transformations = {
'90° Rotation': (
np.array([0, 1]), # î → [0, 1]
np.array([-1, 0]) # ĵ → [-1, 0]
),
'Shear': (
np.array([1, 1]), # î → [1, 1]
np.array([0, 1]) # ĵ → [0, 1]
),
'Stretch': (
np.array([3, 0]), # î → [3, 0]
np.array([0, 2]) # ĵ → [0, 2]
)
}
fig, axes = plt.subplots(2, 3, figsize=(18, 12))
for idx, (name, (i_new, j_new)) in enumerate(transformations.items()):
# Before
ax_before = axes[0, idx]
setup_ax(ax_before, title=f'{name}: BEFORE')
draw_grid(ax_before)
draw_basis_vectors(ax_before, np.array([1, 0]), np.array([0, 1]))
# Sample vector
v = np.array([2, 1])
arrow = FancyArrowPatch((0, 0), tuple(v),
color='blue', arrowstyle='->',
mutation_scale=15, linewidth=2)
ax_before.add_patch(arrow)
# After
ax_after = axes[1, idx]
setup_ax(ax_after, title=f'{name}: AFTER')
draw_basis_vectors(ax_after, i_new, j_new)
# Transform the grid
matrix = transformation_to_matrix(i_new, j_new)
for x in range(-5, 6):
line_points = np.array([[x, y] for y in range(-5, 6)])
transformed = line_points @ matrix.T
ax_after.plot(transformed[:, 0], transformed[:, 1],
'lightgray', alpha=0.5, linewidth=0.5)
for y in range(-5, 6):
line_points = np.array([[x, y] for x in range(-5, 6)])
transformed = line_points @ matrix.T
ax_after.plot(transformed[:, 0], transformed[:, 1],
'lightgray', alpha=0.5, linewidth=0.5)
# Transformed vector
v_new = matrix @ v
arrow_new = FancyArrowPatch((0, 0), tuple(v_new),
color='blue', arrowstyle='->',
mutation_scale=15, linewidth=2)
ax_after.add_patch(arrow_new)
# Display matrix
matrix_str = f"Matrix =\n{matrix}"
ax_after.text(0.02, 0.98, matrix_str,
transform=ax_after.transAxes,
fontsize=11, verticalalignment='top',
bbox=dict(boxstyle='round', facecolor='wheat', alpha=0.8),
family='monospace')
plt.tight_layout()
plt.show()
print("Matrix Construction Rule:")
print("Column 1 = where î lands")
print("Column 2 = where ĵ lands")
5. Matrix-Vector Multiplication (Geometrically)¶
Don’t Memorize - Understand!¶
Interpretation¶
The matrix columns tell you where \(\hat{i}\) and \(\hat{j}\) land
The vector \(\begin{bmatrix} x \\ y \end{bmatrix}\) means \(x\hat{i} + y\hat{j}\)
Multiply: take \(x\) of the first column + \(y\) of the second column
Example¶
# Visualize matrix-vector multiplication
def visualize_matrix_vector_mult(matrix, vector):
fig, axes = plt.subplots(1, 3, figsize=(20, 6))
# Step 1: Original vector
setup_ax(axes[0], xlim=(-1, 6), ylim=(-2, 5),
title=f'Step 1: Original v = {vector}')
draw_grid(axes[0])
draw_basis_vectors(axes[0], np.array([1, 0]), np.array([0, 1]))
arrow_v = FancyArrowPatch((0, 0), tuple(vector),
color='purple', arrowstyle='->',
mutation_scale=20, linewidth=3)
axes[0].add_patch(arrow_v)
axes[0].text(vector[0]+0.3, vector[1]+0.3,
f'v = {vector[0]}î + {vector[1]}ĵ',
fontsize=13, color='purple', fontweight='bold')
# Step 2: Show matrix transformation
i_new = matrix[:, 0]
j_new = matrix[:, 1]
setup_ax(axes[1], title=f'Step 2: Matrix = {matrix.tolist()}')
draw_grid(axes[1], color='lightblue', alpha=0.2)
draw_basis_vectors(axes[1], np.array([1, 0]), np.array([0, 1]),
color_i='lightgreen', color_j='lightcoral')
draw_basis_vectors(axes[1], i_new, j_new)
axes[1].text(0.02, 0.98,
f"î → {i_new}\nĵ → {j_new}",
transform=axes[1].transAxes,
fontsize=12, verticalalignment='top',
bbox=dict(boxstyle='round', facecolor='lightyellow'))
# Step 3: Result
result = matrix @ vector
setup_ax(axes[2], title=f'Step 3: Result = {result}')
draw_basis_vectors(axes[2], i_new, j_new)
# Show linear combination
comp1 = vector[0] * i_new
comp2 = vector[1] * j_new
axes[2].arrow(0, 0, comp1[0], comp1[1],
head_width=0.15, head_length=0.15,
fc='green', ec='green', alpha=0.5, width=0.05)
axes[2].text(comp1[0]/2, comp1[1]/2-0.3,
f'{vector[0]}î', fontsize=11, color='green')
axes[2].arrow(comp1[0], comp1[1], comp2[0], comp2[1],
head_width=0.15, head_length=0.15,
fc='red', ec='red', alpha=0.5, width=0.05)
axes[2].text(comp1[0]+comp2[0]/2+0.2, comp1[1]+comp2[1]/2,
f'{vector[1]}ĵ', fontsize=11, color='red')
arrow_result = FancyArrowPatch((0, 0), tuple(result),
color='purple', arrowstyle='->',
mutation_scale=20, linewidth=3)
axes[2].add_patch(arrow_result)
axes[2].text(result[0]+0.3, result[1]+0.3,
f'Result\n{result}',
fontsize=13, color='purple', fontweight='bold')
plt.tight_layout()
plt.show()
print(f"\nMatrix-Vector Multiplication:")
print(f"Matrix @ Vector = {matrix} @ {vector}")
print(f" = {vector[0]} * {i_new} + {vector[1]} * {j_new}")
print(f" = {comp1} + {comp2}")
print(f" = {result}")
# Example
M = np.array([[1, 2], [-1, 0]])
v = np.array([4, 3])
visualize_matrix_vector_mult(M, v)
6. Common Linear Transformations¶
Gallery of Transformations¶
# Gallery of common transformations
common_transformations = {
'Identity': np.array([[1, 0], [0, 1]]),
'Rotation 90°': np.array([[0, -1], [1, 0]]),
'Reflection (y-axis)': np.array([[-1, 0], [0, 1]]),
'Reflection (x=y line)': np.array([[0, 1], [1, 0]]),
'Horizontal Shear': np.array([[1, 1], [0, 1]]),
'Vertical Shear': np.array([[1, 0], [1, 1]]),
'Scale (2x, 0.5y)': np.array([[2, 0], [0, 0.5]]),
'Projection (x-axis)': np.array([[1, 0], [0, 0]])
}
fig, axes = plt.subplots(2, 4, figsize=(20, 10))
axes = axes.flatten()
# Test vector
test_vector = np.array([2, 1])
for idx, (name, matrix) in enumerate(common_transformations.items()):
ax = axes[idx]
setup_ax(ax, title=name)
# Original basis (light)
draw_basis_vectors(ax, np.array([1, 0]), np.array([0, 1]),
color_i='lightgreen', color_j='lightcoral')
# Original vector (light)
arrow_orig = FancyArrowPatch((0, 0), tuple(test_vector),
color='lightblue', arrowstyle='->',
mutation_scale=15, linewidth=1,
linestyle='--', alpha=0.5)
ax.add_patch(arrow_orig)
# Transformed grid
for x in range(-3, 4):
line = np.array([[x, y] for y in np.linspace(-3, 3, 20)])
transformed = line @ matrix.T
ax.plot(transformed[:, 0], transformed[:, 1],
'gray', alpha=0.3, linewidth=0.5)
for y in range(-3, 4):
line = np.array([[x, y] for x in np.linspace(-3, 3, 20)])
transformed = line @ matrix.T
ax.plot(transformed[:, 0], transformed[:, 1],
'gray', alpha=0.3, linewidth=0.5)
# Transformed basis
i_new = matrix @ np.array([1, 0])
j_new = matrix @ np.array([0, 1])
draw_basis_vectors(ax, i_new, j_new)
# Transformed vector
v_new = matrix @ test_vector
arrow_new = FancyArrowPatch((0, 0), tuple(v_new),
color='blue', arrowstyle='->',
mutation_scale=15, linewidth=2)
ax.add_patch(arrow_new)
# Show matrix
matrix_text = f"{matrix[0]}\n{matrix[1]}"
ax.text(0.98, 0.02, matrix_text,
transform=ax.transAxes,
fontsize=9, verticalalignment='bottom',
horizontalalignment='right',
bbox=dict(boxstyle='round', facecolor='white', alpha=0.8),
family='monospace')
plt.tight_layout()
plt.show()
print("Key Properties:")
print("• Identity: Does nothing (no change)")
print("• Rotation: Preserves lengths and angles")
print("• Reflection: Flips space across a line")
print("• Shear: Slants space in one direction")
print("• Scaling: Stretches or compresses")
print("• Projection: Collapses dimension (loses information)")
Summary¶
The Big Ideas¶
Linear transformations = functions that keep lines straight and fix the origin
Matrices = numerical representation of linear transformations
Matrix columns = where basis vectors land
Matrix-vector multiplication = applying the transformation
The Mental Picture¶
When you see \(\begin{bmatrix} a & b \\ c & d \end{bmatrix}\), think:
“\(\hat{i}\) goes to \(\begin{bmatrix} a \\ c \end{bmatrix}\) and \(\hat{j}\) goes to \(\begin{bmatrix} b \\ d \end{bmatrix}\)”
When you see \(A\vec{v}\), think:
“Where does \(\vec{v}\) land after the transformation described by \(A\)?”
Next Steps¶
Now we understand single transformations. What about doing two transformations in a row? That’s matrix multiplication!
Exercises¶
Draw what happens to the unit square under the transformation \(\begin{bmatrix} 2 & 1 \\ 0 & 1 \end{bmatrix}\)
Find the matrix for a 180° rotation
What transformation does \(\begin{bmatrix} 0 & 0 \\ 0 & 0 \end{bmatrix}\) represent?
Compute \(\begin{bmatrix} 3 & 1 \\ 2 & 2 \end{bmatrix} \begin{bmatrix} 2 \\ 5 \end{bmatrix}\) geometrically
Find the matrix that reflects across the line \(y = -x\)