nested_interval_theorem.py
1from manim import *2import numpy as np3 4class NestedIntervalTheorem(Scene):5 def construct(self):6 # 配置参数7 x_range = [-1, 8, 1]8 y_range = [-0.5, 3.5, 0.5]9 10 # 创建坐标系11 axes = Axes(12 x_range=x_range,13 y_range=y_range,14 x_length=10,15 y_length=6,16 axis_config={17 "include_tip": True,18 "include_numbers": False # 不显示坐标轴数值19 }20 )21 22 self.play(Create(axes, run_time=3))23 24 # 第一步:展示区间套的构造25 intervals = []26 interval_labels = []27 y_positions = [0.5, 1.5, 2.5]28 interval_data = [29 (1, 7, BLUE), # [a₁, b₁]30 (2, 5, RED), # [a₂, b₂]31 (3, 4, GREEN) # [a₃, b₃]32 ]33 34 # 逐个显示区间及其标签35 for i, (a, b, color) in enumerate(interval_data):36 interval = Line(37 axes.c2p(a, y_positions[i]),38 axes.c2p(b, y_positions[i]),39 color=color,40 stroke_width=341 )42 # 添加端点标签43 left_point = Dot(axes.c2p(a, y_positions[i]), color=color, radius=0.05)44 right_point = Dot(axes.c2p(b, y_positions[i]), color=color, radius=0.05)45 left_label = MathTex(f"a_{i+1}", color=color).scale(0.6)46 right_label = MathTex(f"b_{i+1}", color=color).scale(0.6)47 left_label.next_to(left_point, DL, buff=0.1)48 right_label.next_to(right_point, DR, buff=0.1)49 50 # 区间标签51 interval_label = MathTex(f"[a_{i+1}, b_{i+1}]", color=color).scale(0.8)52 interval_label.next_to(interval, UP, buff=0.2)53 54 intervals.append(interval)55 interval_labels.append(interval_label)56 57 self.play(58 Create(interval),59 Create(left_point),60 Create(right_point),61 Write(left_label),62 Write(right_label),63 Write(interval_label),64 run_time=265 )66 67 # 显示包含关系68 if i > 0:69 subset_symbol = MathTex(r"\subset", color=YELLOW).scale(0.6)70 subset_symbol.next_to(interval, LEFT, buff=0.2)71 72 left_proj = DashedLine(73 axes.c2p(interval_data[i-1][0], y_positions[i-1]),74 axes.c2p(interval_data[i-1][0], y_positions[i]),75 color=YELLOW,76 dash_length=0.177 )78 right_proj = DashedLine(79 axes.c2p(interval_data[i-1][1], y_positions[i-1]),80 axes.c2p(interval_data[i-1][1], y_positions[i]),81 color=YELLOW,82 dash_length=0.183 )84 self.play(85 Create(left_proj),86 Create(right_proj),87 Write(subset_symbol),88 run_time=189 )90 91 # 第二步:先展示单调性92 monotone_arrows = VGroup()93 for i in range(len(interval_data)-1):94 # an的单调递增箭头95 a_monotone = Arrow(96 axes.c2p(interval_data[i][0], y_positions[i]),97 axes.c2p(interval_data[i+1][0], y_positions[i+1]),98 color=BLUE,99 buff=0.1,100 max_tip_length_to_length_ratio=0.15101 )102 # bn的单调递减箭头103 b_monotone = Arrow(104 axes.c2p(interval_data[i][1], y_positions[i]),105 axes.c2p(interval_data[i+1][1], y_positions[i+1]),106 color=RED,107 buff=0.1,108 max_tip_length_to_length_ratio=0.15109 )110 monotone_arrows.add(a_monotone, b_monotone)111 112 # 显示单调性标签(缩小并上移)113 monotone_labels = VGroup(114 VGroup(115 MathTex(r"\{a_n\}", color=BLUE),116 Text("单调递增有上界", font="SimSun")117 ).arrange(RIGHT, buff=0.1),118 VGroup(119 MathTex(r"\{b_n\}", color=RED),120 Text("单调递减有下界", font="SimSun")121 ).arrange(RIGHT, buff=0.1)122 ).arrange(DOWN, buff=0.2).scale(0.6) # 缩小字体123 monotone_labels.to_edge(LEFT, buff=0.5).shift(UP * 1.5) # 上移124 125 self.play(126 Write(monotone_labels),127 Create(monotone_arrows),128 run_time=3129 )130 131 # 定义极限点位置132 xi = 3.5 # 在这里定义xi133 134 # 然后展示收敛性135 # 显示an和bn的极限136 limit_arrows = VGroup()137 # an的极限箭头(向上)138 a_limit_arrow = Arrow(139 axes.c2p(interval_data[-1][0], y_positions[-1]),140 axes.c2p(xi, 3.2),141 color=BLUE,142 buff=0.1,143 max_tip_length_to_length_ratio=0.15144 )145 # bn的极限箭头(向上)146 b_limit_arrow = Arrow(147 axes.c2p(interval_data[-1][1], y_positions[-1]),148 axes.c2p(xi, 3.2),149 color=RED,150 buff=0.1,151 max_tip_length_to_length_ratio=0.15152 )153 limit_arrows.add(a_limit_arrow, b_limit_arrow)154 155 # # 显示极限值标签156 # limit_labels = VGroup(157 # MathTex(r"\lim_{n \to \infty} a_n = a", color=BLUE),158 # MathTex(r"\lim_{n \to \infty} b_n = b", color=RED)159 # ).arrange(DOWN, buff=0.3).scale(0.8)160 # limit_labels.next_to(monotone_labels, DOWN, buff=0.5)161 162 # self.play(163 # Create(limit_arrows),164 # Write(limit_labels),165 # run_time=2166 # )167 168 # # 显示收敛性标签(放在极限值标签下面)169 # convergence_label = MathTex(170 # r"\lim_{n \to \infty}(b_n - a_n) = 0", 171 # color=YELLOW172 # ).scale(0.8)173 # convergence_label.next_to(limit_labels, DOWN, buff=0.3)174 175 # self.play(176 # Write(convergence_label),177 # run_time=2178 # )179 180 # # 显示极限值相等(放在收敛性标签下面)181 # equals_label = MathTex(r"a = b = \xi", color=YELLOW).scale(0.8)182 # equals_label.next_to(convergence_label, DOWN, buff=0.3)183 184 # self.play(185 # Write(equals_label),186 # run_time=1.5187 # )188 189 # 1. 显示第n个区间190 final_interval = Line(191 axes.c2p(3.3, 3.2),192 axes.c2p(3.7, 3.2),193 color=GREEN,194 stroke_width=4195 )196 final_left_point = Dot(axes.c2p(3.3, 3.2), color=GREEN, radius=0.05)197 final_right_point = Dot(axes.c2p(3.7, 3.2), color=GREEN, radius=0.05)198 final_left_label = MathTex("a_n", color=GREEN).scale(0.7)199 final_right_label = MathTex("b_n", color=GREEN).scale(0.7)200 final_left_label.next_to(final_left_point, LEFT, buff=0.1)201 final_right_label.next_to(final_right_point, RIGHT, buff=0.1)202 203 self.play(204 Create(final_interval),205 Create(final_left_point),206 Create(final_right_point),207 Write(final_left_label),208 Write(final_right_label),209 run_time=2210 )211 212 # 显示a3到aN和b3到bN的箭头213 a3_to_aN = Arrow(214 axes.c2p(interval_data[-1][0], y_positions[-1]), # 从a3215 axes.c2p(3.3, 3.2), # 到aN216 color=BLUE,217 buff=0.1,218 max_tip_length_to_length_ratio=0.15219 )220 b3_to_bN = Arrow(221 axes.c2p(interval_data[-1][1], y_positions[-1]), # 从b3222 axes.c2p(3.7, 3.2), # 到bN223 color=RED,224 buff=0.1,225 max_tip_length_to_length_ratio=0.15226 )227 228 self.play(229 Create(a3_to_aN),230 Create(b3_to_bN),231 run_time=2232 )233 234 # 2. 显示极限值标签235 limit_labels = VGroup(236 MathTex(r"\lim_{n \to \infty} a_n = a", color=BLUE),237 MathTex(r"\lim_{n \to \infty} b_n = b", color=RED)238 ).arrange(DOWN, buff=0.3).scale(0.8)239 limit_labels.next_to(monotone_labels, DOWN, buff=0.5)240 241 self.play(Write(limit_labels), run_time=3)242 243 # 3. 显示极限点244 limit_point = Dot(axes.c2p(xi, 3.5), color=YELLOW, radius=0.08)245 limit_label = MathTex(r"\xi", color=YELLOW).scale(0.8)246 limit_label.next_to(limit_point, UP)247 248 vertical_line = DashedLine(249 axes.c2p(xi, y_positions[0]),250 axes.c2p(xi, 3.5),251 color=YELLOW,252 dash_length=0.1253 )254 255 self.play(256 Create(vertical_line),257 Create(limit_point),258 Write(limit_label),259 run_time=2260 )261 262 # 显示从aN,bN到极限点的箭头263 aN_to_xi = Arrow(264 axes.c2p(3.3, 3.2), # 从aN265 axes.c2p(xi, 3.5), # 到ξ266 color=BLUE_A,267 buff=0.1,268 max_tip_length_to_length_ratio=0.15269 )270 bN_to_xi = Arrow(271 axes.c2p(3.7, 3.2), # 从bN272 axes.c2p(xi, 3.5), # 到ξ273 color=RED_A,274 buff=0.1,275 max_tip_length_to_length_ratio=0.15276 )277 278 self.play(279 Create(aN_to_xi),280 Create(bN_to_xi),281 run_time=2282 )283 284 # 4. 显示收敛性和极限值相等285 convergence_label = MathTex(286 r"\lim_{n \to \infty}(b_n - a_n) = 0", 287 color=YELLOW288 ).scale(0.8)289 convergence_label.next_to(limit_labels, DOWN, buff=0.3)290 291 equals_label = MathTex(r"a = b = \xi", color=YELLOW).scale(0.8)292 equals_label.next_to(convergence_label, DOWN, buff=0.3)293 294 self.play(295 Write(convergence_label),296 run_time=3297 )298 self.play(299 Write(equals_label),300 run_time=2301 )302 303 # 5. 反证法演示304 eta = 4.2305 other_point = Dot(axes.c2p(eta, 3.5), color=RED, radius=0.08)306 other_label = MathTex(r"\eta", color=RED).scale(0.8)307 other_label.next_to(other_point, UP)308 309 self.play(310 Create(other_point),311 Write(other_label),312 run_time=2.5313 )314 315 # 显示η不等于极限点,加上"任意"的表述316 neq_label = MathTex(r"\forall \eta \neq \xi", color=RED).scale(0.8) # 添加任意符号317 neq_label.next_to(other_point, RIGHT, buff=0.3)318 319 self.play(Write(neq_label), run_time=1.5)320 321 # 显示存在区间使得η不属于该区间322 notin_label = MathTex(r"\exists n: \eta \notin [a_n, b_n]", color=RED).scale(0.8) # 添加存在符号323 notin_label.next_to(neq_label, RIGHT, buff=0.2)324 325 self.play(Write(notin_label), run_time=1.5)326 327 # 用垂直向下的箭头表示η不在区间内(加粗)328 down_arrow = Arrow(329 axes.c2p(eta, 3.5),330 axes.c2p(eta, 3.2),331 color=RED,332 buff=0.1,333 max_tip_length_to_length_ratio=0.2,334 stroke_width=6335 )336 337 self.play(Create(down_arrow), run_time=2)338 339 # 添加an, bn的向上虚线和延长线340 left_proj = DashedLine(341 axes.c2p(3.3, 3.2), # 从an开始342 axes.c2p(3.3, 3.5), # 向上延伸343 color=YELLOW,344 dash_length=0.1345 )346 right_proj = DashedLine(347 axes.c2p(3.7, 3.2), # 从bn开始348 axes.c2p(3.7, 3.5), # 向上延伸349 color=YELLOW,350 dash_length=0.1351 )352 right_extension = DashedLine(353 axes.c2p(3.7, 3.2), # 从bn开始354 axes.c2p(4.5, 3.2), # 向右延伸超过η355 color=GREEN_A,356 dash_length=0.1357 )358 359 self.play(360 Create(left_proj),361 Create(right_proj),362 Create(right_extension),363 run_time=2364 )365 366 # 显示矛盾(只显示叉号)367 x = Cross(scale_factor=0.25, stroke_width=6).move_to(other_point) # 叉号保持在η点368 369 self.play(Create(x), run_time=1.5)370 371 self.wait(2)372 373 # 最后强调唯一性374 xi_label = MathTex(r"\xi", color=YELLOW).scale(0.8)375 unique_text = Text("唯一", font="SimSun", color=YELLOW).scale(0.7)376 377 # 将ξ和"唯一"水平排列378 unique_group = VGroup(xi_label, unique_text).arrange(RIGHT, buff=0.2)379 unique_group.next_to(notin_label, DOWN, buff=0.3) # 放在不等式下方380 381 self.play(382 limit_point.animate.scale(1.5).set_color(YELLOW),383 limit_label.animate.scale(1.5).set_color(YELLOW),384 Write(unique_group),385 run_time=2.5386 )387 388 self.wait(5) # 最后的等待时间389 390def main():391 import os392 os.system("manim -pql nested_interval_theorem.py NestedIntervalTheorem")393 394if __name__ == "__main__":395 main() 讲解
这个视频围绕「单调有界原理证闭区间套定理」展开。画面把问题、图像和公式放在同一条理解路径中,让抽象关系先被看见。
开头先建立问题背景和主要对象,让观察从标题、坐标或第一组关系进入。
画面中出现的文字「单调递增有上界」给出视觉入口。随后画面通过对象创建、移动、淡入淡出和高亮,把抽象命题拆成可以跟随的步骤。
随后出现更具体的图像或公式提示,动画会把局部对象和整体结论联系起来。这里可以重点观察变量、曲线、区域或向量如何随镜头推进而变化。
核心公式可以写成:
这类公式可以和画面中的符号一一对应。
观察路径
观察路径可以分三步:先锁定「单调有界原理证闭区间套定理」中的核心对象,尤其留意单调有界原理、闭区间套定理、数列收敛之间的联系;再跟随画面中变量、图像或向量的变化;最后回到公式或结论,判断局部变化如何支撑整体关系。
本页可以从单调有界原理、闭区间套定理、数列收敛、确界原理这些概念进入,继续沿相邻问题观察同一类数学结构。