数学分析基础可视化

海涅定理

围绕海涅定理,观察海涅定理、函数极限、数列极限之间的关系与推理路径。

打开原视频

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()

讲解

这个视频围绕「海涅定理」展开。画面把问题、图像和公式放在同一条理解路径中,让抽象关系先被看见。

开头先建立问题背景和主要对象,让观察从标题、坐标或第一组关系进入。

画面中出现的文字「海涅定理」给出视觉入口。随后画面通过对象创建、移动、淡入淡出和高亮,把抽象命题拆成可以跟随的步骤。

随后出现更具体的图像或公式提示,动画会把局部对象和整体结论联系起来。这里可以重点观察变量、曲线、区域或向量如何随镜头推进而变化。

核心公式可以写成:

f(x)f(x)

这类公式可以和画面中的符号一一对应。

观察路径

观察路径可以分三步:先锁定「海涅定理」中的核心对象,尤其留意海涅定理、函数极限、数列极限之间的联系;再跟随画面中变量、图像或向量的变化;最后回到公式或结论,判断局部变化如何支撑整体关系。

本页可以从海涅定理、函数极限、数列极限这些概念进入,继续沿相邻问题观察同一类数学结构。