heine_theorem.py
1from manim import *2import numpy as np3 4class HeineTheorem(Scene):5 def construct(self):6 # 标题7 title = Text("海涅定理", font="SimSun", color=BLUE).scale(0.8)8 title.to_corner(UL)9 self.play(Write(title))10 self.wait(1)11 12 # 定理内容显示在标题右侧13 theorem_eq = MathTex(14 r"\lim_{x \to x_0} f(x) = A \iff \forall \{x_n\} \to x_0, \lim_{n \to \infty} f(x_n) = A",15 color=GREEN16 ).scale(0.8)17 theorem_eq.next_to(title, RIGHT, buff=0.5) # 修改为RIGHT而不是DOWN18 19 self.play(Write(theorem_eq))20 self.wait(2)21 22 # 函数极限过程标题显示在定理标题下方23 continuous_title = Text("函数极限过程", font="SimSun", color=RED).scale(0.6)24 continuous_title.next_to(title, DOWN, buff=0.5)25 26 # 数列极限过程标题显示在函数极限过程标题下方27 discrete_title = Text("数列极限过程", font="SimSun", color=GREEN).scale(0.6)28 discrete_title.next_to(continuous_title, DOWN, buff=0.3)29 30 # 显示函数极限过程标题31 self.play(Write(continuous_title))32 self.wait(1)33 34 # 初始化数列点列表以避免作用域问题35 seq1_dots_x = []36 seq1_dots_y = []37 seq1_lines = []38 seq1_labels = []39 seq2_dots_x = []40 seq2_dots_y = []41 seq2_lines = []42 seq2_labels = []43 seq3_dots_x = []44 seq3_dots_y = []45 seq3_lines = []46 seq3_labels = []47 seq4_dots_x = []48 seq4_dots_y = []49 seq4_lines = []50 seq4_labels = []51 52 # 创建局部坐标系 (x=0附近)53 axes = Axes(54 x_range=[-1, 1, 0.25],55 y_range=[-0.2, 0.3, 0.1],56 x_length=8,57 y_length=5,58 axis_config={"color": WHITE, "include_tip": True},59 ).shift(DOWN * 0.5)60 61 # 添加坐标轴标签62 x_label = MathTex("x").next_to(axes.x_axis.get_end(), DOWN)63 y_label = MathTex("f(x)").next_to(axes.y_axis.get_end(), LEFT)64 65 # 绘制函数 f(x) = x^266 def func(x):67 return x**268 69 graph = axes.plot(func, color=BLUE, x_range=[-0.5, 0.5])70 71 graph_label = MathTex(r"f(x) = x^2", color=BLUE).scale(0.7)72 graph_label.next_to(axes.c2p(0.4, func(0.4)), UR, buff=0.3)73 74 self.play(75 Create(axes),76 Write(x_label),77 Write(y_label),78 Create(graph),79 Write(graph_label)80 )81 self.wait(2)82 83 # 标记x₀=0点84 x0_dot = Dot(axes.c2p(0, func(0)), color=YELLOW)85 x0_label = MathTex("x_0=0", color=YELLOW).scale(0.7)86 x0_label.next_to(x0_dot, UR, buff=0.1)87 88 self.play(89 Create(x0_dot),90 Write(x0_label)91 )92 self.wait(1)93 94 # 动态点沿着x轴接近x₀95 moving_x_dot = Dot(axes.c2p(0.4, 0), color=RED)96 moving_y_dot = Dot(axes.c2p(0.4, func(0.4)), color=RED)97 moving_x_label = MathTex("x", color=RED).scale(0.7).next_to(moving_x_dot, DOWN, buff=0.1)98 self.play(Create(moving_x_dot), Create(moving_y_dot), Write(moving_x_label))99 100 # 连接两点的虚线101 line = DashedLine(moving_x_dot, moving_y_dot, color=RED)102 self.play(Create(line))103 104 # 移动点沿着x轴连续地接近x₀,同时在函数图像上显示对应点105 self.play(106 moving_x_dot.animate.move_to(axes.c2p(0.2, 0)),107 moving_y_dot.animate.move_to(axes.c2p(0.2, func(0.2))),108 moving_x_label.animate.next_to(axes.c2p(0.2, 0), DOWN, buff=0.1),109 line.animate.put_start_and_end_on(axes.c2p(0.2, 0), axes.c2p(0.2, func(0.2))),110 run_time=2111 )112 113 self.play(114 moving_x_dot.animate.move_to(axes.c2p(0.1, 0)),115 moving_y_dot.animate.move_to(axes.c2p(0.1, func(0.1))),116 moving_x_label.animate.next_to(axes.c2p(0.1, 0), DOWN, buff=0.1),117 line.animate.put_start_and_end_on(axes.c2p(0.1, 0), axes.c2p(0.1, func(0.1))),118 run_time=2119 )120 121 self.play(122 moving_x_dot.animate.move_to(axes.c2p(0.05, 0)),123 moving_y_dot.animate.move_to(axes.c2p(0.05, func(0.05))),124 moving_x_label.animate.next_to(axes.c2p(0.05, 0), DOWN, buff=0.1),125 line.animate.put_start_and_end_on(axes.c2p(0.05, 0), axes.c2p(0.05, func(0.05))),126 run_time=2127 )128 129 self.play(130 moving_x_dot.animate.move_to(axes.c2p(0, 0)),131 moving_y_dot.animate.move_to(axes.c2p(0, func(0))),132 moving_x_label.animate.next_to(axes.c2p(0, 0), DOWN, buff=0.1),133 line.animate.put_start_and_end_on(axes.c2p(0, 0), axes.c2p(0, func(0))),134 run_time=1135 )136 137 self.wait(1)138 139 # 淡出移动的点和线140 self.play(141 FadeOut(moving_x_dot),142 FadeOut(moving_y_dot),143 FadeOut(moving_x_label),144 FadeOut(line)145 )146 147 self.wait(2)148 149 # 显示函数极限表达式在坐标系下方150 limit_eq = MathTex(151 r"\lim_{x \to 0} f(x) = \lim_{x \to 0} x^2 = 0",152 color=ORANGE153 ).scale(0.7)154 limit_eq.next_to(axes, DOWN, buff=0.3)155 156 self.play(Write(limit_eq))157 self.wait(2)158 159 # # 显示这些数列对应的函数值都趋向于0160 # limit_eq = MathTex(161 # r"\lim_{n \to \infty} f(x_n) = \lim_{n \to \infty} f(y_n) = 0",162 # color=ORANGE163 # ).scale(0.7)164 # limit_eq.next_to(axes, DOWN, buff=0.3)165 166 # self.play(Write(limit_eq))167 # self.wait(2)168 169 # 淡出函数极限过程标题、极限表达式,但保留函数图像和x0点170 self.play(171 FadeOut(continuous_title),172 FadeOut(limit_eq)173 # 保留函数图像、x0点和x0标签174 )175 self.wait(1)176 177 # 演示反例:函数极限不存在的情况178 self.wait(2) # 添加等待而不是空的播放动画179 180 # 显示数列极限过程标题181 self.play(Write(discrete_title))182 self.wait(1)183 184 # 构造两个不同的数列,都趋向于0185 # 数列1: x_n = 1/n186 # 数列2: x_n = (-1)^n/(n+1)187 188 n_values = list(range(1, 10)) # n = 1 to 9189 seq1_values = [1/(n+1) for n in n_values] # x1 = 1/2, x2 = 1/3, ...190 seq2_values = [(-1)**n/(n+1) for n in n_values]191 192 # 显示第一个数列的点在x轴上193 for i, x_val in enumerate(seq1_values):194 y_val = func(x_val)195 dot_x = Dot(axes.c2p(x_val, 0), color=RED)196 dot_y = Dot(axes.c2p(x_val, y_val), color=RED)197 line = DashedLine(dot_x, dot_y, color=RED)198 label = MathTex(f"x_{i+1}", color=RED).scale(0.5)199 label.next_to(dot_x, DOWN, buff=0.1)200 201 seq1_dots_x.append(dot_x)202 seq1_dots_y.append(dot_y)203 seq1_lines.append(line)204 seq1_labels.append(label)205 206 self.play(207 Create(dot_x),208 Create(dot_y),209 Create(line),210 Write(label),211 run_time=0.8212 )213 214 self.wait(1)215 216 # 显示第二个数列的点在x轴上,并淡出第一个数列的所有点217 for i, x_val in enumerate(seq2_values):218 y_val = func(x_val)219 dot_x = Dot(axes.c2p(x_val, 0), color=GREEN)220 dot_y = Dot(axes.c2p(x_val, y_val), color=GREEN)221 line = DashedLine(dot_x, dot_y, color=GREEN)222 label = MathTex(f"y_{i+1}", color=GREEN).scale(0.5)223 label.next_to(dot_x, UP, buff=0.1)224 225 seq2_dots_x.append(dot_x)226 seq2_dots_y.append(dot_y)227 seq2_lines.append(line)228 seq2_labels.append(label)229 230 # 第一个点时淡出所有第一个数列的点231 fade_out_animations = []232 if i == 0:233 fade_out_animations = [234 *[FadeOut(dot) for dot in seq1_dots_x],235 *[FadeOut(dot) for dot in seq1_dots_y],236 *[FadeOut(line) for line in seq1_lines],237 *[FadeOut(label) for label in seq1_labels]238 ]239 240 self.play(241 *fade_out_animations,242 Create(dot_x),243 Create(dot_y),244 Create(line),245 Write(label),246 run_time=0.8247 )248 249 self.wait(2)250 251 # 显示这些数列对应的函数值都趋向于0252 limit_eq = MathTex(253 r"\lim_{n \to \infty} f(x_n) = \lim_{n \to \infty} f(y_n) = 0",254 color=ORANGE255 ).scale(0.7)256 limit_eq.next_to(axes, DOWN, buff=0.3)257 258 self.play(Write(limit_eq))259 self.wait(2)260 261 # 淡出数列极限过程标题、极限表达式和数列点,但保留函数图像和x0点262 self.play(263 FadeOut(discrete_title),264 FadeOut(limit_eq),265 # 淡出两个数列的所有元素266 # *[FadeOut(dot) for dot in seq1_dots_x],267 # *[FadeOut(dot) for dot in seq1_dots_y],268 # *[FadeOut(line) for line in seq1_lines],269 # *[FadeOut(label) for label in seq1_labels],270 *[FadeOut(dot) for dot in seq2_dots_x],271 *[FadeOut(dot) for dot in seq2_dots_y],272 *[FadeOut(line) for line in seq2_lines],273 *[FadeOut(label) for label in seq2_labels]274 )275 self.wait(1)276 277 # 在进入反例演示前,淡出函数图像和x0点278 self.play(279 FadeOut(graph),280 FadeOut(graph_label),281 FadeOut(x0_dot),282 FadeOut(x0_label)283 )284 self.wait(2)285 286 # 反例演示:函数极限不存在的情况287 # --- 反例:函数极限不存在 ---288 counter_example_title = Text("反例:函数极限不存在的情况", font="SimSun", color=ORANGE).scale(0.6)289 counter_example_title.to_corner(UL).shift(DOWN*1)290 291 # 更聚焦的坐标系292 new_axes = Axes(293 x_range=[-0.5, 0.5, 0.1],294 y_range=[-1.2, 1.2, 0.2],295 x_length=8,296 y_length=5,297 axis_config={"color": WHITE, "include_tip": True},298 ).shift(DOWN * 0.5)299 300 new_y_label = MathTex("f(x)").next_to(new_axes.y_axis.get_end(), LEFT)301 302 self.play(303 Transform(axes, new_axes),304 Transform(y_label, new_y_label),305 Write(counter_example_title)306 )307 308 # ✅ 更新 axes 引用309 axes = new_axes310 311 # 添加x0标记点312 x0_dot = Dot(axes.c2p(0, 0), color=YELLOW)313 x0_label = MathTex("x_0", color=YELLOW).scale(0.7)314 x0_label.next_to(x0_dot, UR, buff=0.1)315 316 self.play(317 Create(x0_dot),318 Write(x0_label)319 )320 321 # 定义安全函数322 def func2(x):323 if abs(x) < 1e-8:324 return 0325 y = np.sin(1 / x)326 return np.clip(y, -1, 1)327 328 # 定义绘图函数:使用对数间隔采样329 def get_sin_1x_graph(axes, x_min, x_max, color=BLUE, use_left=False):330 num_points = 1000331 if use_left:332 x_vals = -np.logspace(np.log10(x_min), np.log10(x_max), num_points)333 else:334 x_vals = np.logspace(np.log10(x_min), np.log10(x_max), num_points)335 336 y_vals = []337 for x in x_vals:338 if abs(x) < 1e-8:339 y_vals.append(0)340 else:341 y = np.sin(1 / x)342 y_vals.append(np.clip(y, -1, 1))343 344 y_vals = np.array(y_vals)345 points = [axes.c2p(x, y) for x, y in zip(x_vals, y_vals)]346 347 graph = VMobject(color=color)348 graph.set_points_as_corners(points)349 graph.make_smooth() # 轻微平滑350 return graph351 352 # ✅ 使用对数采样生成图像353 graph2_left = get_sin_1x_graph(axes, x_min=0.02, x_max=0.5, color=BLUE, use_left=True)354 graph2_right = get_sin_1x_graph(axes, x_min=0.02, x_max=0.5, color=BLUE, use_left=False)355 356 graph2_label = MathTex(r"f(x) = \sin\left(\frac{1}{x}\right)", color=BLUE).scale(0.7)357 graph2_label.next_to(axes.c2p(0.3, 0.8), UR, buff=0.3)358 359 self.play(360 Create(graph2_left),361 Create(graph2_right),362 Write(graph2_label)363 )364 self.wait(2)365 366 # 显示数列极限过程标题367 self.play(Write(discrete_title))368 self.wait(1)369 370 # 构造两个趋向于0的数列,但函数值趋向于不同极限371 # 数列1: x_n = 1/(π/2 + 2nπ) (函数值趋向于1)372 # 数列2: x_n = 1/(3π/2 + 2nπ) (函数值趋向于-1)373 374 n_values = list(range(1, 6)) # 从n=1开始375 seq3_values = [1/(np.pi/2 + 2*n*np.pi) for n in n_values]376 seq4_values = [1/(3*np.pi/2 + 2*n*np.pi) for n in n_values]377 378 # 显示第一个数列的点在x轴上379 seq3_dots_x = []380 seq3_dots_y = []381 seq3_lines = []382 seq3_labels = []383 for i, x_val in enumerate(seq3_values):384 y_val = func2(x_val)385 dot_x = Dot(axes.c2p(x_val, 0), color=RED)386 dot_y = Dot(axes.c2p(x_val, y_val), color=RED)387 line = DashedLine(dot_x, dot_y, color=RED)388 label = MathTex(f"x_{i+1}", color=RED).scale(0.5)389 label.next_to(dot_x, DOWN, buff=0.1)390 391 seq3_dots_x.append(dot_x)392 seq3_dots_y.append(dot_y)393 seq3_lines.append(line)394 seq3_labels.append(label)395 396 self.play(397 Create(dot_x),398 Create(dot_y),399 Create(line),400 Write(label),401 run_time=0.8402 )403 404 self.wait(1)405 406 # 显示第二个数列的点在x轴上,不淡出第一个数列的点407 seq4_dots_x = []408 seq4_dots_y = []409 seq4_lines = []410 seq4_labels = []411 for i, x_val in enumerate(seq4_values):412 y_val = func2(x_val)413 dot_x = Dot(axes.c2p(x_val, 0), color=GREEN)414 dot_y = Dot(axes.c2p(x_val, y_val), color=GREEN)415 line = DashedLine(dot_x, dot_y, color=GREEN)416 label = MathTex(f"y_{i+1}", color=GREEN).scale(0.5) # 改为yn标记417 label.next_to(dot_x, UP, buff=0.1)418 419 seq4_dots_x.append(dot_x)420 seq4_dots_y.append(dot_y)421 seq4_lines.append(line)422 seq4_labels.append(label)423 424 self.play(425 Create(dot_x),426 Create(dot_y),427 Create(line),428 Write(label),429 run_time=0.8430 )431 432 self.wait(2)433 434 # 显示两个数列的函数值趋向于不同"极限"435 limit_eq2 = MathTex(436 r"\lim_{n \to \infty} f(x_n) = 1 \neq -1 = \lim_{n \to \infty} f(y_n)",437 color=ORANGE438 ).scale(0.7)439 limit_eq2.next_to(axes, DOWN, buff=0.3)440 441 # 淡出旧的x0点和标签,以及数列极限过程标题,但不淡出数列极限表达式442 self.play(443 FadeOut(x0_dot),444 FadeOut(x0_label),445 FadeOut(discrete_title),446 Write(limit_eq2)447 )448 self.wait(1)449 450 conclusion = Text(451 "存在趋向于同一点的不同数列,\n但函数值趋向于不同极限\n因此函数在该点的极限不存在",452 font="SimSun"453 ).scale(0.5)454 # 将结论文本下移并左移,避免与坐标轴重叠,分三行显示455 conclusion.next_to(counter_example_title, DOWN, buff=1.0).shift(LEFT*0.2)456 457 self.play(Write(conclusion))458 self.wait(3)459 460 # 不再淡出任何元素,保持最后一帧画面461 self.wait(2)462 463 # final_text = Text(464 # "海涅定理表明:\n函数极限存在当且仅当对于所有趋向于该点的数列,\n" +465 # "对应的函数值数列都收敛于同一极限。\n" +466 # "这为我们判断函数极限的存在性提供了有力工具。",467 # font="SimSun"468 # ).scale(0.6)469 # final_text.move_to(ORIGIN)470 471 # self.play(Write(final_text))472 # self.wait(3)473 474def main():475 import os476 os.system("manim -pql heine_theorem.py HeineTheorem")477 478if __name__ == "__main__":479 main() 讲解
这个视频围绕「海涅定理」展开。画面把问题、图像和公式放在同一条理解路径中,让抽象关系先被看见。
开头先建立问题背景和主要对象,让观察从标题、坐标或第一组关系进入。
画面中出现的文字「海涅定理」给出视觉入口。随后画面通过对象创建、移动、淡入淡出和高亮,把抽象命题拆成可以跟随的步骤。
随后出现更具体的图像或公式提示,动画会把局部对象和整体结论联系起来。这里可以重点观察变量、曲线、区域或向量如何随镜头推进而变化。
核心公式可以写成:
这类公式可以和画面中的符号一一对应。
观察路径
观察路径可以分三步:先锁定「海涅定理」中的核心对象,尤其留意海涅定理、函数极限、数列极限之间的联系;再跟随画面中变量、图像或向量的变化;最后回到公式或结论,判断局部变化如何支撑整体关系。
本页可以从海涅定理、函数极限、数列极限这些概念进入,继续沿相邻问题观察同一类数学结构。