多元微积分可视化

小矩形法证明格林公式

用区域分割、单个小矩形近似、内部边界抵消和黎曼和极限解释格林公式的证明思路。

打开原视频

green_theorem_demo-1.py
1from manim import *2import numpy as np3 4config.tex_template = TexTemplateLibrary.ctex5config.tex_template.add_to_preamble(r"\setCJKmainfont{STSong}")6 7 8class GreenTheoremRectangles(Scene):9    def construct(self):10        # ========== 准备:标题 + 区域 D + 边界 L ==========11        title = Text("格林公式证明 —— 小矩形法", font="SimSun", color=YELLOW).scale(0.8)12        title.to_edge(UP, buff=0.3)13        self.play(Write(title), run_time=1.5)14        self.wait(0.5)15 16        # 画一个不规则闭合区域17        def region_curve(t):18            r = 2 + 0.3 * np.sin(3 * t) + 0.2 * np.cos(5 * t)19            return np.array([r * np.cos(t), r * np.sin(t), 0])20 21        boundary = ParametricFunction(22            region_curve, t_range=[0, TAU], color=BLUE, stroke_width=3,23        )24        center_offset = DOWN * 0.325        boundary.shift(center_offset)26 27        region_points = [region_curve(t) + center_offset for t in np.linspace(0, TAU, 200)]28        region_fill = Polygon(29            *region_points, fill_color=BLUE_E, fill_opacity=0.15, stroke_width=0,30        )31 32        d_label = MathTex("D", color=BLUE).scale(0.9)33        d_label.move_to(boundary.get_center())34        l_label = MathTex("L", color=YELLOW).scale(0.7)35        l_label.next_to(boundary, UR, buff=-0.3)36 37        self.play(FadeIn(region_fill), Create(boundary), run_time=2)38        self.play(Write(d_label), Write(l_label), run_time=1)39 40        # 逆时针方向箭头41        arrow_positions = [0.3, 1.2, 2.1, 3.0, 4.0, 5.0]42        direction_arrows = VGroup()43        for t in arrow_positions:44            pos = region_curve(t) + center_offset45            dt = 0.0546            next_pos = region_curve(t + dt) + center_offset47            tangent = (next_pos - pos)48            tangent = tangent / np.linalg.norm(tangent) * 0.349            arr = Arrow(50                pos - tangent * 0.5, pos + tangent * 0.5,51                buff=0, stroke_width=2.5, color=YELLOW, tip_length=0.15,52            )53            direction_arrows.add(arr)54        self.play(LaggedStartMap(Create, direction_arrows, lag_ratio=0.1), run_time=1.5)55        self.wait(0.5)56 57        # 框架说明58        framework = Text(59            "证明思路:分割 → 近似 → 求和 → 取极限",60            font="SimSun", color=WHITE,61        ).scale(0.4)62        framework.next_to(title, DOWN, buff=0.2)63        self.play(Write(framework), run_time=1.5)64        self.wait(1)65        self.play(FadeOut(framework), run_time=0.5)66 67        # ========== Step 1: 分割 ==========68        step1 = Text("Step 1: 分割 —— 将 D 分割成小矩形网格", font="SimSun", color=WHITE).scale(0.42)69        step1.next_to(title, DOWN, buff=0.2)70        self.play(Write(step1), run_time=1)71 72        # 画网格线73        grid_lines = VGroup()74        rect_size = 0.2575        grid_step = rect_size76        for x in np.arange(-2.5, 2.51, grid_step):77            grid_lines.add(Line(78                np.array([x, -2.5, 0]) + center_offset,79                np.array([x, 2.5, 0]) + center_offset,80                stroke_width=0.5, color=GREY, stroke_opacity=0.4,81            ))82        for y in np.arange(-2.5, 2.51, grid_step):83            grid_lines.add(Line(84                np.array([-2.5, y, 0]) + center_offset,85                np.array([2.5, y, 0]) + center_offset,86                stroke_width=0.5, color=GREY, stroke_opacity=0.4,87            ))88        self.play(Create(grid_lines), run_time=2)89        self.wait(0.5)90 91        # 高亮区域内的小矩形92        inner_rects = VGroup()93        for x in np.arange(-2.5, 2.25, rect_size):94            for y in np.arange(-2.5, 2.25, rect_size):95                center = np.array([x + rect_size / 2, y + rect_size / 2, 0]) + center_offset96                half = rect_size / 297                corners_in = True98                for dx in [-half, half]:99                    for dy in [-half, half]:100                        corner = center + np.array([dx, dy, 0])101                        dist = np.sqrt(corner[0] ** 2 + (corner[1] + 0.3) ** 2)102                        angle = np.arctan2(corner[1] + 0.3, corner[0])103                        r_boundary = 2 + 0.3 * np.sin(3 * angle) + 0.2 * np.cos(5 * angle)104                        if dist > r_boundary * 0.98:105                            corners_in = False106                            break107                    if not corners_in:108                        break109                if corners_in:110                    rect = Rectangle(111                        width=rect_size, height=rect_size,112                        stroke_width=1.0, stroke_color=GREEN,113                        fill_color=GREEN, fill_opacity=0.08,114                    )115                    rect.move_to(center)116                    inner_rects.add(rect)117 118        self.play(LaggedStartMap(FadeIn, inner_rects, lag_ratio=0.02), run_time=2)119        di_label = MathTex(r"D_i", color=GREEN).scale(0.6)120        di_label.move_to(inner_rects[len(inner_rects) // 2].get_center())121        self.play(Write(di_label), run_time=0.8)122        self.wait(1)123 124        # ========== Step 2: 近似 ==========125        self.play(FadeOut(step1), FadeOut(di_label))126        step2 = Text("Step 2: 近似 —— 计算单个小矩形的环路积分", font="SimSun", color=WHITE).scale(0.42)127        step2.next_to(title, DOWN, buff=0.2)128        self.play(Write(step2), run_time=1)129 130        # 淡出网格和矩形131        self.play(132            grid_lines.animate.set_stroke(opacity=0.15),133            inner_rects.animate.set_stroke(opacity=0.15).set_fill(opacity=0.02),134            run_time=1,135        )136 137        # 放大一个小矩形,放在左侧138        highlight_rect = Rectangle(139            width=2.0, height=2.0,140            stroke_color=RED, stroke_width=3,141            fill_color=RED, fill_opacity=0.15,142        )143        highlight_rect.move_to(LEFT * 4.5 + DOWN * 0.8)144        self.play(FadeIn(highlight_rect), run_time=1)145 146        rect_center = highlight_rect.get_center()147        rw = highlight_rect.get_width()148        rh = highlight_rect.get_height()149        corners = [150            rect_center + np.array([-rw / 2, -rh / 2, 0]),151            rect_center + np.array([rw / 2, -rh / 2, 0]),152            rect_center + np.array([rw / 2, rh / 2, 0]),153            rect_center + np.array([-rw / 2, rh / 2, 0]),154        ]155 156        corner_labels = VGroup(157            MathTex(r"(x_0, y_0)", color=WHITE).scale(0.35).next_to(corners[0], DL, buff=0.05),158            MathTex(r"(x_0{+}\Delta x, y_0)", color=WHITE).scale(0.35).next_to(corners[1], DR, buff=0.05),159            MathTex(r"(x_0{+}\Delta x, y_0{+}\Delta y)", color=WHITE).scale(0.3).next_to(corners[2], UR, buff=0.05),160            MathTex(r"(x_0, y_0{+}\Delta y)", color=WHITE).scale(0.35).next_to(corners[3], UL, buff=0.05),161        )162        self.play(LaggedStartMap(Write, corner_labels, lag_ratio=0.15), run_time=1.2)163 164        # 四条边箭头(逆时针)165        edge_colors = [ORANGE, TEAL, ORANGE, TEAL]166        rect_arrows = VGroup()167        for i in range(4):168            arr = Arrow(169                corners[i], corners[(i + 1) % 4],170                buff=0.05, stroke_width=3, color=edge_colors[i], tip_length=0.12,171            )172            rect_arrows.add(arr)173        self.play(LaggedStartMap(Create, rect_arrows, lag_ratio=0.15), run_time=1.5)174        self.wait(0.3)175 176        # 右侧详细计算177        calc_top = UP * 2.2 + RIGHT * 2.5178        line1 = Text("四条边的积分(逆时针):", font="SimSun", color=WHITE).scale(0.38)179        line1.move_to(calc_top, aligned_edge=LEFT)180        self.play(Write(line1), run_time=0.8)181 182        f_bottom = MathTex(183            r"\text{① 下边: }\int_{x_0}^{x_0+\Delta x} P(x, y_0)\,dx", color=ORANGE,184        ).scale(0.37)185        f_bottom.next_to(line1, DOWN, buff=0.12).align_to(line1, LEFT)186        self.play(Write(f_bottom), run_time=0.8)187 188        f_top = MathTex(189            r"\text{③ 上边: }\int_{x_0+\Delta x}^{x_0} P(x, y_0{+}\Delta y)\,dx", color=ORANGE,190        ).scale(0.37)191        f_top.next_to(f_bottom, DOWN, buff=0.1).align_to(line1, LEFT)192        self.play(Write(f_top), run_time=0.8)193 194        p_combine = MathTex(195            r"(1)+(3) = \int_{x_0}^{x_0+\Delta x}\left[P(x,y_0) - P(x,y_0{+}\Delta y)\right]dx",196            color=ORANGE,197        ).scale(0.34)198        p_combine.next_to(f_top, DOWN, buff=0.1).align_to(line1, LEFT)199        self.play(Write(p_combine), run_time=1.2)200 201        p_approx = MathTex(202            r"\approx \int_{x_0}^{x_0+\Delta x}\left(-\frac{\partial P}{\partial y}\Delta y\right)dx = -\frac{\partial P}{\partial y}\Delta x\Delta y",203            color=ORANGE,204        ).scale(0.34)205        p_approx.next_to(p_combine, DOWN, buff=0.08).align_to(line1, LEFT)206        self.play(Write(p_approx), run_time=1.2)207        self.wait(0.3)208 209        f_right = MathTex(210            r"\text{② 右边: }\int_{y_0}^{y_0+\Delta y} Q(x_0{+}\Delta x, y)\,dy", color=TEAL,211        ).scale(0.37)212        f_right.next_to(p_approx, DOWN, buff=0.12).align_to(line1, LEFT)213        self.play(Write(f_right), run_time=0.8)214 215        f_left = MathTex(216            r"\text{④ 左边: }\int_{y_0+\Delta y}^{y_0} Q(x_0, y)\,dy", color=TEAL,217        ).scale(0.37)218        f_left.next_to(f_right, DOWN, buff=0.1).align_to(line1, LEFT)219        self.play(Write(f_left), run_time=0.8)220 221        q_combine = MathTex(222            r"(2)+(4) = \int_{y_0}^{y_0+\Delta y}\left[Q(x_0{+}\Delta x,y) - Q(x_0,y)\right]dy",223            color=TEAL,224        ).scale(0.34)225        q_combine.next_to(f_left, DOWN, buff=0.1).align_to(line1, LEFT)226        self.play(Write(q_combine), run_time=1.2)227 228        q_approx = MathTex(229            r"\approx \int_{y_0}^{y_0+\Delta y}\left(\frac{\partial Q}{\partial x}\Delta x\right)dy = \frac{\partial Q}{\partial x}\Delta x\Delta y",230            color=TEAL,231        ).scale(0.34)232        q_approx.next_to(q_combine, DOWN, buff=0.08).align_to(line1, LEFT)233        self.play(Write(q_approx), run_time=1.2)234        self.wait(0.5)235 236        # 近似结论(黄框)237        final_formula = MathTex(238            r"\therefore\ \oint_{\partial D_i} Pdx + Qdy \approx \left(\frac{\partial Q}{\partial x} - \frac{\partial P}{\partial y}\right)\Delta\sigma",239            color=YELLOW,240        ).scale(0.48)241        final_formula.next_to(q_approx, DOWN, buff=0.15).align_to(line1, LEFT)242        final_box = SurroundingRectangle(final_formula, color=YELLOW, buff=0.06, corner_radius=0.05)243        self.play(Write(final_formula), Create(final_box), run_time=1.8)244        self.wait(2)245 246        # 清理,保留黄框公式在上方247        step2_objs = VGroup(248            highlight_rect, rect_arrows, corner_labels,249            line1, f_bottom, f_top, p_combine, p_approx,250            f_right, f_left, q_combine, q_approx,251        )252        self.play(FadeOut(step2_objs), run_time=1)253        self.play(254            final_formula.animate.next_to(title, DOWN, buff=0.6).to_edge(LEFT, buff=0.3),255            final_box.animate.next_to(title, DOWN, buff=0.6).to_edge(LEFT, buff=0.3),256            run_time=1.2,257        )258        new_box = SurroundingRectangle(final_formula, color=YELLOW, buff=0.06, corner_radius=0.05)259        self.play(Transform(final_box, new_box), run_time=0.5)260 261        # ========== Step 3: 求和 ==========262        self.play(FadeOut(step2))263        step3 = Text("Step 3: 求和 —— 所有小矩形的环路积分求和", font="SimSun", color=WHITE).scale(0.42)264        step3.next_to(title, DOWN, buff=0.2)265        self.play(Write(step3), run_time=1)266 267        # 恢复网格和矩形268        self.play(269            grid_lines.animate.set_stroke(opacity=0.4),270            inner_rects.animate.set_stroke(opacity=1.0).set_fill(opacity=0.08),271            run_time=1,272        )273 274        # 求和公式显示在黄框下方275        sum_eq = MathTex(276            r"\sum_i \oint_{\partial D_i} Pdx{+}Qdy",277            r"\approx",278            r"\sum_i \left(\frac{\partial Q}{\partial x} - \frac{\partial P}{\partial y}\right)\Delta\sigma_i",279            color=WHITE,280        ).scale(0.46)281        sum_eq[0].set_color(GREEN)282        sum_eq[2].set_color(TEAL)283        sum_eq.next_to(final_box, DOWN, buff=0.15).align_to(final_formula, LEFT)284        self.play(Write(sum_eq), run_time=1.5)285        self.wait(0.5)286 287        # 演示相邻矩形公共边抵消288        self.play(289            grid_lines.animate.set_stroke(opacity=0.15),290            inner_rects.animate.set_stroke(opacity=0.15).set_fill(opacity=0.02),291            run_time=1,292        )293 294        # 两个相邻矩形放大到右侧295        left_rect = Rectangle(296            width=1.8, height=1.8, stroke_color=BLUE, stroke_width=3,297            fill_color=BLUE, fill_opacity=0.1,298        )299        right_rect = Rectangle(300            width=1.8, height=1.8, stroke_color=ORANGE, stroke_width=3,301            fill_color=ORANGE, fill_opacity=0.1,302        )303        left_rect.move_to(RIGHT * 4.0 + UP * 0.5 + LEFT * 0.9)304        right_rect.move_to(RIGHT * 4.0 + UP * 0.5 + RIGHT * 0.9)305 306        left_label = MathTex(r"D_i", color=BLUE).scale(0.5)307        left_label.move_to(left_rect.get_center())308        right_label = MathTex(r"D_j", color=ORANGE).scale(0.5)309        right_label.move_to(right_rect.get_center())310 311        self.play(312            Create(left_rect), Create(right_rect),313            Write(left_label), Write(right_label),314            run_time=1.2,315        )316 317        lc = left_rect.get_center()318        lw = left_rect.get_width()319        lh = left_rect.get_height()320        l_corners = [321            lc + np.array([-lw / 2, -lh / 2, 0]),322            lc + np.array([lw / 2, -lh / 2, 0]),323            lc + np.array([lw / 2, lh / 2, 0]),324            lc + np.array([-lw / 2, lh / 2, 0]),325        ]326        left_arrows = VGroup()327        for i in range(4):328            left_arrows.add(Arrow(329                l_corners[i], l_corners[(i + 1) % 4],330                buff=0.05, stroke_width=2.5, color=BLUE, tip_length=0.12,331            ))332 333        rc = right_rect.get_center()334        r_corners = [335            rc + np.array([-lw / 2, -lh / 2, 0]),336            rc + np.array([lw / 2, -lh / 2, 0]),337            rc + np.array([lw / 2, lh / 2, 0]),338            rc + np.array([-lw / 2, lh / 2, 0]),339        ]340        right_arrows = VGroup()341        for i in range(4):342            right_arrows.add(Arrow(343                r_corners[i], r_corners[(i + 1) % 4],344                buff=0.05, stroke_width=2.5, color=ORANGE, tip_length=0.12,345            ))346 347        self.play(348            LaggedStartMap(Create, left_arrows, lag_ratio=0.15),349            LaggedStartMap(Create, right_arrows, lag_ratio=0.15),350            run_time=1.5,351        )352 353        shared_left = Arrow(l_corners[1], l_corners[2], buff=0.05, stroke_width=5, color=RED, tip_length=0.18)354        shared_right = Arrow(r_corners[3], r_corners[0], buff=0.05, stroke_width=5, color=RED, tip_length=0.18)355        shared_label = Text("方向相反→抵消!", font="SimSun", color=RED).scale(0.4)356        shared_label.next_to(VGroup(shared_left, shared_right), UP, buff=0.15)357 358        self.play(Create(shared_left), Create(shared_right), Write(shared_label), run_time=1.2)359        self.wait(0.8)360 361        cancel_text = Text("抵消!", font="SimSun", color=YELLOW).scale(0.5)362        cancel_text.move_to((shared_left.get_center() + shared_right.get_center()) / 2)363        self.play(FadeOut(shared_left), FadeOut(shared_right), FadeIn(cancel_text), run_time=0.8)364        self.play(FadeOut(cancel_text), run_time=0.5)365 366        self.play(367            FadeOut(left_rect), FadeOut(right_rect),368            FadeOut(left_arrows), FadeOut(right_arrows),369            FadeOut(left_label), FadeOut(right_label),370            FadeOut(shared_label),371        )372 373        # 恢复网格,然后显示只剩外边界374        self.play(375            grid_lines.animate.set_stroke(opacity=0.4),376            inner_rects.animate.set_stroke(opacity=1.0).set_fill(opacity=0.08),377            run_time=1,378        )379 380        # 计算外边界381        def is_cell_inside(cx, cy, rs, co):382            ctr = np.array([cx + rs / 2, cy + rs / 2, 0]) + co383            half = rs / 2384            for ddx in [-half, half]:385                for ddy in [-half, half]:386                    corner = ctr + np.array([ddx, ddy, 0])387                    dist = np.sqrt(corner[0] ** 2 + (corner[1] + 0.3) ** 2)388                    ang = np.arctan2(corner[1] + 0.3, corner[0])389                    r_b = 2 + 0.3 * np.sin(3 * ang) + 0.2 * np.cos(5 * ang)390                    if dist > r_b * 0.98:391                        return False392            return True393 394        inside_cells = set()395        for cx in np.arange(-2.5, 2.25, rect_size):396            for cy in np.arange(-2.5, 2.25, rect_size):397                if is_cell_inside(cx, cy, rect_size, center_offset):398                    inside_cells.add((round(cx, 4), round(cy, 4)))399 400        outer_boundary_lines = VGroup()401        for (cx, cy) in inside_cells:402            x0, y0, x1, y1 = cx, cy, cx + rect_size, cy + rect_size403            if (round(cx, 4), round(cy - rect_size, 4)) not in inside_cells:404                outer_boundary_lines.add(Line(np.array([x0, y0, 0]) + center_offset, np.array([x1, y0, 0]) + center_offset, stroke_width=2.5, color=GREEN))405            if (round(cx, 4), round(cy + rect_size, 4)) not in inside_cells:406                outer_boundary_lines.add(Line(np.array([x0, y1, 0]) + center_offset, np.array([x1, y1, 0]) + center_offset, stroke_width=2.5, color=GREEN))407            if (round(cx - rect_size, 4), round(cy, 4)) not in inside_cells:408                outer_boundary_lines.add(Line(np.array([x0, y0, 0]) + center_offset, np.array([x0, y1, 0]) + center_offset, stroke_width=2.5, color=GREEN))409            if (round(cx + rect_size, 4), round(cy, 4)) not in inside_cells:410                outer_boundary_lines.add(Line(np.array([x1, y0, 0]) + center_offset, np.array([x1, y1, 0]) + center_offset, stroke_width=2.5, color=GREEN))411 412        # 内部抵消,只留外边界413        self.play(414            FadeOut(grid_lines), FadeOut(inner_rects),415            FadeIn(outer_boundary_lines),416            run_time=2,417        )418 419        outer_text = Text("内部抵消后,只剩阶梯形外边界", font="SimSun", color=GREEN).scale(0.38)420        outer_text.next_to(boundary, DOWN, buff=0.3)421        self.play(Write(outer_text), run_time=1)422        self.wait(1.5)423 424        # ========== Step 4: 取极限 ==========425        self.play(FadeOut(step3), FadeOut(outer_text))426        step4 = Text("Step 4: 取极限 —— Δx, Δy → 0", font="SimSun", color=WHITE).scale(0.42)427        step4.next_to(title, DOWN, buff=0.2)428        self.play(Write(step4), run_time=1)429 430        # 左端极限:阶梯边界 → L431        left_lim_text = Text("左端:阶梯边界 → 曲线 L", font="SimSun", color=GREEN).scale(0.35)432        left_lim_text.next_to(boundary, DOWN, buff=0.3)433        self.play(Write(left_lim_text), run_time=1)434 435        # 第一次加密436        fine_size_1 = 0.15437        inside_cells_1 = set()438        for cx in np.arange(-2.5, 2.35, fine_size_1):439            for cy in np.arange(-2.5, 2.35, fine_size_1):440                if is_cell_inside(cx, cy, fine_size_1, center_offset):441                    inside_cells_1.add((round(cx, 4), round(cy, 4)))442 443        outer_lines_1 = VGroup()444        for (cx, cy) in inside_cells_1:445            x0, y0, x1, y1 = cx, cy, cx + fine_size_1, cy + fine_size_1446            if (round(cx, 4), round(cy - fine_size_1, 4)) not in inside_cells_1:447                outer_lines_1.add(Line(np.array([x0, y0, 0]) + center_offset, np.array([x1, y0, 0]) + center_offset, stroke_width=1.8, color=GREEN))448            if (round(cx, 4), round(cy + fine_size_1, 4)) not in inside_cells_1:449                outer_lines_1.add(Line(np.array([x0, y1, 0]) + center_offset, np.array([x1, y1, 0]) + center_offset, stroke_width=1.8, color=GREEN))450            if (round(cx - fine_size_1, 4), round(cy, 4)) not in inside_cells_1:451                outer_lines_1.add(Line(np.array([x0, y0, 0]) + center_offset, np.array([x0, y1, 0]) + center_offset, stroke_width=1.8, color=GREEN))452            if (round(cx + fine_size_1, 4), round(cy, 4)) not in inside_cells_1:453                outer_lines_1.add(Line(np.array([x1, y0, 0]) + center_offset, np.array([x1, y1, 0]) + center_offset, stroke_width=1.8, color=GREEN))454 455        self.play(FadeOut(outer_boundary_lines), FadeIn(outer_lines_1), run_time=2)456        self.wait(0.8)457 458        # 第二次加密459        fine_size_2 = 0.08460        inside_cells_2 = set()461        for cx in np.arange(-2.5, 2.42, fine_size_2):462            for cy in np.arange(-2.5, 2.42, fine_size_2):463                if is_cell_inside(cx, cy, fine_size_2, center_offset):464                    inside_cells_2.add((round(cx, 4), round(cy, 4)))465 466        outer_lines_2 = VGroup()467        for (cx, cy) in inside_cells_2:468            x0, y0, x1, y1 = cx, cy, cx + fine_size_2, cy + fine_size_2469            if (round(cx, 4), round(cy - fine_size_2, 4)) not in inside_cells_2:470                outer_lines_2.add(Line(np.array([x0, y0, 0]) + center_offset, np.array([x1, y0, 0]) + center_offset, stroke_width=1.2, color=GREEN))471            if (round(cx, 4), round(cy + fine_size_2, 4)) not in inside_cells_2:472                outer_lines_2.add(Line(np.array([x0, y1, 0]) + center_offset, np.array([x1, y1, 0]) + center_offset, stroke_width=1.2, color=GREEN))473            if (round(cx - fine_size_2, 4), round(cy, 4)) not in inside_cells_2:474                outer_lines_2.add(Line(np.array([x0, y0, 0]) + center_offset, np.array([x0, y1, 0]) + center_offset, stroke_width=1.2, color=GREEN))475            if (round(cx + fine_size_2, 4), round(cy, 4)) not in inside_cells_2:476                outer_lines_2.add(Line(np.array([x1, y0, 0]) + center_offset, np.array([x1, y1, 0]) + center_offset, stroke_width=1.2, color=GREEN))477 478        self.play(FadeOut(outer_lines_1), FadeIn(outer_lines_2), run_time=2)479        self.wait(0.5)480 481        # 边界高亮482        self.play(boundary.animate.set_stroke(color=YELLOW, width=5), run_time=1.5)483        self.wait(1)484 485        # 清理左端演示486        self.play(FadeOut(outer_lines_2), FadeOut(left_lim_text), run_time=1)487 488        # 在已有的求和公式下方,直接添加取极限结果489        # ↓ 箭头490        arrow_left = MathTex(r"\downarrow", color=GREEN).scale(0.45)491        arrow_left.next_to(sum_eq[0], DOWN, buff=0.08)492        arrow_right = MathTex(r"\downarrow", color=TEAL).scale(0.45)493        arrow_right.next_to(sum_eq[2], DOWN, buff=0.08)494        note_left = Text("阶梯→L", font="SimSun", color=GREEN).scale(0.22)495        note_left.next_to(arrow_left, LEFT, buff=0.03)496        note_right = Text("黎曼和→积分", font="SimSun", color=TEAL).scale(0.22)497        note_right.next_to(arrow_right, RIGHT, buff=0.03)498        self.play(Write(arrow_left), Write(arrow_right), Write(note_left), Write(note_right), run_time=1)499 500        # 取极限结果501        lim_eq = MathTex(502            r"\oint_L Pdx{+}Qdy",503            r"=",504            r"\iint_D \left(\frac{\partial Q}{\partial x} - \frac{\partial P}{\partial y}\right)d\sigma",505            color=YELLOW,506        ).scale(0.48)507        lim_eq.next_to(arrow_left, DOWN, buff=0.08).align_to(final_formula, LEFT)508        self.play(Write(lim_eq), run_time=1.5)509        self.wait(2)510 511        # ========== 结论:格林公式 ==========512        self.play(FadeOut(step4))513        final_step = Text("结论:格林公式", font="SimSun", color=YELLOW).scale(0.5)514        final_step.next_to(title, DOWN, buff=0.2)515        self.play(Write(final_step), run_time=1)516 517        green_formula = MathTex(518            r"\oint_L P\,dx + Q\,dy = \iint_D \left(\frac{\partial Q}{\partial x} - \frac{\partial P}{\partial y}\right) d\sigma",519            color=WHITE,520        ).scale(0.7)521        green_formula.to_edge(DOWN, buff=0.4)522        green_box = SurroundingRectangle(green_formula, color=YELLOW, buff=0.2, corner_radius=0.1)523 524        self.play(525            FadeOut(arrow_left), FadeOut(arrow_right),526            FadeOut(note_left), FadeOut(note_right),527            FadeOut(lim_eq),528            FadeOut(final_formula), FadeOut(final_box),529            FadeOut(sum_eq),530        )531        self.play(Write(green_formula), Create(green_box), run_time=2)532        self.wait(3)533 534 535def main():536    import os537    os.system("manim -pqh green_theorem_demo.py GreenTheoremRectangles")538 539 540if __name__ == "__main__":541    main()

