矩阵在线性代数中无处不在。矩阵的列描述了相应的基向量相对于初始基的位置。所有变换后的向量都是变换后的基向量的线性组合它们是矩阵的列,这也被称为线性。对矩阵进行操作的算法本质上只是改变了向量变换的方式,保留了一些性质。
from manim import * # 导入 Manim 库
class LinearTransformation3D02(ThreeDScene): # 定义一个名为 LinearTransformation3D02 的类,继承 ThreeDScene
def create_matrix(self, np_matrix): # 定义一个方法,用于创建矩阵对象
m = Matrix(np_matrix) # 创建一个矩阵 m
m.scale(0.5) # 将矩阵缩放到原来的一半大小
m.set_column_colors(GREEN, RED, GOLD) # 设置矩阵列的颜色
m.to_corner(UP + LEFT) # 将矩阵移动到屏幕的左上角
return m # 返回创建的矩阵对象
def construct(self): # 定义构建场景的方法
basis_i_color = GREEN # 设置 i 向量的颜色为绿色
basis_j_color = RED # 设置 j 向量的颜色为红色
basis_k_color = GOLD # 设置 k 向量的颜色为金色
M = np.array([ # 创建一个变换矩阵 M
[2, 2, -1],
[-2, 1, 2],
[3, 1, 0]
])
axes = ThreeDAxes() # 创建三维坐标轴对象
axes.set_color(GRAY) # 设置坐标轴的颜色为灰色
axes.add(axes.get_axis_labels()) # 为坐标轴添加轴标签
self.set_camera_orientation(phi=75 * DEGREES, theta=-45 * DEGREES) # 设置相机的旋转角度
basis_vector_helper = Tex("$i$" , "," , "$j$" , "," , "$k$") # 创建基础向量标签
basis_vector_helper[0].set_color(basis_i_color) # 设置 i 向量标签的颜色
basis_vector_helper[2].set_color(basis_j_color) # 设置 j 向量标签的颜色
basis_vector_helper[4].set_color(basis_k_color) # 设置 k 向量标签的颜色
basis_vector_helper.to_corner(UP + RIGHT) # 将标签移动到右上角
self.add_fixed_in_frame_mobjects(basis_vector_helper) # 将基础向量标签固定在屏幕上
matrix = self.create_matrix(M) # 创建矩阵对象
self.add_fixed_in_frame_mobjects(matrix) # 将矩阵固定在屏幕上
self.add(axes) # 添加坐标轴到场景
self.begin_ambient_camera_rotation(rate=0.2) # 开始相机的环绕旋转
cube = Cube(side_length=1, fill_color=BLUE, stroke_width=2, fill_opacity=0.1) # 创建透明的蓝色正方体
cube.set_stroke(BLUE_E) # 设置正方体的边框颜色
i_vec = Vector(np.array([1, 0, 0]), color=basis_i_color) # 创建 i 向量
j_vec = Vector(np.array([0, 1, 0]), color=basis_j_color) # 创建 j 向量
k_vec = Vector(np.array([0, 0, 1]), color=basis_k_color) # 创建 k 向量
i_vec_new = Vector(M @ np.array([1, 0, 0]), color=basis_i_color) # 计算变换后的 i 向量
j_vec_new = Vector(M @ np.array([0, 1, 0]), color=basis_j_color) # 计算变换后的 j 向量
k_vec_new = Vector(M @ np.array([0, 0, 1]), color=basis_k_color) # 计算变换后的 k 向量
self.play( # 播放初始动画
Create(cube), # 创建正方体动画
GrowArrow(i_vec), # 显示 i 向量动画
GrowArrow(j_vec), # 显示 j 向量动画
GrowArrow(k_vec), # 显示 k 向量动画
Write(basis_vector_helper) # 显示基础向量标签动画
)
self.wait(2) # 等待 2 秒以展示初始状态
# 变换 i 向量
matrix_anim_i = ApplyMatrix(M, cube) # 创建应用矩阵 M 的动画
self.play(
matrix_anim_i, # 应用矩阵的动画
Transform(i_vec, i_vec_new) # 变换 i 向量到新的位置
)
self.wait(2) # 等待 2 秒
# 变换 j 向量
matrix_anim_j = ApplyMatrix(M, cube) # 创建应用矩阵 M 的动画
self.play(
matrix_anim_j, # 应用矩阵的动画
Transform(j_vec, j_vec_new) # 变换 j 向量到新的位置
)
self.wait(2) # 等待 2 秒
# 变换 k 向量
matrix_anim_k = ApplyMatrix(M, cube) # 创建应用矩阵 M 的动画
self.play(
matrix_anim_k, # 应用矩阵的动画
Transform(k_vec, k_vec_new) #
Transform(k_vec, k_vec_new) # 变换 k 向量到新的位置
)
self.wait(2) # 等待 2 秒
# 将正方体移动到新的 i、j 和 k 向量方向对应的位置
new_position = (i_vec_new.get_end() + j_vec_new.get_end() + k_vec_new.get_end()) / 3 # 计算正方体的新位置
self.play(cube.animate.move_to(new_position)) # 移动正方体到新位置
# 最后等待几秒钟以展示最终效果
self.wait(7) # 等待 7 秒以展示最终状态
代码解释
-
向量变换:
- 在每次动画中,
ApplyMatrix(M, cube)
将变换矩阵M
应用于立方体cube
,展示了基于矩阵变换后的三维形状变化。 Transform(i_vec, i_vec_new)
类似地将旧的向量变换为新的向量。这个过程重复了三次,分别对应了i
、j
和k
向量的变化。
- 在每次动画中,
-
新位置计算:
- 在所有的向量都完成变换后,代码计算了正方体的新位置,这个新位置是所有三维向量的终点的平均值。这意味着正方体会中心对齐到建立的新基座。
-
节奏控制:
self.wait(2)
和self.wait(7)
用于控制每个动画阶段之间的间隔以及展示效果。这样使得观众有足够的时间来理解每个变换过程。
示例代码2:
在animm中,有一个特殊的ApplyMatrix动画,允许我们原生地将矩阵应用到对象的每个3D顶点。
from manimlib.imports import *
class LinearTransformation3D(ThreeDScene):
CONFIG = {
"x_axis_label": "$x$",
"y_axis_label": "$y$",
"basis_i_color": GREEN,
"basis_j_color": RED,
"basis_k_color": GOLD
}
def create_matrix(self, np_matrix):
m = Matrix(np_matrix)
m.scale(0.5)
m.set_column_colors(self.basis_i_color, self.basis_j_color, self.basis_k_color)
m.to_corner(UP + LEFT)
return m
def construct(self):
M = np.array([
[2, 2, -1],
[-2, 1, 2],
[3, 1, -0]
])
axes = ThreeDAxes()
axes.set_color(GRAY)
axes.add(axes.get_axis_labels())
self.set_camera_orientation(phi=75 * DEGREES, theta=-45 * DEGREES)
# basis vectors i,j,k
basis_vector_helper = TextMobject("$i$", ",", "$j$", ",", "$k$")
basis_vector_helper[0].set_color(self.basis_i_color)
basis_vector_helper[2].set_color(self.basis_j_color)
basis_vector_helper[4].set_color(self.basis_k_color)
basis_vector_helper.to_corner(UP + RIGHT)
self.add_fixed_in_frame_mobjects(basis_vector_helper)
# matrix
matrix = self.create_matrix(M)
self.add_fixed_in_frame_mobjects(matrix)
# axes & camera
self.add(axes)
self.begin_ambient_camera_rotation(rate=0.2)
cube = Cube(side_length=1, fill_color=BLUE, stroke_width=2, fill_opacity=0.1)
cube.set_stroke(BLUE_E)
i_vec = Vector(np.array([1, 0, 0]), color=self.basis_i_color)
j_vec = Vector(np.array([0, 1, 0]), color=self.basis_j_color)
k_vec = Vector(np.array([0, 0, 1]), color=self.basis_k_color)
i_vec_new = Vector(M @ np.array([1, 0, 0]), color=self.basis_i_color)
j_vec_new = Vector(M @ np.array([0, 1, 0]), color=self.basis_j_color)
k_vec_new = Vector(M @ np.array([0, 0, 1]), color=self.basis_k_color)
self.play(
ShowCreation(cube),
GrowArrow(i_vec),
GrowArrow(j_vec),
GrowArrow(k_vec),
Write(basis_vector_helper)
)
self.wait()
matrix_anim = ApplyMatrix(M, cube)
self.play(
matrix_anim,
Transform(i_vec, i_vec_new, rate_func=matrix_anim.get_rate_func(),
run_time=matrix_anim.get_run_time()),
Transform(j_vec, j_vec_new, rate_func=matrix_anim.get_rate_func(),
run_time=matrix_anim.get_run_time()),
Transform(k_vec, k_vec_new, rate_func=matrix_anim.get_rate_func(),
run_time=matrix_anim.get_run_time())
)
self.wait()
self.wait(7)
示例代码3:
在上一篇文章中,我们实现了一个高级版本的高斯消去算法(PA = LU分解)。我们可以用下面的方法类似地实现纯高斯消去算法:
def gauss(a):
m = a.shape[0]
for x in range(m):
pivotRow = x
# search for the best pivot
for y in range(x + 1, m, 1):
if abs(a[y][x]) > abs(a[pivotRow][x]):
pivotRow = y
if a[pivotRow][x] == 0:
# we didn't find any row with a non-zero leading coefficient
# that means that the matrix has all zeroes in this column
# so we don't need to search for pivots after all for the current column x
continue
# did we just use a pivot that is not on the diagonal?
if pivotRow != x:
# swap the pivot row with the current row in both A and L matrices
a[[x, pivotRow]] = a[[pivotRow, x]]
yield a
# now the pivot row is x
# search for rows where the leading coefficient must be eliminated
for y in range(x + 1, m, 1):
currentValue = a[y][x]
if currentValue == 0:
# variable already eliminated, nothing to do
continue
pivot = a[x][x]
assert pivot != 0 # just in case, we already made sure the pivot is not zero
pivotFactor = currentValue / pivot
# subtract the pivot row from the current row
a[y][x] = 0
for i in range(x + 1, m, 1):
a[y][i] -= pivotFactor * a[x][i]
yield a
注意,在我们想要可视化的每个矩阵运算之后,我都添加了一个yield语句。Python中的生成器非常适合可视化算法,因为它们允许我们保存当前状态,生成值,然后在调用者处理接收到的值时继续执行。在这种情况下,我们在算法运行时生成矩阵的每个版本。
现在我们可以调整和扩展用于呈现线性变换的代码,以便在高斯消去的每一步都这样做:
class Gauss3D(ThreeDScene):
CONFIG = {
"x_axis_label": "$x$",
"y_axis_label": "$y$",
"basis_i_color": GREEN,
"basis_j_color": RED,
"basis_k_color": GOLD
}
def create_matrix(self, np_matrix):
m = Matrix(np_matrix)
m.scale(0.5)
m.set_column_colors(self.basis_i_color, self.basis_j_color, self.basis_k_color)
m.to_corner(UP + LEFT)
return m
def construct(self):
M = np.array([
[-1.0, 1.0, -2.0],
[-4.0, -2.0, 1.0],
[-2.0, 2.0, 3.0]
])
# axes
axes = ThreeDAxes()
axes.set_color(GRAY)
axes.add(axes.get_axis_labels())
self.set_camera_orientation(phi=55 * DEGREES, theta=-45 * DEGREES)
# basis vectors i,j,k
basis_vector_helper = TextMobject("$i$", ",", "$j$", ",", "$k$")
basis_vector_helper[0].set_color(self.basis_i_color)
basis_vector_helper[2].set_color(self.basis_j_color)
basis_vector_helper[4].set_color(self.basis_k_color)
basis_vector_helper.to_corner(UP + RIGHT)
self.add_fixed_in_frame_mobjects(basis_vector_helper)
# matrix
matrix = self.create_matrix(M)
self.add_fixed_in_frame_mobjects(matrix)
# axes & camera
self.add(axes)
self.begin_ambient_camera_rotation(rate=0.15)
cube = Cube(side_length=1, fill_color=BLUE, stroke_width=2, fill_opacity=0.1)
cube.set_stroke(BLUE_E) # cube.set_stroke(TEAL_E)
i_vec = Vector(np.array([1, 0, 0]), color=self.basis_i_color)
j_vec = Vector(np.array([0, 1, 0]), color=self.basis_j_color)
k_vec = Vector(np.array([0, 0, 1]), color=self.basis_k_color)
i_vec_new = Vector(M @ np.array([1, 0, 0]), color=self.basis_i_color)
j_vec_new = Vector(M @ np.array([0, 1, 0]), color=self.basis_j_color)
k_vec_new = Vector(M @ np.array([0, 0, 1]), color=self.basis_k_color)
self.play(
ShowCreation(cube),
GrowArrow(i_vec),
GrowArrow(j_vec),
GrowArrow(k_vec),
Write(basis_vector_helper)
)
self.wait()
matrix_anim = ApplyMatrix(M, cube)
self.play(
matrix_anim,
ReplacementTransform(i_vec, i_vec_new, rate_func=matrix_anim.get_rate_func(),
run_time=matrix_anim.get_run_time()),
ReplacementTransform(j_vec, j_vec_new, rate_func=matrix_anim.get_rate_func(),
run_time=matrix_anim.get_run_time()),
ReplacementTransform(k_vec, k_vec_new, rate_func=matrix_anim.get_rate_func(),
run_time=matrix_anim.get_run_time())
)
self.wait()
i_vec, j_vec, k_vec = i_vec_new, j_vec_new, k_vec_new
self.wait(2)
for a in gauss(M):
a_rounded = np.round(a.copy(), 2)
self.remove(matrix)
matrix = self.create_matrix(a_rounded)
self.add_fixed_in_frame_mobjects(matrix)
# transformed cube
new_cube = Cube(side_length=1, fill_color=BLUE, stroke_width=2, fill_opacity=0.1)
new_cube.set_stroke(BLUE_E)
new_cube.apply_matrix(a)
# vectors
i_vec_new = Vector(a @ np.array([1, 0, 0]), color=self.basis_i_color)
j_vec_new = Vector(a @ np.array([0, 1, 0]), color=self.basis_j_color)
k_vec_new = Vector(a @ np.array([0, 0, 1]), color=self.basis_k_color)
# prepare and run animation
cube_anim = ReplacementTransform(cube, new_cube)
self.play(
cube_anim,
ReplacementTransform(i_vec, i_vec_new, rate_func=cube_anim.get_rate_func(),
run_time=cube_anim.get_run_time()),
ReplacementTransform(j_vec, j_vec_new, rate_func=cube_anim.get_rate_func(),
run_time=cube_anim.get_run_time()),
ReplacementTransform(k_vec, k_vec_new, rate_func=cube_anim.get_rate_func(),
run_time=cube_anim.get_run_time())
)
self.wait()
cube = new_cube
i_vec, j_vec, k_vec = i_vec_new, j_vec_new, k_vec_new
self.wait(1)
self.wait(1)