Skip to content

Commit 9544c9f

Browse files
committed
add exercise to eigen and fix cross references
1 parent b8c7ee7 commit 9544c9f

File tree

4 files changed

+360
-9
lines changed

4 files changed

+360
-9
lines changed

lectures/eigen.md renamed to lectures/eigen_I.md

Lines changed: 343 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,10 @@ We will use the following imports:
5151
```{code-cell} ipython3
5252
import matplotlib.pyplot as plt
5353
import numpy as np
54+
from matplotlib.lines import Line2D
55+
from mpl_toolkits.mplot3d import Axes3D
56+
from matplotlib.patches import FancyArrowPatch
57+
from mpl_toolkits.mplot3d import proj3d
5458
```
5559

5660
(matrices_as_transformation)=
@@ -587,6 +591,8 @@ $$
587591
Let's first see examples of a sequence of iterates $(A^k v)_{k \geq 0}$ under
588592
different maps $A$.
589593

594+
(plot_series)=
595+
590596
```{code-cell} ipython3
591597
from numpy.linalg import matrix_power
592598
from matplotlib import cm
@@ -638,7 +644,7 @@ def plot_series(B, v, n):
638644
B = np.array([[sqrt(3) + 1, -2],
639645
[1, sqrt(3) - 1]])
640646
B = (1/(2*sqrt(2))) * B
641-
v = (-3,-3)
647+
v = (-3, -3)
642648
n = 12
643649
644650
plot_series(B, v, n)
@@ -652,7 +658,7 @@ In this case, repeatedly multiplying a vector by $A$ makes the vector "spiral in
652658
B = np.array([[sqrt(3) + 1, -2],
653659
[1, sqrt(3) - 1]])
654660
B = (1/2) * B
655-
v = (2.5,0)
661+
v = (2.5, 0)
656662
n = 12
657663
658664
plot_series(B, v, n)
@@ -667,7 +673,7 @@ an ellipse".
667673
B = np.array([[sqrt(3) + 1, -2],
668674
[1, sqrt(3) - 1]])
669675
B = (1/sqrt(2)) * B
670-
v = (-1,-0.25)
676+
v = (-1, -0.25)
671677
n = 6
672678
673679
plot_series(B, v, n)
@@ -834,4 +840,337 @@ to one.
834840

835841
The eigenvectors and eigenvalues of a map $A$ determine how a vector $v$ is transformed when we repeatedly multiply by $A$.
836842

837-
This is discussed further later.
843+
This is discussed further later.
844+
845+
846+
```{exercise}
847+
:label: eig1_ex1
848+
849+
Power iteration is a method for finding the largest absolute eigenvalue of a diagnalizable matrix.
850+
851+
The method starts with a random vector $b_0$ and repeatedly applies the matrix $A$ to it
852+
853+
$$
854+
b_{k+1}=\frac{A b_k}{\left\|A b_k\right\|}
855+
$$
856+
857+
A thorough discussion of the method can be found [here](https://pythonnumericalmethods.berkeley.edu/notebooks/chapter15.02-The-Power-Method.html).
858+
859+
In this exercise, implement the power iteration method and use it to find the largest eigenvalue of the matrix.
860+
861+
Visualize your results by plotting the eigenvalue as a function of the number of iterations.
862+
```
863+
864+
```{solution-start} eig1_ex1
865+
:class: dropdown
866+
```
867+
868+
```{code-cell} ipython3
869+
# Define a matrix A
870+
A = np.array([[1, 0, 3],
871+
[0, 2, 0],
872+
[3, 0, 1]])
873+
874+
# Define a number of iterations
875+
num_iters = 20
876+
877+
# Define a random starting vector b
878+
b = np.random.rand(A.shape[1])
879+
880+
# Initialize a list to store the eigenvector approximations
881+
res = []
882+
883+
# Power iteration loop
884+
for i in range(num_iters):
885+
# Multiply b by A
886+
b = A @ b
887+
# Normalize b
888+
b = b / np.linalg.norm(b)
889+
# Append b to the list of eigenvector approximations
890+
dis = np.linalg.norm(np.array(b) - np.linalg.eig(A)[1][:, 0])
891+
res.append(dis)
892+
893+
# Plot the eigenvector approximations for each iteration
894+
plt.figure(figsize=(10, 6))
895+
plt.xlabel('Iterations')
896+
plt.ylabel('L2 Norm')
897+
plt.title('Power Iteration and Eigenvector Approximations')
898+
_ = plt.plot(res)
899+
```
900+
901+
```{code-cell} ipython3
902+
import numpy as np
903+
import matplotlib.pyplot as plt
904+
from mpl_toolkits.mplot3d import Axes3D
905+
906+
# Define a matrix A
907+
A = np.array([[1, 0, 3],
908+
[0, 2, 0],
909+
[3, 0, 1]])
910+
911+
# Define a number of iterations
912+
num_iters = 20
913+
914+
# Define a random starting vector b
915+
b = np.array([0.5, 1, 0.5])
916+
917+
# Initialize a list to store the eigenvector approximations
918+
res = [b]
919+
920+
# Power iteration loop
921+
for i in range(num_iters):
922+
# Multiply b by A
923+
b = A @ b
924+
# Normalize b
925+
b = b / np.linalg.norm(b)
926+
# Append b to the list of eigenvector approximations
927+
res.append(b)
928+
929+
# Get the actual eigenvectors of matrix A
930+
eigenvector = np.linalg.eig(A)[1][:, 0]
931+
# Set up the figure and axis for 3D plot
932+
fig = plt.figure()
933+
ax = fig.add_subplot(111, projection='3d')
934+
935+
# Plot the actual eigenvectors
936+
937+
ax.scatter(eigenvector[0], eigenvector[1], eigenvector[2], color='r', s = 80)
938+
939+
# Plot the approximated eigenvectors (b) at each iteration
940+
for i, vec in enumerate(res):
941+
ax.scatter(vec[0], vec[1], vec[2], color='b', alpha=(i + 1) / (num_iters+1), s = 80)
942+
943+
ax.set_xlabel('X')
944+
ax.set_ylabel('Y')
945+
ax.set_zlabel('Z')
946+
ax.set_title('Power iteration eigenvector approximations')
947+
points = [plt.Line2D([0], [0], linestyle='none', c=i, marker='o') for i in ['r', 'b']]
948+
ax.legend(points, ['Actual eigenvectors', 'Approximated eigenvectors (b)'], numpoints=1)
949+
ax.set_box_aspect(aspect=None, zoom=0.8)
950+
951+
# Show the plot
952+
plt.show()
953+
```
954+
955+
```{solution-end}
956+
```
957+
958+
```{exercise}
959+
:label: eig1_ex2
960+
961+
We have discussed the trajectory of the vector $v$ after being transformed by $A$.
962+
963+
Consider the matrix $A = \begin{bmatrix} 1 & 2 \\ 1 & 1 \end{bmatrix}$ and the vector $v = \begin{bmatrix} 2 \\ -2 \end{bmatrix}$.
964+
965+
Try to compute the trajectory of $v$ after being transformed by $A$ for $n=6$ iterations and plot the result.
966+
967+
```
968+
969+
970+
```{solution-start} eig1_ex2
971+
:class: dropdown
972+
```
973+
974+
```{code-cell} ipython3
975+
A = np.array([[1, 2],
976+
[1, 1]])
977+
v = (0.4, -0.4)
978+
n = 11
979+
980+
# Compute right eigenvectors and eigenvalues
981+
eigenvalues, eigenvectors = np.linalg.eig(A)
982+
983+
print(f"eigenvalues:\n {eigenvalues}")
984+
print(f"eigenvectors:\n {eigenvectors}")
985+
986+
plot_series(A, v, n)
987+
```
988+
989+
We find the trajectory of the vector $v$ after being transformed by $A$ for $n=6$ iterations and plot the result seems to converge to the eigenvector of $A$ with the largest eigenvalue.
990+
991+
Let's use a vector field to visualize the transformation brought by A.
992+
993+
```{code-cell} ipython3
994+
# Create a grid of points (vector field)
995+
x, y = np.meshgrid(np.linspace(-5, 5, 15), np.linspace(-5, 5, 20))
996+
997+
# Apply the matrix A to each point in the vector field
998+
vec_field = np.stack([x, y])
999+
u, v = np.tensordot(A, vec_field, axes=1)
1000+
1001+
1002+
# Plot the transformed vector field
1003+
c = plt.streamplot(x, y, u - x, v - y, density=1, linewidth=None, color='#A23BEC')
1004+
c.lines.set_alpha(0.5)
1005+
c.arrows.set_alpha(0.5)
1006+
1007+
# Plot the eigenvectors as long blue and green arrows
1008+
origin = np.zeros((2, len(eigenvectors)))
1009+
plt.quiver(*origin, eigenvectors[0], eigenvectors[1], color=['b', 'g'], angles='xy', scale_units='xy', scale=0.1, width=0.01)
1010+
plt.quiver(*origin, - eigenvectors[0], - eigenvectors[1], color=['b', 'g'], angles='xy', scale_units='xy', scale=0.1, width=0.01)
1011+
1012+
colors = ['b', 'g']
1013+
lines = [Line2D([0], [0], color=c, linewidth=3) for c in colors]
1014+
labels = ["2.4 eigenspace", "0.4 eigenspace"]
1015+
plt.legend(lines, labels,loc='center left', bbox_to_anchor=(1, 0.5))
1016+
1017+
plt.title("Convergence/Divergence towards Eigenvectors")
1018+
plt.xlabel("x-axis")
1019+
plt.ylabel("y-axis")
1020+
plt.grid()
1021+
plt.gca().set_aspect('equal', adjustable='box')
1022+
plt.show()
1023+
```
1024+
1025+
Note that the vector field converges to the eigenvector of $A$ with the largest eigenvalue and diverges from the eigenvector of $A$ with the smallest eigenvalue.
1026+
1027+
In fact, the eigenvectors are also the directions in which the matrix $A$ stretches or shrinks the space.
1028+
1029+
Specifically, the eigenvector with the largest eigenvalue is the direction in which the matrix $A$ stretches the space the most.
1030+
1031+
We will see more intriguing examples of eigenvectors in the following exercise.
1032+
1033+
```{solution-end}
1034+
```
1035+
1036+
```{exercise}
1037+
:label: eig1_ex3
1038+
1039+
{ref}`Previously <plot_series>`, we demonstrated the trajectory of the vector $v$ after being transformed by $A$ for three different matrices.
1040+
1041+
Use the visualization in the previous exercise to explain why the trajectory of the vector $v$ after being transformed by $A$ for the three different matrices.
1042+
1043+
```
1044+
1045+
1046+
```{solution-start} eig1_ex3
1047+
:class: dropdown
1048+
```
1049+
1050+
Here is one solution
1051+
1052+
```{code-cell} ipython3
1053+
import numpy as np
1054+
import matplotlib.pyplot as plt
1055+
from matplotlib.lines import Line2D
1056+
1057+
figure, ax = plt.subplots(1,3, figsize = (15,5))
1058+
A = np.array([[sqrt(3) + 1, -2],
1059+
[1, sqrt(3) - 1]])
1060+
A = (1/(2*sqrt(2))) * A
1061+
1062+
B = np.array([[sqrt(3) + 1, -2],
1063+
[1, sqrt(3) - 1]])
1064+
B = (1/2) * B
1065+
1066+
C = np.array([[sqrt(3) + 1, -2],
1067+
[1, sqrt(3) - 1]])
1068+
C = (1/sqrt(2)) * C
1069+
1070+
examples = [A, B, C]
1071+
1072+
for i, example in enumerate(examples):
1073+
M = example
1074+
1075+
# Compute right eigenvectors and eigenvalues
1076+
eigenvalues, eigenvectors = np.linalg.eig(M)
1077+
print(f'Example {i+1}:\n')
1078+
print(f'eigenvalues:\n {eigenvalues}')
1079+
print(f'eigenvectors:\n {eigenvectors}\n')
1080+
1081+
eigenvalues_real = eigenvalues.real
1082+
eigenvectors_real = eigenvectors.real
1083+
1084+
# Create a grid of points (vector field)
1085+
x, y = np.meshgrid(np.linspace(-20, 20, 15), np.linspace(-20, 20, 20))
1086+
1087+
# Apply the matrix A to each point in the vector field
1088+
vec_field = np.stack([x, y])
1089+
u, v = np.tensordot(M, vec_field, axes=1)
1090+
1091+
# Plot the transformed vector field
1092+
c = ax[i].streamplot(x, y, u - x, v - y, density=1, linewidth=None, color='#A23BEC')
1093+
c.lines.set_alpha(0.5)
1094+
c.arrows.set_alpha(0.5)
1095+
1096+
# Plot the eigenvectors as long blue and green arrows
1097+
parameters = {'color':['b', 'g'], 'angles':'xy', 'scale_units':'xy', 'scale':1, 'width':0.01, 'alpha':0.5}
1098+
origin = np.zeros((2, len(eigenvectors)))
1099+
ax[i].quiver(*origin, eigenvectors_real[0], eigenvectors_real[1], **parameters)
1100+
ax[i].quiver(*origin, - eigenvectors_real[0], - eigenvectors_real[1], **parameters)
1101+
1102+
ax[i].set_xlabel("x-axis")
1103+
ax[i].set_ylabel("y-axis")
1104+
ax[i].grid()
1105+
ax[i].set_aspect('equal', adjustable='box')
1106+
1107+
plt.show()
1108+
```
1109+
1110+
The pattern demonstrated here is because we have complex eigenvalues and eigenvectors.
1111+
1112+
It is important to acknowledge that there is a complex plane.
1113+
1114+
If we add the complex axis for the plot, the plot will be more complicated.
1115+
1116+
Here we used the real part of the eigenvalues and eigenvectors.
1117+
1118+
We can try to plot the complex plane for one of the matrix using `Arrow3D` class retrieved from [stackoverflow](https://stackoverflow.com/questions/22867620/putting-arrowheads-on-vectors-in-matplotlibs-3d-plot).
1119+
1120+
```{code-cell} ipython3
1121+
class Arrow3D(FancyArrowPatch):
1122+
def __init__(self, xs, ys, zs, *args, **kwargs):
1123+
super().__init__((0,0), (0,0), *args, **kwargs)
1124+
self._verts3d = xs, ys, zs
1125+
1126+
def do_3d_projection(self, renderer=None):
1127+
xs3d, ys3d, zs3d = self._verts3d
1128+
xs, ys, zs = proj3d.proj_transform(xs3d, ys3d, zs3d, self.axes.M)
1129+
self.set_positions((0.1*xs[0],0.1*ys[0]),(0.1*xs[1],0.1*ys[1]))
1130+
1131+
return np.min(zs)
1132+
1133+
1134+
# Define matrix A with complex eigenvalues
1135+
A = np.array([[sqrt(3) + 1, -2],
1136+
[1, sqrt(3) - 1]])
1137+
A = (1/(2*sqrt(2))) * A
1138+
1139+
# Find eigenvalues and eigenvectors
1140+
eigenvalues, eigenvectors = np.linalg.eig(A)
1141+
1142+
# Create meshgrid for vector field
1143+
x, y = np.meshgrid(np.linspace(-2, 2, 10), np.linspace(-2, 2, 10))
1144+
1145+
# Calculate vector field (real and imaginary parts)
1146+
u_real = A[0][0] * x + A[0][1] * y
1147+
v_real = A[1][0] * x + A[1][1] * y
1148+
u_imag = np.zeros_like(x)
1149+
v_imag = np.zeros_like(y)
1150+
1151+
# Create 3D figure
1152+
fig = plt.figure()
1153+
ax = fig.add_subplot(111, projection='3d')
1154+
vlength = np.linalg.norm(eigenvectors)
1155+
ax.quiver(x, y, u_imag, u_real-x, v_real-y, v_imag-u_imag, colors = 'b', alpha=0.3, length = .2, arrow_length_ratio = 0.01)
1156+
1157+
arrow_prop_dict = dict(mutation_scale=2, arrowstyle='-|>', shrinkA=0, shrinkB=0)
1158+
1159+
# Plot 3D eigenvectors
1160+
for c, i in zip(['b', 'g'], [0, 1]):
1161+
a = Arrow3D([0, eigenvectors[0][i].real], [0, eigenvectors[1][i].real],
1162+
[0, eigenvectors[1][i].imag], color=c, **arrow_prop_dict)
1163+
ax.add_artist(a)
1164+
1165+
# Set axis labels and title
1166+
ax.set_xlabel('X')
1167+
ax.set_ylabel('Y')
1168+
ax.set_zlabel('Im')
1169+
ax.set_box_aspect(aspect=None, zoom=0.8)
1170+
1171+
plt.draw()
1172+
plt.show()
1173+
```
1174+
1175+
```{solution-end}
1176+
```

0 commit comments

Comments
 (0)