directional_derivative.py
1from manim import *2import numpy as np3 4config.tex_template = TexTemplateLibrary.ctex5config.tex_template.add_to_preamble(r"\setCJKmainfont{STSong}")6 7class DirectionalDerivative(ThreeDScene):8 def create_vertical_plane(self, direction_vector, base_point):9 """创建过方向向量且垂直于底面的平面"""10 u_vec = np.array(direction_vector) / np.linalg.norm(direction_vector)11 base = np.array(base_point)12 13 return Surface(14 lambda u, v: [15 base[0] + u * u_vec[0],16 base[1] + u * u_vec[1],17 base[2] + v18 ],19 u_range=[-4, 4],20 v_range=[-4, 4],21 resolution=(1, 1),22 color=RED,23 fill_opacity=0.3,24 stroke_opacity=025 )26 27 def create_secant(self, p1, p2):28 """创建曲面上的割线"""29 t_values = np.linspace(-3, 3, 400)30 points = []31 for t in t_values:32 x = p1[0] + t*(p2[0]-p1[0])33 y = p1[1] + t*(p2[1]-p1[1])34 if abs(x) > 3 or abs(y) > 3:35 continue36 z = x**2 + y**237 points.append(self.axes.c2p(x, y, z))38 return VMobject().set_points_smoothly(points).set_style(39 stroke_color=YELLOW,40 stroke_width=8,41 stroke_opacity=142 )43 44 def create_tangent_line(self, point, direction, length=6):45 """创建直线切线"""46 direction = np.array(direction) / np.linalg.norm(direction)47 p = np.array(point)48 49 z_change = 2 * (p[0]*direction[0] + p[1]*direction[1])50 tangent = np.array([direction[0], direction[1], z_change])51 tangent = tangent / np.linalg.norm(tangent)52 53 start = p - length * tangent54 end = p + length * tangent55 56 return Line(57 self.axes.c2p(*start),58 self.axes.c2p(*end),59 color=WHITE,60 stroke_width=661 )62 63 def construct(self):64 # 创建3D坐标系65 self.axes = ThreeDAxes(66 x_range=[-2, 2, 1],67 y_range=[-2, 2, 1],68 z_range=[0, 8, 2],69 x_length=6,70 y_length=6,71 z_length=4,72 axis_config={"color": GREY, "include_ticks": False}73 ).add_coordinates()74 75 # 创建标题76 title = Text("方向导数的几何意义", font="STSong")77 title.scale(0.6).to_corner(UP+LEFT)78 self.add_fixed_in_frame_mobjects(title)79 self.play(Write(title))80 self.wait(1)81 82 # 创建函数曲面83 surface = Surface(84 lambda u, v: self.axes.c2p(u, v, u**2 + v**2),85 u_range=[-2, 2], v_range=[-2, 2],86 resolution=(30, 30),87 checkerboard_colors=[BLUE_D, BLUE_E],88 fill_opacity=0.889 )90 91 # 设置固定视角92 self.set_camera_orientation(phi=60*DEGREES, theta=-40*DEGREES)93 self.stop_ambient_camera_rotation()94 95 # 显示坐标系和曲面96 self.play(Create(self.axes), Create(surface))97 self.wait(1)98 99 # 创建底面上的点P(1,1)100 P = (1, 1, 0)101 P_surface = (1, 1, 2)102 point_P = Dot3D(self.axes.c2p(*P), color=RED)103 point_P_surface = Dot3D(self.axes.c2p(*P_surface), color=RED)104 vertical_line = Line(self.axes.c2p(*P), self.axes.c2p(*P_surface), color=YELLOW)105 106 # 创建曲面方程和基准点标记107 surface_equation = MathTex(108 "z = f(x,y)",109 color=WHITE110 ).to_corner(UP+RIGHT)111 self.add_fixed_in_frame_mobjects(surface_equation)112 self.play(Write(surface_equation))113 114 # 创建基准点标记115 P0_label = MathTex(116 "P_0(x_0,y_0)",117 color=RED118 ).scale(0.6).next_to(119 self.axes.c2p(*P), # 使用三维坐标定位120 UR, # 改为右上方向121 buff=0.2122 ).shift(LEFT*0.3 + DOWN*2) # 微调位置123 self.add_fixed_in_frame_mobjects(P0_label)124 125 # 显示基准点和标记126 self.play(Create(point_P))127 self.wait(0.5)128 129 # 创建方向向量130 angle = 30*DEGREES131 u_vec = [np.cos(angle), np.sin(angle), 0]132 vector_scale = 0.8133 direction_arrow = Arrow3D(134 start=self.axes.c2p(*P),135 end=self.axes.c2p(*(np.array(P) + vector_scale * np.array(u_vec))),136 color=GREEN137 )138 139 # 显示方向向量140 self.play(Create(direction_arrow))141 self.wait(0.1) # 确保箭头已渲染142 143 # 修改方向向量标记的位置144 # 将标签直接附加在箭头末端145 direction_label = MathTex(146 "\\vec{l}",147 color=GREEN148 ).scale(0.7).next_to(149 direction_arrow.get_end(), # 使用箭头末端坐标150 UR, # 右上方向151 buff=0.1152 ).shift(LEFT*0.2 + DOWN*2) # 微调位置153 154 # 确保标签位于底面155 direction_label.move_to(156 self.axes.c2p(157 *(np.array(P) + vector_scale * np.array(u_vec))[:2], # 取x,y坐标158 0 # z坐标强制为0159 )160 ).shift(RIGHT*0.3 + DOWN*2) # 最终微调161 162 # 确保标记固定在底面163 self.add_fixed_in_frame_mobjects(direction_label)164 self.play(Write(direction_label))165 self.wait(1)166 167 # 计算Ph点坐标(使用第一个h值)168 h = 0.8 # 使用固定值169 Ph = np.array(P) + h * vector_scale * np.array(u_vec)170 Ph_clipped = np.clip(Ph[:2], -1.8, 1.8)171 Ph_surface = (*Ph_clipped, Ph_clipped[0]**2 + Ph_clipped[1]**2)172 173 # 创建几何元素174 points = {175 "P": P,176 "Ph": (*Ph_clipped, 0),177 "P_surface": P_surface,178 "Ph_surface": Ph_surface179 }180 181 # 创建点和线182 ph_point = Dot3D(183 self.axes.c2p(*points["Ph"]), 184 color=PINK, # 改为更醒目的颜色185 radius=0.08 # 稍微加大点的大小186 )187 ph_surface_point = Dot3D(self.axes.c2p(*points["Ph_surface"]), color=GREEN)188 ph_line = Line(189 self.axes.c2p(*points["Ph"]), 190 self.axes.c2p(*points["Ph_surface"]), 191 color=YELLOW192 )193 194 # 创建移动点标记195 Ph_label = MathTex(196 "P(x_0+\\rho\\cos\\alpha, y_0+\\rho\\sin\\alpha)",197 color=PINK198 ).scale(0.6).next_to(199 self.axes.c2p(*points["Ph"]), # 使用三维坐标定位200 UR, # 保持右上方向201 buff=0.2202 ).shift(LEFT*0.5+DOWN*2) # 向左微调避免超出屏幕203 self.add_fixed_in_frame_mobjects(Ph_label)204 205 # 显示移动点和标记206 self.play(Create(ph_point))207 self.wait(0.5)208 209 # 3. 显示曲面上对应的点和垂线210 self.play(211 Create(point_P_surface),212 Create(ph_surface_point),213 Create(vertical_line),214 Create(ph_line),215 run_time=1216 )217 self.wait(1)218 219 # 4. 显示垂直平面220 vertical_plane = self.create_vertical_plane(u_vec, P)221 self.play(Create(vertical_plane))222 self.wait(0.5)223 224 # 5. 显示交线225 intersection_curve = self.create_secant(226 (P[0] - 3*u_vec[0], P[1] - 3*u_vec[1], 0),227 (P[0] + 3*u_vec[0], P[1] + 3*u_vec[1], 0)228 ).set_color(YELLOW_A) # 使用更亮的黄色229 self.play(Create(intersection_curve))230 self.wait(0.1)231 232 # 创建rho标记233 t = 0.5 # 中点参数234 mid_x = (1 - t)*P[0] + t*points["Ph"][0]235 mid_y = (1 - t)*P[1] + t*points["Ph"][1]236 mid_z = 0237 rho_mid = self.axes.c2p(mid_x, mid_y, mid_z)238 239 rho_label = MathTex(240 "\\rho",241 color=YELLOW242 ).scale(0.8).next_to(243 rho_mid,244 RIGHT*0.5+ DOWN*0.5, 245 buff=0.2246 )247 self.add_fixed_in_frame_mobjects(rho_label)248 249 # 创建曲面上的连线250 surface_line = Line(251 self.axes.c2p(*P_surface),252 self.axes.c2p(*points["Ph_surface"]),253 color=BLUE_C,254 stroke_width=4255 )256 257 # 调整动画顺序258 # 6. 显示rho标记和曲面连线259 self.play(Write(rho_label))260 self.play(Create(surface_line))261 self.wait(1)262 263 # 7. 显示切线264 final_tangent = self.create_tangent_line(265 P_surface,266 u_vec,267 length=6268 )269 self.play(Create(final_tangent))270 self.wait(1)271 272 # 修改旋转方向273 current_theta = self.camera.theta274 self.move_camera(275 theta=current_theta + 90 * DEGREES, # 改为顺时针旋转276 run_time=2277 )278 self.wait(1)279 280 # 创建方向导数定义281 definition = VGroup(282 MathTex("\\text{方向导数:}"),283 MathTex(284 "f'_l(x_0,y_0) = \\lim_{\\rho \\to 0} \\frac{f(x_0+\\rho\\cos\\alpha, y_0+\\rho\\sin\\alpha) - f(x_0,y_0)}{\\rho}"285 )286 ).arrange(RIGHT, buff=0.5)287 definition.scale(0.8).to_edge(DOWN)288 self.add_fixed_in_frame_mobjects(definition)289 290 # 显示定义291 self.play(Write(definition))292 self.wait(1)293 294 # 添加结论说明295 conclusion = Text(296 "函数f在P₀点沿方向l的方向导数为图中白色切线的斜率",297 font="STSong",298 font_size=24 # 缩小字体大小299 ).scale(0.8).next_to(definition, DOWN, buff=0.2) # 添加缩放和调整间距300 self.add_fixed_in_frame_mobjects(conclusion)301 self.play(Write(conclusion))302 self.wait(2)303 304def main():305 import os306 os.system("manim -pqh directional_derivative.py DirectionalDerivative")307 308if __name__ == "__main__":309 main() 讲解
这个视频围绕「方向导数的几何意义」展开。画面把问题、图像和公式放在同一条理解路径中,让抽象关系先被看见。
开头先建立问题背景和主要对象,让观察从标题、坐标或第一组关系进入。
画面中出现的文字「方向导数的几何意义」给出视觉入口。随后画面通过对象创建、移动、淡入淡出和高亮,把抽象命题拆成可以跟随的步骤。
随后出现更具体的图像或公式提示,动画会把局部对象和整体结论联系起来。这里可以重点观察变量、曲线、区域或向量如何随镜头推进而变化。
观察路径
观察路径可以分三步:先锁定「方向导数的几何意义」中的核心对象,尤其留意方向导数、偏导数、切平面之间的联系;再跟随画面中变量、图像或向量的变化;最后回到公式或结论,判断局部变化如何支撑整体关系。
本页可以从方向导数、偏导数、切平面、函数图像这些概念进入,继续沿相邻问题观察同一类数学结构。