讲解

这个视频用“小矩形法”证明格林公式。它的思路是把区域 DD 分割成许多小矩形,先分析单个小矩形上的环路积分,再把所有小矩形的贡献相加,最后让网格无限变细。

LPdx+Qdy=D(QxPy)dσ\oint_L P\,dx+Q\,dy = \iint_D\left(\frac{\partial Q}{\partial x}-\frac{\partial P}{\partial y}\right)d\sigma

开场画面建立区域 DD、边界 LL 和逆时针方向箭头,并给出整体证明路线:分割、近似、求和、取极限。

区域分割步骤把 DD 切成小矩形网格,并高亮完全落在区域内部的小矩形 DiD_i。这里的“小矩形”是后续局部近似的基本单元。

单个小矩形分析先放大 DiD_i,再按逆时针方向计算四条边的积分。上下两条边给出

PyΔxΔy,-\frac{\partial P}{\partial y}\Delta x\Delta y,

左右两条边给出

QxΔxΔy.\frac{\partial Q}{\partial x}\Delta x\Delta y.

因此单个小矩形满足近似关系:

DiPdx+Qdy(QxPy)Δσ.\oint_{\partial D_i}P\,dx+Q\,dy \approx \left(\frac{\partial Q}{\partial x}-\frac{\partial P}{\partial y}\right)\Delta\sigma.

求和步骤把所有小矩形的环路积分相加。相邻小矩形共享边的方向相反,所以内部公共边会成对抵消,最后只剩下阶梯形外边界。

取极限步骤说明:当 Δx,Δy0\Delta x,\Delta y\to0 时,阶梯形边界趋近原曲线 LL,右侧的求和趋近区域上的二重积分:

LPdx+Qdy=D(QxPy)dσ.\oint_LP\,dx+Q\,dy = \iint_D\left(\frac{\partial Q}{\partial x}-\frac{\partial P}{\partial y}\right)d\sigma.

最终结论就是格林公式本身。