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() 讲解
这个视频用“小矩形法”证明格林公式。它的思路是把区域 分割成许多小矩形,先分析单个小矩形上的环路积分,再把所有小矩形的贡献相加,最后让网格无限变细。
开场画面建立区域 、边界 和逆时针方向箭头,并给出整体证明路线:分割、近似、求和、取极限。
区域分割步骤把 切成小矩形网格,并高亮完全落在区域内部的小矩形 。这里的“小矩形”是后续局部近似的基本单元。
单个小矩形分析先放大 ,再按逆时针方向计算四条边的积分。上下两条边给出
左右两条边给出
因此单个小矩形满足近似关系:
求和步骤把所有小矩形的环路积分相加。相邻小矩形共享边的方向相反,所以内部公共边会成对抵消,最后只剩下阶梯形外边界。
取极限步骤说明:当 时,阶梯形边界趋近原曲线 ,右侧的求和趋近区域上的二重积分:
最终结论就是格林公式本身。