3b1b-videos/_2024/linalg/eigenlecture.py
2024-11-30 10:09:15 -06:00

370 lines
12 KiB
Python

from manim_imports_ext import *
from _2021.matrix_exp import *
import matplotlib.pyplot as plt
def get_intensity_colors(values, cmap_name='viridis'):
"""
Convert a value between 0 and 1 to a color using a perceptually uniform colormap.
Args:
value (np.array): Array of values between 0 and 1
cmap_name (str): Name of colormap (default: 'viridis')
Returns:
np.array: RGB color values as (r,g,b) where each component is between 0 and 1
"""
cmap = plt.get_cmap(cmap_name)
return cmap(values)[:, :3] # Only return RGB, exclude alpha
class TexScratchPad(InteractiveScene):
def construct(self):
# ODE
tex = Tex(R"""
\left[\begin{array}{c} x'(t) \\ y'(t) \end{array}\right] =
\left[\begin{array}{cc} 1 & 2 \\ 3 & 1 \end{array}\right]
\left[\begin{array}{c} x(t) \\ y(t) \end{array}\right]
""")
# ODE in new coordinates
tex = Tex(
R"""
\left[\begin{array}{c} \tilde{x}'(t) \\ \tilde{y}'(t) \end{array}\right] =
\left[\begin{array}{cc} \lambda_1 & 0 \\ 0 & \lambda_2 \end{array}\right]
\left[\begin{array}{c} \tilde{x}(t) \\ \tilde{y}(t) \end{array}\right]
""",
t2c={R"\lambda_1": TEAL, R"\lambda_2": YELLOW}
)
# Solution to ODE in new coordinates
tex = Tex(
R"""
\tilde{x}(t) = \tilde{x}_0 e^{\lambda_1 t} \\
\tilde{y}(t) = \tilde{y}_0 e^{\lambda_2 t} \\
""",
t2c={R"\lambda_1": TEAL, R"\lambda_2": YELLOW}
)
self.add(tex)
# Meaning of the alternate coordinates
tex = Tex(
R"""
x \hat{\textbf{\i}} + y \hat{\textbf{\j}} =
\tilde{x} \vec{\textbf{v}}_1 + \tilde{y} \vec{\textbf{v}}_2
""",
t2c={
R"\lambda_1": TEAL,
R"\lambda_2": YELLOW,
R"\vec{\textbf{v}}_1": TEAL,
R"\vec{\textbf{v}}_2": YELLOW,
R"\hat{\textbf{\i}}": GREEN,
R"\hat{\textbf{\j}}": RED,
}
)
self.add(tex)
# Change of basis matrix
mat_tex = Tex(
R"""
\left[\begin{array}{cc} a & b \\ c & d \end{array}\right]
""",
)
old_cols = VGroup(
mat_tex[1:4:2],
mat_tex[2:5:2],
)
old_cols.set_opacity(0)
new_cols = VGroup(
Tex(R"\vec{\textbf{v}}_1").set_color(TEAL),
Tex(R"\vec{\textbf{v}}_2").set_color(YELLOW),
)
for new_col, old_col in zip(new_cols, old_cols):
lines = Line(UP, DOWN).set_height(0.35).replicate(2)
lines[0].next_to(new_col, UP, SMALL_BUFF)
lines[1].next_to(new_col, DOWN, SMALL_BUFF)
new_col.add(lines)
new_col.match_height(mat_tex)
new_col.move_to(old_col)
new_cols[1].align_to(new_cols[0], UP)
cob_matrix = VGroup(mat_tex, new_cols)
og_matrix = Tex(
R"""
\left[\begin{array}{cc} a & b \\ c & d \end{array}\right]
""",
)
inv_cob = cob_matrix.copy()
inv_cob.add(Tex(R"-1", font_size=24).next_to(inv_cob, RIGHT, SMALL_BUFF, aligned_edge=UP))
diag_matrix = Tex(
R"""
\left[\begin{array}{cc} \lambda_1 & 0 \\ 0 & \lambda_2 \end{array}\right]
""",
t2c={R"\lambda_1": TEAL, R"\lambda_2": YELLOW}
)
expr = VGroup(
inv_cob, og_matrix, cob_matrix,
Tex("="), diag_matrix
)
expr.arrange(RIGHT, buff=MED_SMALL_BUFF)
self.add(expr)
# Show powers
diag_matrix = Tex(
R"""
\left[\begin{array}{cc} \lambda_1 & 0 \\ 0 & \lambda_2 \end{array}\right]^n =
\left[\begin{array}{cc} \lambda_1^n & 0 \\ 0 & \lambda_2^n \end{array}\right]
""",
t2c={R"\lambda_1": TEAL, R"\lambda_2": YELLOW}
)
self.add(diag_matrix)
# Solve
diag_matrix = Tex(
R"""
\left[\begin{array}{cc} 0 & 1 \\ 1 & 1 \end{array}\right]^n =
\left[\begin{array}{cc} \lambda_1^n & 0 \\ 0 & \lambda_2^n \end{array}\right]
""",
t2c={R"\lambda_1": TEAL, R"\lambda_2": YELLOW}
)
self.add(diag_matrix)
# Write eigenvectors
eigen1_equation = Tex(
R"""
\left[\begin{array}{cc} 0 & 1 \\ 1 & 1 \end{array}\right]
\left[\begin{array}{c} 1 \\ \lambda_1 \end{array}\right] =
\left[\begin{array}{c} \lambda_1 \\ \lambda_1 + 1 \end{array}\right] =
\left[\begin{array}{c} \lambda_1 \\ \lambda_1^2 \end{array}\right] =
\lambda_1 \left[\begin{array}{c} 1 \\ \lambda_1 \end{array}\right] =
""",
t2c={R"\lambda_1": TEAL, R"\lambda_2": YELLOW}
)
eigen1 = Tex(
R"""
\vec{\textbf{v}}_1 = \left[\begin{array}{c} 1 \\ \lambda_1 \end{array}\right]
""",
t2c={R"\lambda_1": TEAL, R"\lambda_2": YELLOW}
)
eigen2 = Tex(
R"""
\vec{\textbf{v}}_2 = \left[\begin{array}{c} 1 \\ \lambda_2 \end{array}\right]
""",
t2c={R"\lambda_1": TEAL, R"\lambda_2": YELLOW}
)
self.add(eigen2)
# Eigen matrix
fib_cob = Tex(
R"""
S = \left[\begin{array}{cc} 1 & 1 \\ \lambda_1 & \lambda_2 \end{array}\right]
""",
t2c={R"\lambda_1": TEAL, R"\lambda_2": YELLOW}
)
self.add(fib_cob)
# Fibonacci expression
fib_expr = Tex(
R"""
A^n \left[\begin{array}{c} 0 \\ 1 \end{array}\right] =
\left[\begin{array}{c} F_n \\ F_{n + 1} \end{array}\right]
""",
t2c={R"\lambda_1": TEAL, R"\lambda_2": YELLOW}
)
self.add(fib_expr)
# Fib formula
fib_formula = Tex(
R"""
F_n = \frac{\lambda_1^n - \lambda_2^n}{\lambda_1 - \lambda_2}
""",
t2c={R"\lambda_1": TEAL, R"\lambda_2": YELLOW}
)
self.add(fib_formula)
# Translation
diag_matrix = Tex(
R"""
S \left[\begin{array}{cc} \lambda_1^n & 0 \\ 0 & \lambda_2^n \end{array}\right] S^{-1} =
A^n
""",
t2c={R"\lambda_1": TEAL, R"\lambda_2": YELLOW}
)
self.add(diag_matrix)
# Matrix
tex = Tex(R"""
\left[\begin{array}{cc} 3 & 1 \\ 0 & 2 \end{array}\right]
""")
tex[1:4:2].set_color(GREEN)
tex[2:5:2].set_color(RED)
self.add(tex)
# Basis
basis_syms = VGroup(
Tex(R"\hat{\textbf{\i}}").set_color(GREEN),
Tex(R"\hat{\textbf{\j}}").set_color(RED),
)
basis_syms.arrange(RIGHT)
self.add(basis_syms)
# Equation
tex = Tex(R"""
\left[\begin{array}{c} x \\ y \end{array}\right] =
\left[\begin{array}{c} 1 \\ 1 + \sqrt{5} \end{array}\right]
""", t2c={R"\lambda": TEAL})
self.add(tex)
# List bases
tex = Tex(R"""
\left[\begin{array}{c} 1 \\ 0 \end{array}\right],
\left[\begin{array}{c} 0 \\ 1 \end{array}\right]
""")
self.add(tex)
# Diagonal matrix
tex = Tex(
R"""
\left[\begin{array}{cc} \lambda_1 & 0 \\ 0 & \lambda_2 \end{array}\right]
""",
t2c={R"\lambda_1": TEAL, R"\lambda_2": YELLOW}
)
self.add(tex)
def get_vector_field_and_stream_lines(
func, coordinate_system,
# Vector config
vector_stroke_width=5,
vector_opacity=0.5,
density=4,
# Streamline config
sample_freq=5,
n_samples_per_line=10,
solution_time=1,
arc_len=3, # Does nothing
time_width=0.5,
line_color=WHITE,
line_width=3,
line_opacity=1.0,
):
# Vector field
vector_field = VectorField(
func, axes,
density=density,
stroke_width=vector_stroke_width,
stroke_opacity=vector_opacity,
)
# Streamlines
stream_lines = StreamLines(
func, axes,
density=sample_freq,
n_samples_per_line=n_samples_per_line,
solution_time=solution_time,
magnitude_range=vector_field.magnitude_range,
color_by_magnitude=False,
stroke_color=line_color,
stroke_width=line_width,
stroke_opacity=line_opacity,
)
animated_lines = AnimatedStreamLines(
stream_lines,
line_anim_config=dict(time_width=time_width),
rate_multiple=0.25,
)
return vector_field, animated_lines
class VectorFieldSolution(InteractiveScene):
def construct(self):
# Add axes
mat = np.array([[1, 2], [3, 1]])
# mat = np.array([[2, 0], [0, -1]])
axes = NumberPlane((-4, 4), (-2, 2), faded_line_ratio=1)
axes.set_height(FRAME_HEIGHT)
axes.background_lines.set_stroke(BLUE, 1)
axes.faded_lines.set_stroke(BLUE, 0.5, 0.5)
axes.add_coordinate_labels(font_size=36)
def func(v):
return 0.5 * np.dot(v, mat.T)
self.add(axes)
# Calculate eigen vectors
eigenvalues, eigenvectors = np.linalg.eig(mat)
eigenlines = VGroup(
Line(-v, v).set_length(10)
for v in eigenvectors.T
)
eigenlines.set_stroke(TEAL, 5)
# Show the flow
config = dict()
# config = dict(step_multiple=0.5, vector_stroke_width=8)
vector_field, animated_lines = get_vector_field_and_stream_lines(func, axes, **config)
self.add(vector_field, animated_lines)
vector_field.set_stroke(opacity=1)
self.play(vector_field.animate.set_stroke(opacity=0.5))
self.wait(10)
self.play(ShowCreation(eigenlines))
self.wait(10)
class Transformation(InteractiveScene):
def construct(self):
# Apply matrix
mat = np.array([[1, 2], [3, 1]])
ghost_plane = NumberPlane(faded_line_ratio=0)
ghost_plane.set_stroke(GREY, 1)
plane = self.get_plane()
basis = VGroup(
self.get_updated_vector((1, 0), plane, GREEN),
self.get_updated_vector((0, 1), plane, RED),
)
self.add(ghost_plane, plane, basis)
self.play(
plane.animate.apply_matrix(mat),
run_time=4
)
self.wait()
# Fade out
self.play(FadeOut(VGroup(ghost_plane, plane, basis)))
# Show matrix in new coordinate system
eigenvalues, eigenvectors = np.linalg.eig(mat)
eigenplane = self.get_plane()
eigenplane.apply_matrix(eigenvectors, about_point=ORIGIN)
eigenbasis = VGroup(
self.get_updated_vector((1, 0), eigenplane, TEAL),
self.get_updated_vector((0, 1), eigenplane, YELLOW),
)
self.add(eigenplane, eigenbasis)
self.play(
eigenplane.animate.apply_matrix(mat),
run_time=4
)
self.wait()
def get_plane(self, x_range=(-16, 16), y_range=(-8, 8)):
return NumberPlane(x_range, y_range, faded_line_ratio=1)
def get_updated_vector(self, coords, coord_system, color=YELLOW, thickness=4, **kwargs):
vect = Vector(RIGHT, fill_color=color, thickness=thickness, **kwargs)
vect.add_updater(lambda m: m.put_start_and_end_on(
coord_system.get_origin(),
coord_system.c2p(*coords),
))
return vect