极限思想可视化

割圆术与数列极限

围绕割圆术与数列极限,观察割圆术、数列极限、多边形逼近之间的关系与推理路径。

打开原视频

circle_inscription_limit.py
1from manim import *2import numpy as np3 4# 配置中文字体支持5config.tex_template = TexTemplateLibrary.ctex6config.tex_template.add_to_preamble(r"\setCJKmainfont{STSong}")7 8class CircleInscriptionLimit(Scene):9    def construct(self):10        # 标题11        title = Text("割圆术与数列极限", font="STSong", font_size=40, color=BLUE)12        title.to_edge(UP, buff=0.5)13        self.play(Write(title), run_time=1.5)14        self.wait(1)15        16        # 说明文字17        explanation = Text(18            "通过不断增加正多边形的边数,观察周长数列如何收敛到圆的周长",19            font="STSong", font_size=24, color=WHITE20        ).next_to(title, DOWN, buff=0.3)21        self.play(FadeIn(explanation), run_time=1.5)22        self.wait(1)23        24        # 创建圆(固定半径,但显示大小不变)25        radius = 1  # 半径改为126        display_radius = 2  # 显示半径保持2,图形大小不变27        circle = Circle(radius=display_radius, color=BLUE, stroke_width=3)28        circle.shift(LEFT * 4)  # 更靠左一些,为右侧文字留出空间29        30        # 圆心31        center = circle.get_center()32        center_dot = Dot(center, color=YELLOW, radius=0.08)33        center_label = MathTex("O", font_size=24, color=YELLOW).next_to(center_dot, DOWN, buff=0.15)34        35        # 显示半径标注(从圆心到圆上一点,标注r=1)36        radius_point = circle.point_at_angle(0)  # 右侧的点37        radius_line = Line(center, radius_point, color=GREEN, stroke_width=2)38        radius_label = MathTex("r=1", font_size=20, color=GREEN)39        radius_label.next_to(radius_line, UP, buff=0.1).shift(RIGHT * 0.3)40        41        # 显示圆42        self.play(Create(circle), run_time=1.5)43        self.play(Create(center_dot), Write(center_label), run_time=1)44        self.play(Create(radius_line), Write(radius_label), run_time=1)45        self.wait(0.5)46        47        # 计算圆的中心位置和右侧目标位置(与圆对称)48        circle_center = circle.get_center()49        circle_right_edge = circle_center[0] + display_radius50        51        # 统一的字体大小和行间距52        font_size = 2253        line_spacing = 0.3554        55        # 目标位置:圆的右侧,垂直方向与圆中心对齐(对称),整体右移1个单位56        target_x = circle_right_edge + 0.8 + 1.0  # 整体右移1个单位57        target_y_start = circle_center[1] - 1  # 向下平移一个单位(标量值)58        59        # 先显示实际值60        actual_value_text = Text("实际值:", font="STSong", font_size=font_size, color=GREEN)61        actual_value = MathTex(62            r"2\pi \cdot 1 = 2\pi \approx 6.283",63            font_size=font_size, color=GREEN64        )65        actual_value_group = VGroup(actual_value_text, actual_value).arrange(RIGHT, buff=0.15)66        actual_value_group.move_to([target_x, target_y_start + 2.5, 0])67        68        # 显示极限公式(n趋近于无穷显示在右边)69        limit_title = Text("极限:", font="STSong", font_size=font_size, color=YELLOW)70        limit_formula = MathTex(71            r"\lim_{n \to \infty} P_n = 2\pi r",72            font_size=font_size, color=YELLOW73        )74        limit_group = VGroup(limit_title, limit_formula).arrange(RIGHT, buff=0.2)75        limit_group.next_to(actual_value_group, DOWN, buff=line_spacing).align_to(actual_value_group, LEFT)76        77        # 数列标题78        sequence_title = Text("周长数列:", font="STSong", font_size=font_size, color=GREEN)79        sequence_title.next_to(limit_group, DOWN, buff=line_spacing).align_to(actual_value_group, LEFT)80        81        # 显示文字(按顺序:实际值 -> 极限 -> 周长数列)82        self.play(Write(actual_value_group), run_time=1)83        self.wait(0.3)84        self.play(Write(limit_group), run_time=1.2)85        self.wait(0.3)86        self.play(Write(sequence_title), run_time=1)87        self.wait(0.5)88        89        # 存储多边形和标签90        current_polygon = None91        current_label = None92        sequence_values = []93        94        # 从正四边形开始,每次边数乘以2,计算到64边形95        n_values = [4, 8, 16, 32, 64]96        97        # 存储数列显示项(保留所有,不删除)98        sequence_items = []99        error_texts = []100        101        # 列管理:每列最多显示6项,然后换列102        items_per_column = 6103        current_column = 0104        items_in_current_column = 0105        column_start_x = target_x  # 第一列的x坐标106        column_spacing = 1.8  # 列之间的间距107        108        # 动画:逐步增加边数109        for idx, n in enumerate(n_values):110            # 计算正n边形的顶点(使用显示半径)111            vertices = []112            for i in range(n):113                angle = 2 * PI * i / n - PI / 2  # 从顶部开始114                x = center[0] + display_radius * np.cos(angle)115                y = center[1] + display_radius * np.sin(angle)116                vertices.append([x, y, 0])117            118            # 创建正多边形119            polygon = Polygon(*vertices, color=RED, stroke_width=2.5)120            121            # 计算周长(使用实际半径值1)122            # 正n边形内接于半径为r的圆,边长 = 2r * sin(π/n)123            side_length = 2 * radius * np.sin(PI / n)124            perimeter = n * side_length125            sequence_values.append(perimeter)126            127            # 更新多边形(替换旧的)128            if current_polygon is not None:129                self.play(Transform(current_polygon, polygon), run_time=0.8)130            else:131                current_polygon = polygon132                self.add(polygon)133                self.play(Create(polygon), run_time=1)134            135            # 更新标签136            new_label = Text(137                f"正{n}边形",138                font="STSong", font_size=20, color=ORANGE139            ).next_to(circle, DOWN, buff=0.3)140            141            if current_label is not None:142                self.play(Transform(current_label, new_label), run_time=0.5)143            else:144                current_label = new_label145                self.add(new_label)146                self.play(Write(new_label), run_time=0.5)147            148            # 检查是否需要换列(P32开始换到右侧列)149            # P4, P8, P16在第一列,P32, P64在第二列150            if n >= 32:151                if current_column == 0:152                    current_column = 1153                    items_in_current_column = 0154            elif items_in_current_column >= items_per_column:155                current_column += 1156                items_in_current_column = 0157            158            # 计算当前列的x坐标159            if current_column == 1:160                # 第二列:找到第一列最右侧的位置(P16的位置),然后在其右侧显示P32161                # 此时sequence_items中应该有P4, P8, P16(都是第一列的)162                if len(sequence_items) >= 3:163                    # 找到所有第一列项的右边界(包括公式和误差文本)164                    max_right = 0165                    for item in sequence_items:166                        # item是VGroup,包含formula_with_result和error_text167                        # 需要找到整个组的右边界168                        item_right = item.get_right()[0]169                        if item_right > max_right:170                            max_right = item_right171                    172                    # P32显示在P16右侧,留出适当间距(1.2个单位),再右移1个单位173                    current_column_x = max_right + 1.2 + 1.0174                else:175                    # 如果还没有足够的项,使用默认位置176                    current_column_x = column_start_x + column_spacing * 2177            else:178                current_column_x = column_start_x + current_column * column_spacing179            180            # 计算目标位置(从数列标题下方开始,使用统一行间距)181            if len(sequence_items) == 0:182                # 第一项:从数列标题下方开始183                target_pos = sequence_title.get_bottom() + DOWN * line_spacing184                target_pos = np.array([current_column_x, target_pos[1], 0])185            else:186                if items_in_current_column == 0:187                    # 新列的第一项:从数列标题下方开始188                    target_pos = sequence_title.get_bottom() + DOWN * line_spacing189                    # 如果是第二列,整体上移两个单位190                    if current_column == 1:191                        target_pos = np.array([current_column_x, target_pos[1] + 2.0, 0])192                    else:193                        target_pos = np.array([current_column_x, target_pos[1], 0])194                else:195                    # 同一列的后续项:放在上一项下方(使用统一行间距)196                    prev_item = sequence_items[-1]197                    target_pos = prev_item.get_bottom() + DOWN * line_spacing198                    target_pos = np.array([prev_item.get_center()[0], target_pos[1], 0])199            200            # 显示计算公式和结果(在同一行,用≈连接,使用统一字体大小)201            formula_with_result = MathTex(202                f"P_{{{n}}} = {n} \\times 2r \\times \\sin\\left(\\frac{{\\pi}}{{{n}}}\\right) \\approx {perimeter:.4f}",203                font_size=font_size, color=YELLOW204            )205            # 移动到目标位置206            formula_with_result.move_to(target_pos)207            # 只有第一列才进行左对齐,第二列使用绝对位置208            if current_column == 0:209                formula_with_result.align_to(sequence_title, LEFT)210            211            # 显示公式和结果212            self.play(Write(formula_with_result), run_time=1.5)213            self.wait(0.3)214            215            # 计算误差216            true_perimeter = 2 * PI * radius217            error = abs(perimeter - true_perimeter)218            219            # 显示误差(用绝对值≈误差的形式,使用统一字体大小和行间距)220            error_text = MathTex(221                f"|P_{{{n}}} - 2\\pi r| \\approx {error:.4f}",222                font_size=font_size, color=GRAY223            )224            error_text.next_to(formula_with_result, DOWN, buff=line_spacing)225            # 误差文本与公式左对齐226            error_text.align_to(formula_with_result, LEFT)227            error_texts.append(error_text)228            self.play(Write(error_text), run_time=0.8)229            230            # 将公式和误差组合在一起231            complete_group = VGroup(formula_with_result, error_text)232            sequence_items.append(complete_group)233            items_in_current_column += 1234            235            # 短暂等待236            self.wait(0.3)237        238        # 显示收敛过程总结239        self.wait(1)240        241        # 创建收敛性说明 - 接在第二列下面显示(n趋近于无穷)242        convergence_text = VGroup(243            Text("当 ", font="STSong", font_size=font_size, color=YELLOW),244            MathTex(r"n \to \infty", font_size=font_size, color=YELLOW),245            Text(" 时,", font="STSong", font_size=font_size, color=YELLOW),246            MathTex(r"P_n = n \times 2r \times \sin\left(\frac{\pi}{n}\right)", font_size=font_size, color=YELLOW),247            MathTex(r" \to 2\pi r", font_size=font_size, color=YELLOW)248        ).arrange(RIGHT, buff=0.1)249        250        # 找到最后一个数列项的位置,接在第二列下面251        if len(sequence_items) > 0:252            # 找到第二列(P32, P64)的位置253            second_column_items = []254            for item in sequence_items:255                item_x = item.get_center()[0]256                # 判断是否属于第二列(x坐标大于第一列)257                if item_x > column_start_x + column_spacing:258                    second_column_items.append(item)259            260            if second_column_items:261                # 找到第二列最后一项的位置262                last_second_column_item = second_column_items[-1]263                convergence_text.next_to(last_second_column_item, DOWN, buff=line_spacing)264                convergence_text.align_to(last_second_column_item, LEFT)265            else:266                # 如果没有第二列,放在最后一项下面267                convergence_text.next_to(sequence_items[-1], DOWN, buff=line_spacing)268                convergence_text.align_to(sequence_items[-1], LEFT)269        else:270            convergence_text.next_to(sequence_title, DOWN, buff=line_spacing)271            convergence_text.align_to(sequence_title, LEFT)272        273        self.play(Write(convergence_text), run_time=1.5)274        self.wait(1)275        276        # 显示数列的极限定义 - 放在收敛说明下方277        limit_def_title = Text("数列极限的定义:", font="STSong", font_size=20, color=BLUE)278        limit_def_title.next_to(convergence_text, DOWN, buff=0.3).align_to(convergence_text, LEFT)279        280        limit_def = MathTex(281            r"\forall \varepsilon > 0, \exists N \in \mathbb{N},",282            font_size=18, color=WHITE283        )284        limit_def.next_to(limit_def_title, DOWN, buff=0.2).align_to(limit_def_title, LEFT)285        286        limit_def2 = MathTex(287            r"\text{当 } n > N \text{ 时}, |P_n - 2\pi r| < \varepsilon",288            font_size=18, color=WHITE289        )290        limit_def2.next_to(limit_def, DOWN, buff=0.1).align_to(limit_def, LEFT)291        292        self.play(Write(limit_def_title), run_time=1)293        self.play(Write(limit_def), run_time=1.5)294        self.play(Write(limit_def2), run_time=1.5)295        self.wait(2)296        297        # 高亮显示最后的几个值,展示收敛298        if len(sequence_items) > 0:299            highlight_animations = []300            for item in sequence_items[-3:]:301                highlight_animations.append(item.animate.set_color(YELLOW))302            303            self.play(*highlight_animations, run_time=1.5)304            self.wait(1)305        306        # 最终总结(放在极限定义下面显示,分两行,字体小一点)307        final_text_line1 = Text(308            "割圆术展示了通过不断倍增正多边形的边数,",309            font="STSong", font_size=font_size - 3, color=GREEN310        )311        final_text_line2 = Text(312            "以有限步骤刻画无限逼近的过程。",313            font="STSong", font_size=font_size - 3, color=GREEN314        )315        316        final_text = VGroup(final_text_line1, final_text_line2).arrange(DOWN, buff=0.2, aligned_edge=LEFT)317        318        # 放在极限定义下面,确保不出框319        final_text.next_to(limit_def2, DOWN, buff=line_spacing).align_to(limit_def_title, LEFT)320        321        # 检查是否出框,如果出框则调整位置322        if final_text.get_bottom()[1] < -config.frame_height / 2 + 0.5:323            # 如果出框,上移一些324            final_text.shift(UP * 0.3)325        326        self.play(Write(final_text_line1), run_time=1.0)327        self.play(Write(final_text_line2), run_time=1.0)328        self.wait(2)329        330        # 不淡出,保持所有内容显示

讲解

这个视频围绕「割圆术与数列极限」展开。画面把问题、图像和公式放在同一条理解路径中,让抽象关系先被看见。

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

画面中出现的文字「割圆术与数列极限」给出视觉入口。随后画面通过对象创建、移动、淡入淡出和高亮,把抽象命题拆成可以跟随的步骤。

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

核心公式可以写成:

r=1r=1

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

观察路径

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

本页可以从割圆术、数列极限、多边形逼近、极限过程这些概念进入,继续沿相邻问题观察同一类数学结构。