A linear transformation in two dimensions can be visualized through its effect on the unit square defined by the two orthonormal basis vectors, $\boldsymbol{\hat{\imath}}$ and $\boldsymbol{\hat{\jmath}}$. In general, it can be represented by a $2\times 2$ matrix, $\mathbf{T}$, which acts on a vector $\boldsymbol{v}$ to map it from a vector space spanned by one basis onto a different vector space spanned by another basis: $\boldsymbol{v'} = \mathbf{T}\boldsymbol{v}$. Eigenvectors under such a transformation may be scaled but do not change orientation, as illustrated by the following code for the transformation matrix: \begin{align*} \mathbf{T} = \left( \begin{array}{rr} \frac{3}{2} & \frac{1}{2}\\ \frac{1}{2} & \frac{3}{2} \end{array} \right). \end{align*}
The effect of the transformation on a set of points in the Cartesian plane is also visualized in the output plot below.
import numpy as np
import matplotlib.pyplot as plt
# Set up a Cartesian grid of points.
XMIN, XMAX, YMIN, YMAX = -3, 3, -3, 3
N = 16
xgrid = np.linspace(XMIN, XMAX, N)
ygrid = np.linspace(YMIN, YMAX, N)
grid = np.array(np.meshgrid(xgrid, ygrid)).reshape(2, N**2)
# Our untransformed unit basis vectors, i and j:
basis = np.array([[1,0], [0,1]])
def plot_quadrilateral(basis, color='k'):
"""Plot the quadrilateral defined by the two basis vectors."""
ix, iy = basis[0]
jx, jy = basis[1]
plt.plot([0, ix, ix+jx, jx, 0], [0, iy, iy+jy, jy, 0], color)
def plot_vector(v, color='k', lw=1):
"""Plot vector v as a line with a specified color and linewidth."""
plt.plot([0, v[0]], [0, v[1]], c=color, lw=lw)
def plot_points(grid, color='k'):
"""Plot the grid points in a specified color."""
plt.scatter(*grid, c=color, s=2, alpha=0.5)
def apply_transformation(basis, T):
"""Return the transformed basis after applying transformation T."""
return (T @ basis.T).T
# The untransformed grid and unit square.
plot_points(grid)
plot_quadrilateral(basis)
# Apply the transformation matrix, S, to the scene.
S = np.array(((1.5, 0.5),(0.5, 1.5)))
tbasis = apply_transformation(basis, S)
plot_quadrilateral(tbasis, 'r')
tgrid = S @ grid
plot_points(tgrid, 'r')
# Find the eigenvalues and eigenvectors of S...
vals, vecs = np.linalg.eig(S)
print(vals, vecs)
if all(np.isreal(vals)):
# ... if they're all real, indicate them on the diagram.
v1, v2 = vals
e1, e2 = vecs.T
plot_vector(v1*e1, 'r', 3)
plot_vector(v2*e2, 'r', 3)
plot_vector(e1, 'k')
plot_vector(e2, 'k')
# Ensure the plot has 1:1 aspect (i.e. squares look square) and set the limits.
plt.axis('square')
plt.xlim(XMIN, XMAX)
plt.ylim(YMIN, YMAX)
plt.show()
Note that we need to reshape the meshgrid of $N\,\times\,N$ points into an array of $2\,\times\,N^2$ coordinates with:
grid = np.array(np.meshgrid(xgrid, ygrid)).reshape(2, N**2)
so that grid
can be transformed in a single line of code by the vectorized operation, S @ grid
.