arc_length_microelement.py
1from manim import *2import numpy as np3 4class ArcLengthMicroelementScene(Scene):5 def construct(self):6 # 标题7 title = Text("微元法求曲线弧长", font_size=42)8 title.to_edge(UP)9 self.play(Write(title), run_time=1.5)10 self.wait(1)11 12 # 淡出标题13 self.play(FadeOut(title), run_time=1)14 15 # 介绍文本16 intro = Text("将曲线分割为无数个微小弧段,每段近似为直线", font_size=28)17 intro.to_edge(UP, buff=0.5) # 调整位置,因为标题已淡出18 self.play(Write(intro), run_time=1.5)19 self.wait(1)20 21 # 淡出介绍22 self.play(FadeOut(intro), run_time=1)23 24 # 创建坐标系25 axes = Axes(26 x_range=[0, 4, 1],27 y_range=[0, 4, 1],28 x_length=10,29 y_length=7,30 axis_config={"color": BLUE}31 )32 33 # 添加坐标轴标签34 x_label = axes.get_x_axis_label("x")35 y_label = axes.get_y_axis_label("y")36 37 # 显示坐标系38 self.play(39 Create(axes),40 Write(x_label),41 Write(y_label),42 run_time=1.543 )44 45 # 定义函数 - 弯曲程度较大的复合函数46 def func(x):47 return 0.3 * x * x * x + 0.5 * np.sin(4 * x) + 148 49 def func_derivative(x):50 return 0.9 * x * x + 2 * np.cos(4 * x)51 52 # 创建函数曲线53 curve = axes.plot(func, x_range=[0, 2.5], color=RED, stroke_width=4)54 curve_label = MathTex("y = f(x)", font_size=28, color=RED)55 curve_label.next_to(curve.get_end(), UP + LEFT)56 57 self.play(58 Create(curve),59 Write(curve_label),60 run_time=261 )62 self.wait(1)63 64 # 定义弧长计算区间 - 选择弯曲明显的区域65 a, b = 1.0, 2.066 67 # 标记计算区间68 a_line = axes.get_vertical_line(axes.c2p(a, func(a)), color=GREEN)69 b_line = axes.get_vertical_line(axes.c2p(b, func(b)), color=GREEN)70 71 a_label = MathTex("a", font_size=28, color=GREEN)72 a_label.next_to(axes.c2p(a, 0), DOWN)73 74 b_label = MathTex("b", font_size=28, color=GREEN)75 b_label.next_to(axes.c2p(b, 0), DOWN)76 77 # 高亮弧长段78 arc_segment = axes.plot(func, x_range=[a, b], color=YELLOW, stroke_width=6)79 80 self.play(81 Create(a_line),82 Create(b_line),83 Write(a_label),84 Write(b_label),85 Create(arc_segment),86 run_time=287 )88 self.wait(1)89 90 # 弧长标记91 arc_label = Text("待求弧长", font_size=24, color=YELLOW)92 arc_label.move_to(axes.c2p(2, func(2) + 0.3))93 self.play(Write(arc_label), run_time=1)94 self.wait(1.5)95 96 # 开始演示分割过程97 self.play(FadeOut(arc_label), run_time=0.5)98 99 # 演示分割 - 选择8段作为例子100 n = 8101 dx_seg = (b - a) / n102 103 # 显示分割说明104 division_title = Text("将弧长分割成若干段", font_size=30, color=ORANGE)105 division_title.to_corner(UL).shift(DOWN * 0.5)106 self.play(Write(division_title), run_time=1)107 108 # 创建分割点和弧段109 division_points = []110 micro_elements = []111 colors = [YELLOW, BLUE, GREEN, PINK]112 113 # 第一步:显示分割点114 for i in range(n + 1): # n+1个分割点115 x_point = a + i * dx_seg116 point = Dot(axes.c2p(x_point, func(x_point)), color=WHITE, radius=0.04)117 division_points.append(point)118 119 self.play(120 *[Create(point) for point in division_points],121 run_time=2122 )123 self.wait(1)124 125 # 第二步:显示分割后的弧段126 for i in range(n):127 x_left = a + i * dx_seg128 x_right = a + (i + 1) * dx_seg129 130 micro_arc = axes.plot(func, x_range=[x_left, x_right], 131 color=colors[i % len(colors)], stroke_width=5)132 micro_elements.append(micro_arc)133 134 self.play(135 *[Create(elem) for elem in micro_elements],136 run_time=2137 )138 self.wait(1.5)139 140 # 淡出分割说明141 self.play(FadeOut(division_title), run_time=0.5)142 143 # 演示一小段的近似计算144 segment_index = 1 # 选择第2段进行演示(靠近a点)145 x_left = a + segment_index * dx_seg146 x_right = a + (segment_index + 1) * dx_seg147 148 # 高亮选中的段149 highlight_text = Text("选择其中一段进行近似计算", font_size=26, color=RED)150 highlight_text.to_corner(UL).shift(DOWN * 0.5)151 self.play(Write(highlight_text), run_time=1)152 153 # 高亮显示选中的弧段154 selected_arc = axes.plot(func, x_range=[x_left, x_right], 155 color=RED, stroke_width=8)156 self.play(157 *[FadeOut(elem) for i, elem in enumerate(micro_elements) if i != segment_index],158 *[FadeOut(point) for i, point in enumerate(division_points) if i != segment_index and i != segment_index + 1],159 Transform(micro_elements[segment_index], selected_arc),160 run_time=2161 )162 self.wait(1)163 164 # 显示端点165 point1 = Dot(axes.c2p(x_left, func(x_left)), color=RED, radius=0.06)166 point2 = Dot(axes.c2p(x_right, func(x_right)), color=RED, radius=0.06)167 168 self.play(169 Create(point1),170 Create(point2),171 run_time=1172 )173 self.wait(0.5)174 175 # 放大演示:展示曲线弧和切线的关系176 self.play(FadeOut(highlight_text), run_time=0.5)177 178 zoom_text = Text("放大观察:曲线段越短,与切线段越接近", font_size=26, color=BLUE)179 zoom_text.to_corner(UL).shift(DOWN * 0.5)180 self.play(Write(zoom_text), run_time=1)181 182 # 创建放大的视图区域183 zoom_center_x = (x_left + x_right) / 2184 zoom_center_y = func(zoom_center_x)185 186 # 放大的坐标系187 zoom_range = 0.3188 zoom_axes = Axes(189 x_range=[zoom_center_x - zoom_range, zoom_center_x + zoom_range, 0.1],190 y_range=[zoom_center_y - zoom_range, zoom_center_y + zoom_range, 0.1],191 x_length=5.5,192 y_length=4.5,193 axis_config={"color": GRAY, "stroke_width": 1}194 )195 zoom_axes.move_to(RIGHT * 3 + UP * 0.2)196 197 # 放大视图的边框198 zoom_box = Rectangle(199 width=5.8, height=4.8,200 color=BLUE, stroke_width=3201 ).move_to(RIGHT * 3 + UP * 0.2)202 203 # 显示放大视图框架204 self.play(205 Create(zoom_box),206 Create(zoom_axes),207 run_time=1.5208 )209 210 # 演示不断缩短的过程211 segment_lengths = [0.28, 0.18, 0.11, 0.06] # 从0.28开始,逐步缩短212 colors = [RED, ORANGE, YELLOW, GREEN]213 214 for i, length in enumerate(segment_lengths):215 # 当前段的范围216 current_left = zoom_center_x - length / 2217 current_right = zoom_center_x + length / 2218 219 # 显示当前阶段说明220 if i == 0:221 stage_text = Text("较长的曲线段", font_size=22, color=colors[i])222 elif i == 1:223 stage_text = Text("缩短曲线段", font_size=22, color=colors[i])224 elif i == 2:225 stage_text = Text("进一步缩短", font_size=22, color=colors[i])226 else:227 stage_text = Text("很短的曲线段", font_size=22, color=colors[i])228 229 stage_text.next_to(zoom_text, DOWN, aligned_edge=LEFT, buff=0.3)230 231 if i > 0:232 self.play(Transform(prev_stage_text, stage_text), run_time=0.8)233 else:234 self.play(Write(stage_text), run_time=0.8)235 prev_stage_text = stage_text236 237 # 放大视图中的曲线段238 zoom_curve = zoom_axes.plot(239 func, 240 x_range=[current_left, current_right], 241 color=colors[i], 242 stroke_width=8243 )244 245 # 曲线段的端点246 zoom_point1 = Dot(zoom_axes.c2p(current_left, func(current_left)), color=colors[i], radius=0.06)247 zoom_point2 = Dot(zoom_axes.c2p(current_right, func(current_right)), color=colors[i], radius=0.06)248 249 # 计算切线(在段中点处的切线)250 mid_x = zoom_center_x251 mid_y = func(mid_x)252 slope = func_derivative(mid_x) # 在中点处的导数(切线斜率)253 254 # 切线段(从左端点到右端点)255 tangent_y_left = mid_y + slope * (current_left - mid_x)256 tangent_y_right = mid_y + slope * (current_right - mid_x)257 258 zoom_tangent = Line(259 zoom_axes.c2p(current_left, tangent_y_left),260 zoom_axes.c2p(current_right, tangent_y_right),261 color=PURPLE, stroke_width=6262 )263 264 # 显示曲线段和切线段265 if i == 0:266 self.play(267 Create(zoom_curve),268 Create(zoom_point1),269 Create(zoom_point2),270 run_time=1.5271 )272 self.wait(1)273 self.play(Create(zoom_tangent), run_time=1.2)274 self.wait(1.5)275 else:276 # 端点移动、曲线缩短和长曲线消失同时进行277 self.play(278 ReplacementTransform(prev_curve, zoom_curve),279 ReplacementTransform(prev_point1, zoom_point1),280 ReplacementTransform(prev_point2, zoom_point2),281 ReplacementTransform(prev_tangent, zoom_tangent),282 run_time=3.0 # 稍长的时间让变化过程更清晰自然283 )284 self.wait(1.5)285 286 # 保存当前对象以便下次更新287 prev_curve = zoom_curve288 prev_point1 = zoom_point1289 prev_point2 = zoom_point2290 prev_tangent = zoom_tangent291 292 # 最终说明293 self.play(FadeOut(zoom_text), run_time=0.5)294 final_zoom_text = Text("极限情况:曲线段与切线段完全重合", font_size=24, color=GREEN)295 final_zoom_text.to_corner(UL).shift(DOWN * 0.5)296 self.play(Write(final_zoom_text), run_time=1.5)297 self.wait(2)298 299 # 回到主视图进行详细分析300 self.play(301 FadeOut(final_zoom_text),302 FadeOut(prev_stage_text),303 *[FadeOut(mob) for mob in [zoom_box, zoom_axes, prev_curve, 304 prev_point1, prev_point2, prev_tangent]],305 run_time=1.5306 )307 308 # 添加原来的直线近似到主视图309 approx_line = Line(310 axes.c2p(x_left, func(x_left)),311 axes.c2p(x_right, func(x_right)),312 color=PURPLE, stroke_width=6313 )314 315 approximation_text = Text("用直线段近似这段弧长", font_size=26, color=PURPLE)316 approximation_text.to_corner(UL).shift(DOWN * 0.5)317 self.play(Write(approximation_text), run_time=1)318 319 self.play(Create(approx_line), run_time=1.5)320 self.wait(1)321 322 # 构造直角三角形进行详细分析323 dy = func(x_right) - func(x_left)324 325 # 水平线 (dx)326 horizontal_line = Line(327 axes.c2p(x_left, func(x_left)),328 axes.c2p(x_right, func(x_left)),329 color=BLUE, stroke_width=4330 )331 332 # 竖直线 (dy)333 vertical_line = Line(334 axes.c2p(x_right, func(x_left)),335 axes.c2p(x_right, func(x_right)),336 color=GREEN, stroke_width=4337 )338 339 # 标签340 dx_label = MathTex("dx", font_size=28, color=BLUE)341 dx_label.next_to(horizontal_line, DOWN, buff=0.2)342 343 dy_label = MathTex("dy", font_size=28, color=GREEN)344 dy_label.next_to(vertical_line, RIGHT, buff=0.2)345 346 ds_label = MathTex("ds", font_size=28, color=PURPLE)347 ds_label.next_to(approx_line.get_center(), UP + LEFT, buff=0.2)348 349 self.play(FadeOut(approximation_text), run_time=0.5)350 351 triangle_text = Text("构造直角三角形分析", font_size=26, color=WHITE)352 triangle_text.to_corner(UL).shift(DOWN * 0.5)353 self.play(Write(triangle_text), run_time=1)354 355 self.play(356 Create(horizontal_line),357 Create(vertical_line),358 Write(dx_label),359 Write(dy_label),360 Write(ds_label),361 run_time=2362 )363 self.wait(2)364 365 # 显示计算公式366 self.play(FadeOut(triangle_text), run_time=0.5)367 368 # 显示弧长微元公式(右侧)369 formula_group = VGroup()370 371 # 勾股定理372 pythagorean = MathTex(r"ds^2 = dx^2 + dy^2", font_size=24)373 pythagorean.to_corner(UR).shift(LEFT * 1.5 + DOWN * 0.5)374 formula_group.add(pythagorean)375 376 # 弧长微元公式377 ds_formula = MathTex(r"ds = \sqrt{dx^2 + dy^2}", font_size=24)378 ds_formula.next_to(pythagorean, DOWN, aligned_edge=LEFT, buff=0.2)379 formula_group.add(ds_formula)380 381 # 导数形式382 derivative_form = MathTex(r"ds = \sqrt{1 + \left(\frac{dy}{dx}\right)^2} dx", font_size=24)383 derivative_form.next_to(ds_formula, DOWN, aligned_edge=LEFT, buff=0.2)384 formula_group.add(derivative_form)385 386 self.play(Write(pythagorean), run_time=1)387 self.wait(0.5)388 self.play(Write(ds_formula), run_time=1)389 self.wait(0.5)390 self.play(Write(derivative_form), run_time=1)391 self.wait(2)392 393 # 清除详细分析,准备回到整体演示394 self.play(395 *[FadeOut(mob) for mob in [396 micro_elements[segment_index], point1, point2, approx_line,397 horizontal_line, vertical_line, dx_label, dy_label, ds_label,398 formula_group399 ]],400 run_time=1.5401 )402 403 # 恢复完整的弧长段用于后续演示404 complete_arc = axes.plot(func, x_range=[a, b], color=YELLOW, stroke_width=6)405 self.play(Create(complete_arc), run_time=1)406 self.wait(0.5)407 408 # 显示极限和积分概念409 limit_text = Text("当段长 → 0 时,所有直线段长度之和趋向于曲线弧长", font_size=24)410 limit_text.to_edge(DOWN)411 self.play(Write(limit_text), run_time=2)412 self.wait(1.5)413 414 # 最终的积分公式415 final_formula = MathTex(r"L = \int_a^b \sqrt{1 + \left(\frac{dy}{dx}\right)^2} dx", 416 font_size=36, color=GOLD)417 final_box = SurroundingRectangle(final_formula, buff=0.3, color=GOLD)418 final_group = VGroup(final_formula, final_box)419 final_group.move_to(RIGHT * 4 + UP * 2.5) # 右移到更右侧位置,避免重叠420 421 self.play(422 FadeOut(limit_text),423 Write(final_group),424 run_time=2425 )426 427 self.wait(2)428 429 # 结论430 conclusion = Text("微元法:将复杂的曲线问题转化为简单的直线问题", font_size=26)431 conclusion.to_edge(DOWN)432 self.play(Write(conclusion), run_time=2)433 434 self.wait(3)435 436 # 淡出所有元素437 self.play(*[FadeOut(mob) for mob in self.mobjects], run_time=1.5)438 439 440if __name__ == "__main__":441 # 直接渲染场景442 scene = ArcLengthMicroelementScene()443 scene.render() 讲解
这个视频围绕「平面曲线的弧长计算」展开。画面把问题、图像和公式放在同一条理解路径中,让抽象关系先被看见。
开头先建立问题背景和主要对象,让观察从标题、坐标或第一组关系进入。
画面中出现的文字「微元法求曲线弧长」给出视觉入口。随后画面通过对象创建、移动、淡入淡出和高亮,把抽象命题拆成可以跟随的步骤。
随后出现更具体的图像或公式提示,动画会把局部对象和整体结论联系起来。这里可以重点观察变量、曲线、区域或向量如何随镜头推进而变化。
核心公式可以写成:
这类公式可以和画面中的符号一一对应。
观察路径
观察路径可以分三步:先锁定「平面曲线的弧长计算」中的核心对象,尤其留意曲线弧长、微元法、定积分之间的联系;再跟随画面中变量、图像或向量的变化;最后回到公式或结论,判断局部变化如何支撑整体关系。
本页可以从曲线弧长、微元法、定积分、函数图像这些概念进入,继续沿相邻问题观察同一类数学结构。