manim_function_limit_comparison.py
1#!/usr/bin/env python2# -*- coding: utf-8 -*-3 4"""5这个程序用Manim创建动画,对比一元函数和二元函数的概念及其极限6作者: Claude7"""8 9from manim import *10import numpy as np11 12# 配置中文支持13config.tex_template.add_to_preamble(r"\usepackage[UTF8]{ctex}")14 15# 定义全局变量A代表极限值116A = 117 18class OneVariableFunction(Scene):19 """演示一元函数及其极限"""20 21 def construct(self):22 # 标题23 title = Text("一元函数极限", font="SimSun")24 self.play(Write(title))25 self.wait()26 self.play(title.animate.scale(0.6).to_edge(UP))27 28 # 创建坐标系29 axes = Axes(30 x_range=[-5, 5, 1],31 y_range=[-1.5, 1.5, 0.5],32 axis_config={"include_tip": False},33 ).scale(0.8)34 35 # 坐标轴标签36 x_label = Text("x", font="SimSun").next_to(axes.x_axis.get_end(), RIGHT)37 y_label = Text("f(x)", font="SimSun").next_to(axes.y_axis.get_end(), UP)38 axes_labels = VGroup(x_label, y_label)39 40 # 函数标题41 func_title = Text("f(x) = sin(x)/x", font="SimSun", font_size=24)42 func_title.next_to(axes, UP)43 44 # 函数定义45 def f(x):46 return np.sin(x) / x if x != 0 else A47 48 # 创建函数图像49 graph = axes.plot(lambda x: f(x) if x != 0 else None, 50 discontinuities=[0], color=BLUE)51 52 # 单点极限值53 limit_point = Dot(axes.c2p(0, A), color=RED)54 limit_label = Text("极限值 = A", font="SimSun", font_size=20, color=RED)55 limit_label.next_to(limit_point, UR, buff=0.1)56 57 # 显示所有元素58 self.play(59 Create(axes),60 Write(axes_labels),61 Write(func_title)62 )63 self.play(Create(graph))64 self.wait()65 66 # 动画演示点的趋近67 approach_dot = Dot(axes.c2p(3, f(3)), color=YELLOW)68 x_tracker = ValueTracker(3)69 70 approach_dot.add_updater(71 lambda d: d.move_to(axes.c2p(x_tracker.get_value(), 72 f(x_tracker.get_value())))73 )74 75 approach_arrow = Arrow(start=axes.c2p(3, 0), 76 end=axes.c2p(3, f(3)), 77 color=YELLOW, buff=0)78 approach_arrow.add_updater(79 lambda a: a.put_start_and_end_on(80 axes.c2p(x_tracker.get_value(), 0),81 axes.c2p(x_tracker.get_value(), f(x_tracker.get_value()))82 )83 )84 85 value_label = Text("", font="SimSun", font_size=18)86 value_label.add_updater(87 lambda t: t.become(88 Text(f"x = {x_tracker.get_value():.3f}\nf(x) = {f(x_tracker.get_value()):.3f}", 89 font="SimSun", font_size=18)90 ).next_to(approach_dot, UR, buff=0.1)91 )92 93 self.play(Create(approach_dot), Create(approach_arrow), Create(value_label))94 95 # 从正向趋近96 self.play(x_tracker.animate.set_value(0.001), run_time=3)97 self.wait()98 99 # 从负向趋近100 self.play(x_tracker.animate.set_value(-3), run_time=1)101 self.play(x_tracker.animate.set_value(-0.001), run_time=3)102 self.wait()103 104 # 显示极限点和标签105 self.play(Create(limit_point), Write(limit_label))106 self.wait()107 108 # 清除更新器109 approach_dot.clear_updaters()110 approach_arrow.clear_updaters()111 value_label.clear_updaters()112 113 # 一元函数极限概念114 concept = Text(115 "一元函数极限:当x趋近于某个值a时,函数值f(x)趋近于的极限值L\n"116 "记为:lim(x→a) f(x) = L", 117 font="SimSun", font_size=24118 )119 concept.to_edge(DOWN, buff=0.5)120 121 self.play(Write(concept))122 self.wait(2)123 124 # 清场准备转入二元函数125 self.play(126 *[FadeOut(mob) for mob in self.mobjects]127 )128 129class TwoVariableFunction(ThreeDScene):130 """演示二元函数及其极限"""131 132 def construct(self):133 # 设置3D场景134 self.set_camera_orientation(phi=70 * DEGREES, theta=-30 * DEGREES)135 136 # 标题137 title = Text("二元函数极限", font="SimSun")138 title_mobject = self.add_fixed_in_frame_mobjects(title)139 title.to_corner(UL)140 self.play(Write(title))141 142 # 函数信息143 func_info = Text("f(x,y) = sin(√(x²+y²))/√(x²+y²)", font="SimSun", font_size=24)144 func_info.next_to(title, DOWN, aligned_edge=LEFT)145 self.add_fixed_in_frame_mobjects(func_info)146 self.play(Write(func_info))147 148 # 创建3D坐标系149 axes = ThreeDAxes(150 x_range=[-5, 5, 1],151 y_range=[-5, 5, 1],152 z_range=[-1, 1.5, 0.5],153 ).scale(0.7)154 155 # 添加轴标签156 x_label = Text("x", font="SimSun").next_to(axes.x_axis.get_end(), RIGHT)157 y_label = Text("y", font="SimSun").next_to(axes.y_axis.get_end(), RIGHT)158 z_label = Text("f(x,y)", font="SimSun").next_to(axes.z_axis.get_end(), UP)159 160 # 固定标签在帧中161 self.add_fixed_in_frame_mobjects(x_label, y_label, z_label)162 163 # 创建坐标系164 self.play(Create(axes))165 self.play(Write(VGroup(x_label, y_label, z_label)))166 167 # 函数定义168 def f(x, y):169 r = np.sqrt(x**2 + y**2)170 if r == 0:171 return A172 return np.sin(r) / r173 174 # 创建曲面175 surface = Surface(176 lambda u, v: axes.c2p(u, v, f(u, v)),177 u_range=[-4, 4],178 v_range=[-4, 4],179 resolution=(20, 20),180 checkerboard_colors=[BLUE_D, BLUE_E],181 fill_opacity=0.7182 )183 184 # 显示曲面185 self.play(Create(surface), run_time=2)186 self.wait(0.5)187 188 # 添加说明文字189 approach_text_3d = Text("自变量沿多条路径趋向于原点", font="SimSun", font_size=24)190 approach_text_3d.next_to(title, DOWN, buff=0.5)191 self.add_fixed_in_frame_mobjects(approach_text_3d)192 self.play(Write(approach_text_3d))193 self.wait(0.5)194 self.play(FadeOut(approach_text_3d))195 196 # 创建原点197 origin = Dot3D(axes.c2p(0, 0, A), color=RED)198 199 # 极限点200 limit_point = Dot3D(axes.c2p(0, 0, A), color=RED)201 limit_label = Text("极限值 = A", font="SimSun", color=RED)202 limit_label.next_to(limit_point, UP)203 self.add_fixed_in_frame_mobjects(limit_label)204 205 self.play(Create(limit_point))206 self.play(Write(limit_label))207 208 # 修改描述文本209 paths_description = Text("不同路径趋向同一极限值", font="SimSun", font_size=28, color=BLUE)210 paths_description.to_edge(UP)211 self.add_fixed_in_frame_mobjects(paths_description)212 self.play(Write(paths_description))213 214 # 添加并显示不同路径215 paths = VGroup()216 217 # 沿x轴趋近218 x_path = ParametricFunction(219 lambda t: axes.c2p(t, 0, f(t, 0)),220 t_range=[4, 0.01, -0.01],221 color=YELLOW222 )223 paths.add(x_path)224 225 # 沿y轴趋近226 y_path = ParametricFunction(227 lambda t: axes.c2p(0, t, f(0, t)),228 t_range=[4, 0.01, -0.01],229 color=GREEN230 )231 paths.add(y_path)232 233 # 沿直线y=x趋近234 xy_path = ParametricFunction(235 lambda t: axes.c2p(t, t, f(t, t)),236 t_range=[4, 0.01, -0.01],237 color=PURPLE238 )239 paths.add(xy_path)240 241 # 沿螺旋线趋近242 spiral_path = ParametricFunction(243 lambda t: axes.c2p(244 t * np.cos(2 * t), 245 t * np.sin(2 * t), 246 f(t * np.cos(2 * t), t * np.sin(2 * t))247 ),248 t_range=[4, 0.01, -0.01],249 color=ORANGE250 )251 paths.add(spiral_path)252 253 # 逐个显示点254 self.play(255 Create(x_path),256 Create(y_path),257 Create(xy_path),258 Create(spiral_path),259 )260 261 # 显示所有路径262 self.play(Create(paths), run_time=2)263 self.wait(1)264 265 # 显示极限点和标签266 limit_point_3d = Dot3D(axes.c2p(0, 0, A), color=RED, radius=0.1)267 limit_label_3d = Text("极限值=A", font="SimSun", font_size=20, color=RED)268 limit_label_3d.next_to(limit_point_3d, UP, buff=0.2)269 270 self.play(Create(limit_point_3d))271 self.add_fixed_in_frame_mobjects(limit_label_3d)272 self.play(Write(limit_label_3d))273 274 # 二元函数极限概念275 concept = Text(276 "二元函数极限:当点(x,y)沿任意路径趋近于点(a,b)时,\n"277 "若函数值f(x,y)都趋近于同一个值L,则L为函数在该点的极限\n"278 "记为:lim(x,y)→(a,b) f(x,y) = L", 279 font="SimSun", font_size=24280 )281 concept.to_edge(DOWN, buff=0.5)282 self.add_fixed_in_frame_mobjects(concept)283 284 self.play(Write(concept))285 self.wait(2)286 287class LimitDoesNotExist(ThreeDScene):288 """演示二元函数极限不存在的情况"""289 290 def construct(self):291 # 设置3D场景292 self.set_camera_orientation(phi=70 * DEGREES, theta=-30 * DEGREES)293 294 # 标题295 title = Text("二元函数极限不存在示例", font="SimSun")296 self.add_fixed_in_frame_mobjects(title)297 title.to_corner(UL)298 self.play(Write(title))299 300 # 函数信息301 func_info = Text("f(x,y) = xy/(x²+y²)", font="SimSun", font_size=24)302 func_info.next_to(title, DOWN, aligned_edge=LEFT)303 self.add_fixed_in_frame_mobjects(func_info)304 self.play(Write(func_info))305 306 # 创建3D坐标系307 axes = ThreeDAxes(308 x_range=[-5, 5, 1],309 y_range=[-5, 5, 1],310 z_range=[-1, 1, 0.5],311 ).scale(0.7)312 313 # 添加轴标签314 x_label = Text("x", font="SimSun").next_to(axes.x_axis.get_end(), RIGHT)315 y_label = Text("y", font="SimSun").next_to(axes.y_axis.get_end(), RIGHT)316 317 # 修改z轴标签位置,不再放在z轴的顶部318 z_label = Text("f(x,y)", font="SimSun", color=BLUE_B)319 z_label.to_edge(LEFT).shift(DOWN * 2) # 放在左侧边缘位置,不会阻碍视线320 321 # 固定标签在帧中322 self.add_fixed_in_frame_mobjects(x_label, y_label, z_label)323 324 # 创建坐标系325 self.play(Create(axes))326 self.play(Write(VGroup(x_label, y_label, z_label)))327 328 # 函数定义329 def f(x, y):330 r_squared = x**2 + y**2331 if r_squared == 0:332 return A # 函数在原点未定义,但这里设为A便于可视化333 return (x * y) / r_squared334 335 # 创建表面,去除原点周围以避免奇点336 resolution = 20 # 减小以提高性能337 surface = Surface(338 lambda u, v: axes.c2p(339 u, v, f(u, v)340 ),341 u_range=[-4, 4],342 v_range=[-4, 4],343 resolution=(resolution, resolution),344 checkerboard_colors=[BLUE_D, BLUE_E],345 fill_opacity=0.7346 )347 348 # 添加表面349 self.play(Create(surface), run_time=2)350 self.wait()351 352 # 添加从不同方向趋近的路径353 paths = VGroup()354 355 # 沿x轴趋近356 x_path = ParametricFunction(357 lambda t: axes.c2p(t, 0, f(t, 0)),358 t_range=[4, 0.01, -0.01],359 color=YELLOW360 )361 paths.add(x_path)362 x_path_label = Text("x轴路径: 极限值 = A", font="SimSun", font_size=20, color=YELLOW)363 x_path_label.to_edge(LEFT).shift(UP * 1.5)364 self.add_fixed_in_frame_mobjects(x_path_label)365 366 # 沿y轴趋近367 y_path = ParametricFunction(368 lambda t: axes.c2p(0, t, f(0, t)),369 t_range=[4, 0.01, -0.01],370 color=GREEN371 )372 paths.add(y_path)373 y_path_label = Text("y轴路径: 极限值 = A", font="SimSun", font_size=20, color=GREEN)374 y_path_label.next_to(x_path_label, DOWN, aligned_edge=LEFT)375 self.add_fixed_in_frame_mobjects(y_path_label)376 377 # 沿y=x直线趋近378 xy_path = ParametricFunction(379 lambda t: axes.c2p(t, t, f(t, t)),380 t_range=[4, 0.01, -0.01],381 color=PURPLE382 )383 paths.add(xy_path)384 xy_path_label = Text("y=x路径: 极限值 = A", font="SimSun", font_size=20, color=PURPLE)385 xy_path_label.next_to(y_path_label, DOWN, aligned_edge=LEFT)386 self.add_fixed_in_frame_mobjects(xy_path_label)387 388 # 沿y=-x直线趋近389 xy_neg_path = ParametricFunction(390 lambda t: axes.c2p(t, -t, f(t, -t)),391 t_range=[4, 0.01, -0.01],392 color=ORANGE393 )394 paths.add(xy_neg_path)395 xy_neg_path_label = Text("y=-x路径: 极限值 = A", font="SimSun", font_size=20, color=ORANGE)396 xy_neg_path_label.next_to(xy_path_label, DOWN, aligned_edge=LEFT)397 self.add_fixed_in_frame_mobjects(xy_neg_path_label)398 399 # 添加路径400 self.play(Create(paths), run_time=3)401 402 # 显示路径标签403 self.play(404 Write(x_path_label),405 Write(y_path_label),406 Write(xy_path_label),407 Write(xy_neg_path_label)408 )409 410 # 说明文本411 description = Text(412 "二元函数极限不存在情况:\n"413 "从不同方向趋近原点,得到不同的极限值", 414 font="SimSun", font_size=22, color=RED415 )416 description.to_edge(DOWN, buff=0.5)417 self.add_fixed_in_frame_mobjects(description)418 419 self.play(Write(description))420 self.wait(1)421 422 # 旋转相机以更好地观察表面423 self.begin_ambient_camera_rotation(rate=0.1)424 self.wait(5)425 self.stop_ambient_camera_rotation()426 427 # 添加说明:不同路径得到不同极限值428 explanation_2 = Text(429 "不同路径趋近原点时得到不同的极限值",430 font="SimSun", color=YELLOW431 ).scale(0.5)432 explanation_2.next_to(axes, DOWN)433 self.play(Write(explanation_2), run_time=1)434 435 # 定义结论文本436 conclusion_2 = Text(437 "不同路径得到不同极限值,因此极限不存在",438 font="SimSun", color=RED439 ).scale(0.7)440 conclusion_2.to_edge(UP, buff=2)441 442 # 点沿着路径移动到原点443 self.play(FadeOut(description), Write(conclusion_2))444 self.wait(2)445 446class FunctionLimitComparison(Scene):447 """整合一元函数与二元函数极限的完整比较"""448 449 def construct(self):450 # 主标题 - 使用Text而非Title以避免TeX编译错误451 title = Text("函数极限概念对比", font="SimSun", font_size=36)452 title.to_edge(UP)453 self.play(Write(title))454 self.wait()455 456 # 说明文本457 intro_text = Text(458 "本动画将分三部分展示:\n"459 "1. 一元函数极限\n"460 "2. 二元函数极限\n"461 "3. 二元函数极限不存在的情况", 462 font="SimSun", 463 font_size=28464 )465 intro_text.next_to(title, DOWN, buff=0.5)466 467 self.play(Write(intro_text))468 self.wait(2)469 470 # 第一部分提示471 part1_text = Text("第一部分:一元函数极限", font="SimSun", font_size=36, color=BLUE)472 self.play(473 FadeOut(intro_text),474 ReplacementTransform(title, part1_text)475 )476 self.wait()477 self.play(FadeOut(part1_text))478 479 # 一元函数极限场景480 # 主标题481 title = Text("一元函数极限", font="SimSun")482 self.play(Write(title))483 self.wait()484 self.play(title.animate.scale(0.6).to_edge(UP))485 486 # 说明文本487 intro = Text("一元函数 f(x) = sin(x)/x", font="SimSun", font_size=24)488 intro.next_to(title, DOWN)489 self.play(Write(intro))490 self.wait(2)491 492 # 清场准备展示一元函数493 self.play(494 FadeOut(intro),495 title.animate.scale(0.8).to_corner(UL)496 )497 498 # 创建坐标系499 axes = Axes(500 x_range=[-5, 5, 1],501 y_range=[-1.5, 1.5, 0.5],502 axis_config={"include_tip": False},503 ).scale(0.8)504 505 # 函数标题506 func_eq = Text("f(x) = sin(x)/x", font="SimSun", font_size=28)507 func_eq.next_to(axes, UP, buff=0.2)508 509 # 函数定义510 def f1(x):511 return np.sin(x) / x if x != 0 else A512 513 # 创建函数图像514 graph = axes.plot(lambda x: f1(x) if x != 0 else None, 515 discontinuities=[0], color=BLUE)516 517 # 单点极限值518 limit_point = Dot(axes.c2p(0, A), color=RED)519 limit_label = Text("极限值 = A", font="SimSun", font_size=20, color=RED)520 limit_label.next_to(limit_point, UR, buff=0.1)521 522 # 显示元素523 self.play(524 Create(axes),525 Write(func_eq)526 )527 self.play(Create(graph))528 529 # 创建用于演示的两个点,分别从正向和负向趋近530 pos_x_start = 3.0531 neg_x_start = -3.0532 pos_dot = Dot(axes.c2p(pos_x_start, f1(pos_x_start)), color=YELLOW)533 neg_dot = Dot(axes.c2p(neg_x_start, f1(neg_x_start)), color=GREEN)534 535 # 使用ParametricFunction创建点的轨迹路径536 pos_path = ParametricFunction(537 lambda t: axes.c2p(538 pos_x_start * (1 - t) + 0.01 * t, # 从pos_x_start到0.01539 f1(pos_x_start * (1 - t) + 0.01 * t)540 ),541 t_range=[0, 1],542 color=YELLOW_A,543 stroke_opacity=0.6544 )545 546 neg_path = ParametricFunction(547 lambda t: axes.c2p(548 neg_x_start * (1 - t) - 0.01 * t, # 从neg_x_start到-0.01549 f1(neg_x_start * (1 - t) - 0.01 * t)550 ),551 t_range=[0, 1],552 color=GREEN_A,553 stroke_opacity=0.6554 )555 556 # 添加表示趋近方向的标签557 pos_direction = Text("x→0+", font="SimSun", font_size=20, color=YELLOW)558 pos_direction.next_to(pos_dot, UR, buff=0.1)559 560 neg_direction = Text("x→0-", font="SimSun", font_size=20, color=GREEN)561 neg_direction.next_to(neg_dot, UL, buff=0.1)562 563 self.add_fixed_in_frame_mobjects(pos_direction, neg_direction)564 565 # 显示点和轨迹566 self.play(Create(pos_dot), Create(neg_dot))567 self.play(Create(pos_path), Create(neg_path), run_time=1)568 self.play(Write(pos_direction), Write(neg_direction))569 self.wait()570 571 # 沿着曲线移动点(使用单个动画,而不是分两个阶段)572 self.play(573 MoveAlongPath(pos_dot, pos_path),574 MoveAlongPath(neg_dot, neg_path),575 # 中途更新标签位置576 UpdateFromFunc(577 pos_direction,578 lambda m: m.next_to(axes.c2p(579 pos_x_start * (1 - self.renderer.time / 5) + 0.01 * self.renderer.time / 5,580 f1(pos_x_start * (1 - self.renderer.time / 5) + 0.01 * self.renderer.time / 5)581 ), UR, buff=0.1)582 ),583 UpdateFromFunc(584 neg_direction,585 lambda m: m.next_to(axes.c2p(586 neg_x_start * (1 - self.renderer.time / 5) - 0.01 * self.renderer.time / 5,587 f1(neg_x_start * (1 - self.renderer.time / 5) - 0.01 * self.renderer.time / 5)588 ), UL, buff=0.1)589 ),590 run_time=5591 )592 593 self.wait()594 595 # 显示极限点和标签(确保这段代码不会被跳过)596 self.play(Create(limit_point))597 self.add_fixed_in_frame_mobjects(limit_label)598 self.play(Write(limit_label))599 self.wait(1) # 确保足够的暂停时间,让用户看清极限值标签600 601 # 使用Text创建极限符号表示(避免LaTeX错误)602 lim_text = Text("lim", font_size=28)603 x_to_0 = Text("x→0", font_size=22)604 x_to_0.next_to(lim_text, DOWN, buff=0.05, aligned_edge=LEFT)605 f_x_text = Text("f(x) = A", font_size=28)606 f_x_text.next_to(lim_text, RIGHT, buff=0.2)607 608 limit_group = VGroup(lim_text, x_to_0, f_x_text)609 limit_group.shift(DOWN * 2)610 611 self.add_fixed_in_frame_mobjects(limit_group)612 self.play(Write(lim_text), Write(x_to_0), Write(f_x_text))613 self.wait(2)614 615 # 清场,准备第二部分616 self.play(617 *[FadeOut(mob) for mob in self.mobjects if mob != lim_text],618 FadeOut(title),619 FadeOut(limit_label),620 FadeOut(func_eq),621 FadeOut(pos_direction),622 FadeOut(neg_direction),623 FadeOut(pos_dot),624 FadeOut(neg_dot),625 FadeOut(pos_path),626 FadeOut(neg_path)627 )628 629 # 第二部分提示630 part2_text = Text("第二部分:二元函数极限", font="SimSun", font_size=36, color=GREEN)631 self.play(Write(part2_text))632 self.wait()633 self.play(FadeOut(part2_text))634 635 # 显示提示信息636 tip_text = Text(637 "注意:接下来是三维场景演示\n"638 "请运行以下命令观看:\n"639 "manim -pql manim_function_limit_comparison.py TwoVariableFunction",640 font="SimSun", 641 font_size=28642 )643 644 self.play(Write(tip_text))645 self.wait(3)646 647 # 第三部分提示648 self.play(FadeOut(tip_text))649 part3_text = Text("第三部分:二元函数极限不存在的情况", font="SimSun", font_size=36, color=RED)650 self.play(Write(part3_text))651 self.wait()652 self.play(FadeOut(part3_text))653 654 # 显示提示信息655 tip_text2 = Text(656 "注意:接下来是三维场景演示\n"657 "请运行以下命令观看:\n"658 "manim -pql manim_function_limit_comparison.py LimitDoesNotExist",659 font="SimSun", 660 font_size=28661 )662 663 self.play(Write(tip_text2))664 self.wait(3)665 666 # 最后的总结667 self.play(FadeOut(tip_text2))668 summary = Text(669 "本演示展示了函数极限的概念区别:\n\n"670 "一元函数:极限存在当且仅当从左右两个方向趋近的值相同\n\n"671 "二元函数:极限存在当且仅当从任意方向趋近的值都相同",672 font="SimSun", 673 font_size=28674 )675 676 self.play(Write(summary))677 self.wait(3)678 679 # 结束680 end_text = Text(681 "动画演示结束,谢谢观看",682 font="SimSun", 683 font_size=36,684 color=BLUE685 )686 687 self.play(688 FadeOut(summary),689 Write(end_text)690 )691 self.wait(2)692 693class CompleteFunctionLimitDemo(ThreeDScene):694 """完整演示:依次展示一元函数极限、二元函数极限和二元函数极限不存在的情况"""695 696 def construct(self):697 # 添加总标题,修改为"二元函数极限演示"698 main_title = Text("二元函数极限演示", font="SimSun", font_size=36, color=BLUE)699 main_title.to_edge(UP)700 701 self.add_fixed_in_frame_mobjects(main_title)702 self.play(Write(main_title))703 self.wait(1)704 # 显示标题后淡出705 self.play(FadeOut(main_title))706 self.wait(0.5)707 708 # 设置为2D视角用于一元函数部分709 self.set_camera_orientation(phi=0, theta=-90 * DEGREES)710 711 # 添加一元函数小标题712 one_var_title = Text("一元函数极限", font="SimSun", font_size=28, color=BLUE)713 one_var_title.to_edge(UP)714 self.add_fixed_in_frame_mobjects(one_var_title)715 self.play(Write(one_var_title))716 self.wait(0.5)717 718 # 创建坐标系719 axes = Axes(720 x_range=[-5, 5, 1],721 y_range=[-1.5, 1.5, 0.5],722 axis_config={"include_tip": False},723 ).scale(0.8)724 725 # 函数定义726 def f1(x):727 return np.sin(x) / x if x != 0 else A728 729 # 创建函数图像730 function_graph = axes.plot(lambda x: f1(x) if x != 0 else None, 731 discontinuities=[0], color=BLUE)732 733 self.play(734 Create(axes),735 Create(function_graph)736 )737 738 # 添加文字说明739 approach_text = Text("自变量从两个方向趋向于0点", font="SimSun", font_size=24)740 approach_text.next_to(one_var_title, DOWN, buff=0.5)741 self.add_fixed_in_frame_mobjects(approach_text)742 self.play(Write(approach_text))743 self.wait(0.5)744 self.play(FadeOut(approach_text))745 746 # 创建用于演示的两个点,分别从正向和负向趋近747 pos_x_start = 2.0748 neg_x_start = -2.0749 750 # 使用ParametricFunction创建点的轨迹路径751 pos_path = ParametricFunction(752 lambda t: axes.c2p(753 pos_x_start * (1 - t) + 0.01 * t, # 从pos_x_start到0.01754 f1(pos_x_start * (1 - t) + 0.01 * t)755 ),756 t_range=[0, 1],757 color=YELLOW_A,758 stroke_opacity=0.6759 )760 761 neg_path = ParametricFunction(762 lambda t: axes.c2p(763 neg_x_start * (1 - t) - 0.01 * t, # 从neg_x_start到-0.01764 f1(neg_x_start * (1 - t) - 0.01 * t)765 ),766 t_range=[0, 1],767 color=GREEN_A,768 stroke_opacity=0.6769 )770 771 self.play(Create(pos_path), Create(neg_path))772 773 # 创建移动点和标签774 pos_point = Dot(axes.c2p(pos_x_start, f1(pos_x_start)), color=YELLOW)775 neg_point = Dot(axes.c2p(neg_x_start, f1(neg_x_start)), color=GREEN)776 777 pos_label = Text("x→0+", font="SimSun", font_size=20).next_to(pos_point, UR, buff=0.1)778 neg_label = Text("x→0-", font="SimSun", font_size=20).next_to(neg_point, UL, buff=0.1)779 780 self.add_fixed_in_frame_mobjects(pos_label, neg_label)781 self.play(Create(pos_point), Create(neg_point))782 self.play(Write(pos_label), Write(neg_label))783 784 # 移动点到原点785 self.play(786 MoveAlongPath(pos_point, pos_path),787 MoveAlongPath(neg_point, neg_path),788 run_time=3789 )790 self.wait(0.5)791 792 # 移动点到达后显示极限点和标签793 limit_point = Dot(axes.c2p(0, A), color=RED)794 limit_label = Text("极限值=A", font="SimSun", font_size=20, color=RED)795 limit_label.next_to(limit_point, UR, buff=0.1)796 797 self.play(Create(limit_point))798 self.add_fixed_in_frame_mobjects(limit_label)799 self.play(Write(limit_label))800 self.wait(1)801 802 # 使用Text创建极限符号表示(避免LaTeX错误)803 lim_text = Text("lim", font_size=28)804 x_to_0 = Text("x→0", font_size=22)805 x_to_0.next_to(lim_text, DOWN, buff=0.05, aligned_edge=LEFT)806 f_x_text = Text("f(x) = A", font_size=28)807 f_x_text.next_to(lim_text, RIGHT, buff=0.2)808 809 limit_group = VGroup(lim_text, x_to_0, f_x_text)810 limit_group.shift(DOWN * 2)811 812 self.add_fixed_in_frame_mobjects(limit_group)813 self.play(Write(lim_text), Write(x_to_0), Write(f_x_text))814 self.wait(2)815 816 # 清除一元函数部分817 self.clear()818 819 # 开始二元函数部分820 two_var_title = Text("二元函数极限", font="SimSun", font_size=28, color=BLUE)821 two_var_title.to_edge(UP)822 self.add_fixed_in_frame_mobjects(two_var_title)823 self.play(Write(two_var_title))824 self.wait(0.5)825 826 # 添加二元函数极限结论到标题下方827 conclusion_2 = Text("二元函数极限存在:从任意路径趋近得到相同极限值", font="SimSun", font_size=20, color=BLUE)828 conclusion_2.next_to(two_var_title, DOWN, buff=0.3)829 self.add_fixed_in_frame_mobjects(conclusion_2)830 self.play(Write(conclusion_2))831 self.wait(0.5)832 833 # 设置3D相机角度834 self.set_camera_orientation(phi=75 * DEGREES, theta=-60 * DEGREES)835 836 # 创建3D坐标轴837 axes_3d = ThreeDAxes(838 x_range=[-5, 5, 1],839 y_range=[-5, 5, 1],840 z_range=[-1, 5, 1],841 x_length=6,842 y_length=6,843 z_length=4,844 ).move_to(ORIGIN)845 846 # 显示3D坐标系847 self.play(Create(axes_3d))848 849 # 函数定义,计算曲面上的点850 def f(x, y):851 r = np.sqrt(x**2 + y**2)852 if r == 0:853 return A854 return np.sin(r) / r855 856 # 创建曲面857 surface = Surface(858 lambda u, v: axes_3d.c2p(u, v, f(u, v)),859 u_range=[-4, 4],860 v_range=[-4, 4],861 resolution=(20, 20),862 checkerboard_colors=[BLUE_D, BLUE_E],863 fill_opacity=0.7864 )865 866 # 显示曲面867 self.play(Create(surface), run_time=2)868 self.wait(0.5)869 870 # 添加说明文字871 approach_text_3d = Text("自变量沿多条路径趋向于原点", font="SimSun", font_size=24)872 approach_text_3d.next_to(two_var_title, DOWN, buff=0.5)873 self.add_fixed_in_frame_mobjects(approach_text_3d)874 self.play(Write(approach_text_3d))875 self.wait(0.5)876 self.play(FadeOut(approach_text_3d))877 878 # 创建原点879 origin = Dot3D(axes_3d.c2p(0, 0, A), color=RED)880 self.play(Create(origin))881 self.wait(0.5)882 883 # 添加从不同方向趋近的路径884 paths = VGroup()885 886 # 沿x轴趋近887 x_path = ParametricFunction(888 lambda t: axes_3d.c2p(t, 0, f(t, 0)),889 t_range=[4, 0.01, -0.01],890 color=YELLOW891 )892 paths.add(x_path)893 x_point = Sphere(radius=0.1, color=YELLOW).move_to(axes_3d.c2p(4, 0, f(4, 0)))894 895 # 沿y轴趋近896 y_path = ParametricFunction(897 lambda t: axes_3d.c2p(0, t, f(0, t)),898 t_range=[4, 0.01, -0.01],899 color=GREEN900 )901 paths.add(y_path)902 y_point = Sphere(radius=0.1, color=GREEN).move_to(axes_3d.c2p(0, 4, f(0, 4)))903 904 # 沿直线y=x趋近905 xy_path = ParametricFunction(906 lambda t: axes_3d.c2p(t, t, f(t, t)),907 t_range=[4, 0.01, -0.01],908 color=PURPLE909 )910 paths.add(xy_path)911 xy_point = Sphere(radius=0.1, color=PURPLE).move_to(axes_3d.c2p(3, 3, f(3, 3)))912 913 # 沿直线y=-x趋近914 xy_neg_path = ParametricFunction(915 lambda t: axes_3d.c2p(t, -t, f(t, -t)),916 t_range=[4, 0.01, -0.01],917 color=TEAL918 )919 paths.add(xy_neg_path)920 xy_neg_point = Sphere(radius=0.1, color=TEAL).move_to(axes_3d.c2p(3, -3, f(3, -3)))921 922 # 沿螺旋线趋近923 spiral_path = ParametricFunction(924 lambda t: axes_3d.c2p(925 t * np.cos(2 * t), 926 t * np.sin(2 * t), 927 f(t * np.cos(2 * t), t * np.sin(2 * t))928 ),929 t_range=[4, 0.01, -0.01],930 color=ORANGE931 )932 paths.add(spiral_path)933 spiral_point = Sphere(radius=0.1, color=ORANGE).move_to(934 axes_3d.c2p(4 * np.cos(8), 4 * np.sin(8), f(4 * np.cos(8), 4 * np.sin(8)))935 )936 937 # 逐个显示点938 self.play(939 Create(x_point),940 Create(y_point),941 Create(xy_point),942 Create(xy_neg_point),943 Create(spiral_point),944 )945 946 # 显示所有路径947 self.play(Create(paths), run_time=2)948 self.wait(1)949 950 # 显示极限点和标签951 limit_point_3d = Dot3D(axes_3d.c2p(0, 0, A), color=RED, radius=0.1)952 limit_label_3d = Text("极限值=A", font="SimSun", font_size=20, color=RED)953 limit_label_3d.next_to(limit_point_3d, UP, buff=0.2)954 955 # 添加进一步说明956 limit_explanation = Text("无论从哪个方向趋近,极限值都相同", font="SimSun", font_size=22, color=GREEN)957 limit_explanation.to_edge(DOWN, buff=1)958 959 self.play(Create(limit_point_3d))960 self.add_fixed_in_frame_mobjects(limit_label_3d, limit_explanation)961 self.play(Write(limit_label_3d))962 self.play(Write(limit_explanation))963 self.wait(1)964 965 # 使用Text创建极限符号表示(避免LaTeX错误)966 lim_text_2 = Text("lim", font_size=28)967 xy_to_00 = Text("(x,y)→(0,0)", font_size=22)968 xy_to_00.next_to(lim_text_2, DOWN, buff=0.05, aligned_edge=LEFT)969 f_xy_text_2 = Text("f(x,y) = A", font_size=28)970 f_xy_text_2.next_to(lim_text_2, RIGHT, buff=0.5) # 增加右侧缓冲区,从0.2改为0.5971 972 limit_group_2 = VGroup(lim_text_2, xy_to_00, f_xy_text_2)973 limit_group_2.shift(DOWN * 3).scale(0.9) # 缩小整体大小并调整位置974 975 self.add_fixed_in_frame_mobjects(limit_group_2)976 self.play(Write(lim_text_2), Write(xy_to_00), Write(f_xy_text_2))977 self.wait(2)978 979 # 清除场景980 self.clear()981 982 # 开始二元函数极限不存在的情况983 no_limit_title = Text("二元函数极限不存在的情况", font="SimSun", font_size=28, color=RED)984 no_limit_title.to_edge(UP)985 self.add_fixed_in_frame_mobjects(no_limit_title)986 self.play(Write(no_limit_title))987 self.wait(0.5)988 989 # 添加二元函数极限不存在结论到标题下方990 conclusion_3 = Text("不同路径趋近得到不同极限值,因此极限不存在", font="SimSun", font_size=20, color=RED)991 conclusion_3.next_to(no_limit_title, DOWN, buff=0.3)992 self.add_fixed_in_frame_mobjects(conclusion_3)993 self.play(Write(conclusion_3))994 self.wait(0.5)995 996 # 设置3D相机角度997 self.set_camera_orientation(phi=75 * DEGREES, theta=-60 * DEGREES)998 999 # 创建新的3D坐标轴1000 axes_3d_2 = ThreeDAxes(1001 x_range=[-5, 5, 1],1002 y_range=[-5, 5, 1],1003 z_range=[-1, 1, 0.5],1004 x_length=6,1005 y_length=6,1006 z_length=4,1007 ).move_to(ORIGIN)1008 1009 # 显示3D坐标系1010 self.play(Create(axes_3d_2))1011 1012 # 创建函数 z = xy/(x^2+y^2) 的曲面1013 def f2(x, y):1014 r_squared = x**2 + y**21015 if r_squared < 0.001: # 避免除以零1016 return 0 # 返回0而不是A,因为在这个例子中沿x轴和y轴趋近的极限值是01017 return (x * y) / r_squared1018 1019 # 创建曲面1020 surface_2 = Surface(1021 lambda u, v: axes_3d_2.c2p(u, v, f2(u, v)),1022 u_range=[-2, 2],1023 v_range=[-2, 2],1024 resolution=(30, 30),1025 fill_opacity=0.7,1026 checkerboard_colors=[BLUE_D, BLUE_E],1027 )1028 1029 # 创建函数公式1030 func_formula = Text("z=f(x,y)=xy/(x²+y²)", font="SimSun", font_size=24, color=BLUE)1031 func_formula.to_edge(UP, buff=1.5)1032 1033 self.play(Create(surface_2))1034 self.add_fixed_in_frame_mobjects(func_formula)1035 self.play(Write(func_formula))1036 1037 # 创建原点1038 origin = Dot3D(axes_3d_2.c2p(0, 0, 0), color=RED)1039 self.play(Create(origin))1040 self.wait(0.5)1041 1042 # 显示解释文本1043 explain_text = Text("观察不同路径趋近原点时的极限值", font="SimSun", font_size=20)1044 explain_text.to_edge(UP, buff=2)1045 self.add_fixed_in_frame_mobjects(explain_text)1046 self.play(Write(explain_text))1047 self.wait(1)1048 self.play(FadeOut(explain_text))1049 1050 # 创建不同类型的路径及其标签1051 paths_3d = VGroup()1052 path_points = []1053 path_labels = []1054 1055 # 1. 沿x轴趋近 - 极限值 = A1056 x_path = ParametricFunction(1057 lambda t: axes_3d_2.c2p(t, 0, f2(t, 0)),1058 t_range=[2, 0.1, -0.05],1059 color=YELLOW1060 )1061 paths_3d.add(x_path)1062 x_point = Sphere(radius=0.08, color=YELLOW).move_to(axes_3d_2.c2p(2, 0, f2(2, 0)))1063 x_label = Text("沿x轴路径趋近:极限值为A", font="SimSun", font_size=18, color=YELLOW)1064 x_label.to_corner(UL).shift(DOWN * 2)1065 self.add_fixed_in_frame_mobjects(x_label)1066 path_points.append(x_point)1067 path_labels.append(x_label)1068 1069 # 2. 沿y轴趋近 - 极限值 = A1070 y_path = ParametricFunction(1071 lambda t: axes_3d_2.c2p(0, t, f2(0, t)),1072 t_range=[2, 0.1, -0.05],1073 color=GREEN1074 )1075 paths_3d.add(y_path)1076 y_point = Sphere(radius=0.08, color=GREEN).move_to(axes_3d_2.c2p(0, 2, f2(0, 2)))1077 y_label = Text("沿y轴路径趋近:极限值为A", font="SimSun", font_size=18, color=GREEN)1078 y_label.next_to(x_label, DOWN, aligned_edge=LEFT)1079 self.add_fixed_in_frame_mobjects(y_label)1080 path_points.append(y_point)1081 path_labels.append(y_label)1082 1083 # 3. 沿直线y=x路径趋近 - 极限值 = A1084 xy_path = ParametricFunction(1085 lambda t: axes_3d_2.c2p(t, t, f2(t, t)),1086 t_range=[2, 0.1, -0.05],1087 color=PURPLE1088 )1089 paths_3d.add(xy_path)1090 xy_point = Sphere(radius=0.08, color=PURPLE).move_to(axes_3d_2.c2p(2, 2, f2(2, 2)))1091 xy_label = Text("沿y=x路径趋近:极限值为A", font="SimSun", font_size=18, color=PURPLE)1092 xy_label.next_to(y_label, DOWN, aligned_edge=LEFT)1093 self.add_fixed_in_frame_mobjects(xy_label)1094 path_points.append(xy_point)1095 path_labels.append(xy_label)1096 1097 # 4. 沿直线y=-x路径趋近 - 极限值 = A1098 xy_neg_path = ParametricFunction(1099 lambda t: axes_3d_2.c2p(t, -t, f2(t, -t)),1100 t_range=[2, 0.1, -0.05],1101 color=TEAL1102 )1103 paths_3d.add(xy_neg_path)1104 xy_neg_point = Sphere(radius=0.08, color=TEAL).move_to(axes_3d_2.c2p(2, -2, f2(2, -2)))1105 xy_neg_label = Text("沿y=-x路径趋近:极限值为A", font="SimSun", font_size=18, color=TEAL)1106 xy_neg_label.next_to(xy_label, DOWN, aligned_edge=LEFT)1107 self.add_fixed_in_frame_mobjects(xy_neg_label)1108 path_points.append(xy_neg_point)1109 path_labels.append(xy_neg_label)1110 1111 # 创建极限点1112 limit_points = [1113 Dot3D(axes_3d_2.c2p(0, 0, 0), color=YELLOW, radius=0.08), # x轴路径极限点:(0,0,0)1114 Dot3D(axes_3d_2.c2p(0, 0, 0), color=GREEN, radius=0.08), # y轴路径极限点:(0,0,0)1115 Dot3D(axes_3d_2.c2p(0, 0, 0.5), color=PURPLE, radius=0.08), # y=x路径极限点:(0,0,0.5)1116 Dot3D(axes_3d_2.c2p(0, 0, -0.5), color=TEAL, radius=0.08), # y=-x路径极限点:(0,0,-0.5)1117 ]1118 1119 # 显示极限点1120 for limit_point in limit_points:1121 self.play(Create(limit_point), run_time=0.5)1122 1123 # 然后逐个演示点沿着路径移动到各自的极限值1124 for i, (point, limit_point) in enumerate(zip(path_points, limit_points)):1125 # 对于每个点,创建一个路径从点的当前位置到对应的极限值1126 self.play(1127 point.animate.move_to(limit_point.get_center()),1128 run_time=21129 )1130 self.wait(0.5)1131 1132 # 使用Text创建极限符号表示(避免LaTeX错误)1133 lim_text_3 = Text("lim", font_size=28)1134 xy_to_00_3 = Text("(x,y)→(0,0)", font_size=22)1135 xy_to_00_3.next_to(lim_text_3, DOWN, buff=0.05, aligned_edge=LEFT)1136 f_xy_text_3 = Text("xy/(x²+y²) 不存在", font_size=28)1137 f_xy_text_3.next_to(lim_text_3, RIGHT, buff=0.2)1138 1139 limit_group_3 = VGroup(lim_text_3, xy_to_00_3, f_xy_text_3)1140 limit_group_3.to_edge(DOWN, buff=1)1141 1142 self.add_fixed_in_frame_mobjects(limit_group_3)1143 self.play(Write(lim_text_3), Write(xy_to_00_3), Write(f_xy_text_3))1144 self.wait(2)1145 1146if __name__ == "__main__":1147 # 运行完整演示1148 # manim -pql manim_function_limit_comparison.py CompleteFunctionLimitDemo1149 pass 讲解
这个视频围绕「二元函数极限演示」展开。画面把问题、图像和公式放在同一条理解路径中,让抽象关系先被看见。
开头先建立问题背景和主要对象,让观察从标题、坐标或第一组关系进入。
画面中出现的文字「一元函数极限」给出视觉入口。随后画面通过对象创建、移动、淡入淡出和高亮,把抽象命题拆成可以跟随的步骤。
随后出现更具体的图像或公式提示,动画会把局部对象和整体结论联系起来。这里可以重点观察变量、曲线、区域或向量如何随镜头推进而变化。
观察路径
观察路径可以分三步:先锁定「二元函数极限演示」中的核心对象,尤其留意二元函数极限、路径极限、函数极限之间的联系;再跟随画面中变量、图像或向量的变化;最后回到公式或结论,判断局部变化如何支撑整体关系。
本页可以从二元函数极限、路径极限、函数极限、多元函数这些概念进入,继续沿相邻问题观察同一类数学结构。