greens_theorem_hole.py
1from manim import *2import numpy as np3 4config.tex_template = TexTemplateLibrary.ctex5config.tex_template.add_to_preamble(r"\setCJKmainfont{STSong}")6 7 8class GreensTheoremHole(Scene):9 def _inward_normal(self, circle, point, center):10 """Get unit vector pointing inward (toward center) from point on circle."""11 direction = center - point12 norm = np.linalg.norm(direction)13 if norm < 0.01:14 return np.array([0, 1, 0])15 return direction / norm16 17 def _outward_normal(self, circle, point, center):18 """Get unit vector pointing outward (away from center) from point on circle."""19 direction = point - center20 norm = np.linalg.norm(direction)21 if norm < 0.01:22 return np.array([0, 1, 0])23 return direction / norm24 25 def construct(self):26 # ========== 标题 ==========27 title = Text('格林公式的"洞"处理——挖洞法', font="SimSun", color=YELLOW).scale(0.6)28 title.to_edge(UP, buff=0.3)29 self.play(Write(title), run_time=1.5)30 self.wait(0.5)31 32 # ========== 场景1:展示向量场和奇点 ==========33 # 向量场函数34 center = DOWN * 0.3 # 图形中心偏下35 36 def vec_field_func(pos):37 x, y = pos[0] - center[0], pos[1] - center[1]38 r2 = x**2 + y**239 if r2 < 0.12:40 return np.array([0, 0, 0])41 return np.array([-y / r2, x / r2, 0])42 43 # 画向量场44 vector_field = ArrowVectorField(45 vec_field_func,46 x_range=[-3.5, 3.5, 0.7],47 y_range=[-3.0, 2.5, 0.7],48 length_func=lambda norm: 0.35 * sigmoid(norm),49 color_scheme=None,50 )51 vector_field.set_color(BLUE_C).set_opacity(0.5)52 53 self.play(Create(vector_field), run_time=2.5)54 55 # 原点奇点闪烁56 sing_dot = Dot(center, color=RED, radius=0.12)57 sing_ring = Circle(radius=0.2, color=RED, stroke_width=2).move_to(center)58 sing_label = MathTex("O", color=RED).scale(0.5).next_to(sing_dot, DR, buff=0.05)59 60 self.play(FadeIn(sing_dot), Create(sing_ring), Write(sing_label), run_time=1)61 self.play(62 sing_ring.animate.scale(2).set_opacity(0),63 rate_func=there_and_back,64 run_time=1,65 )66 67 # 向量场公式(缩小保留在左上)68 field_formula = MathTex(69 r"\vec{F} = \left(\frac{-y}{x^2+y^2},\ \frac{x}{x^2+y^2}\right)",70 color=WHITE,71 ).scale(0.4)72 field_formula.to_edge(UL, buff=0.5).shift(DOWN * 0.5)73 74 note_sing = Text("原点无定义(奇点)", font="SimSun", color=RED).scale(0.3)75 note_sing.next_to(field_formula, DOWN, buff=0.1)76 77 self.play(Write(field_formula), Write(note_sing), run_time=1.2)78 self.wait(1)79 80 # 要计算的问题(保留全程)81 problem_desc = Text('L 为包围原点的闭曲线(逆时针)', font="SimSun", color=WHITE).scale(0.26)82 problem_desc.next_to(field_formula, DOWN, buff=0.25)83 problem_eq = MathTex(84 r"\text{求:}\ \oint_L Pdx + Qdy = \ ?",85 color=YELLOW,86 ).scale(0.42)87 problem_eq.next_to(problem_desc, DOWN, buff=0.08)88 problem_group = VGroup(problem_desc, problem_eq)89 problem_box = SurroundingRectangle(problem_group, color=YELLOW, buff=0.08, corner_radius=0.05)90 self.play(Write(problem_desc), run_time=0.8)91 self.play(Write(problem_eq), Create(problem_box), run_time=1.2)92 self.wait(1)93 94 # ========== 场景2:外边界L + 直接用格林 → 错误 ==========95 self.play(FadeOut(note_sing), vector_field.animate.set_opacity(0.2), run_time=0.8)96 97 # 画外边界L98 outer_curve = Circle(radius=2.2, color=BLUE, stroke_width=3.5).move_to(center)99 outer_fill = Circle(radius=2.2, color=BLUE, fill_opacity=0.08, stroke_width=0).move_to(center)100 101 l_label = MathTex("L", color=BLUE).scale(0.6).next_to(outer_curve, UR, buff=-0.15)102 d_label = MathTex("D", color=BLUE).scale(0.7).move_to(center + UP * 1.0 + RIGHT * 1.0)103 104 self.play(Create(outer_curve), FadeIn(outer_fill), run_time=1.5)105 self.play(Write(l_label), Write(d_label), run_time=0.6)106 107 # 动画:点沿L逆时针运动展示方向108 tracer_L = Dot(color=BLUE, radius=0.08)109 tracer_L.move_to(outer_curve.point_from_proportion(0))110 trail_L = TracedPath(tracer_L.get_center, stroke_color=BLUE, stroke_width=4)111 self.add(trail_L)112 self.play(113 MoveAlongPath(tracer_L, outer_curve),114 run_time=2.5, rate_func=linear,115 )116 self.play(FadeOut(tracer_L), FadeOut(trail_L), run_time=0.3)117 118 # 逆时针箭头标记119 l_arrows = VGroup()120 for prop in [0.1, 0.35, 0.6, 0.85]:121 pos = outer_curve.point_from_proportion(prop)122 next_pos = outer_curve.point_from_proportion(prop + 0.02)123 tangent = next_pos - pos124 tangent = tangent / np.linalg.norm(tangent) * 0.22125 arr = Arrow(pos - tangent * 0.5, pos + tangent * 0.5,126 buff=0, stroke_width=2.5, color=BLUE, tip_length=0.1)127 l_arrows.add(arr)128 self.play(LaggedStartMap(Create, l_arrows, lag_ratio=0.1), run_time=0.8)129 130 # 右侧:尝试格林公式131 naive_group = VGroup()132 naive_title = Text('直接用格林公式?', font="SimSun", color=WHITE).scale(0.35)133 naive_title.to_edge(RIGHT, buff=0.8).shift(UP * 1.5)134 naive_group.add(naive_title)135 136 naive_eq = MathTex(137 r"\oint_L = \iint_D 0\,d\sigma = 0\ ?",138 color=WHITE,139 ).scale(0.4)140 naive_eq.next_to(naive_title, DOWN, buff=0.12)141 naive_group.add(naive_eq)142 143 self.play(Write(naive_title), run_time=0.6)144 self.play(Write(naive_eq), run_time=1)145 146 # 闪烁D区域表示"有问题"147 flash_fill = outer_fill.copy().set_fill(RED, opacity=0.3)148 self.play(FadeIn(flash_fill), run_time=0.3)149 self.play(FadeOut(flash_fill), run_time=0.3)150 self.play(FadeIn(flash_fill), run_time=0.3)151 self.play(FadeOut(flash_fill), run_time=0.3)152 153 # 错误提示154 error_text = Text('错误!D内有奇点!', font="SimSun", color=RED).scale(0.32)155 error_text.next_to(naive_eq, DOWN, buff=0.15)156 cross_line = Line(157 naive_eq.get_left() + LEFT * 0.05,158 naive_eq.get_right() + RIGHT * 0.05,159 color=RED, stroke_width=3,160 )161 self.play(Write(error_text), Create(cross_line), run_time=1)162 self.wait(1.5)163 164 # 清理右侧165 self.play(FadeOut(naive_group), FadeOut(error_text), FadeOut(cross_line), run_time=0.6)166 167 # ========== 场景3:挖洞法 —— 动画展示 ==========168 step3_label = Text('挖洞法:绕开奇点', font="SimSun", color=GREEN).scale(0.35)169 step3_label.to_edge(RIGHT, buff=0.8).shift(UP * 1.8)170 self.play(Write(step3_label), run_time=0.8)171 172 # 动画:小圆从0逐渐长大到 epsilon173 eps_circle = Circle(radius=0.01, color=YELLOW, stroke_width=3).move_to(center)174 self.play(Create(eps_circle), run_time=0.3)175 self.play(eps_circle.animate.scale(50), run_time=1.5) # 0.01 * 50 = 0.5176 177 # 填充黑色(挖掉)178 hole_fill = Circle(radius=0.5, color=BLACK, fill_opacity=0.85, stroke_width=0).move_to(center)179 self.play(FadeIn(hole_fill), FadeOut(sing_dot), FadeOut(sing_ring), FadeOut(sing_label), run_time=1)180 181 # 小圆标签182 eps_label = MathTex(r"C_\varepsilon", color=YELLOW).scale(0.45)183 eps_label.next_to(eps_circle, DOWN, buff=0.08)184 self.play(Write(eps_label), run_time=0.5)185 186 # 描述小圆的特征/方程187 eps_desc = MathTex(188 r"C_\varepsilon: x^2 + y^2 = \varepsilon^2",189 color=YELLOW,190 ).scale(0.38)191 eps_desc.next_to(step3_label, DOWN, buff=0.15)192 eps_note = Text('以奇点为圆心,半径为 ε 的小圆', font="SimSun", color=WHITE).scale(0.26)193 eps_note.next_to(eps_desc, DOWN, buff=0.08)194 eps_note2 = Text('(ε 足够小,使 C_ε 完全在 L 内部)', font="SimSun", color=GREY_B).scale(0.24)195 eps_note2.next_to(eps_note, DOWN, buff=0.05)196 self.play(Write(eps_desc), run_time=0.8)197 self.play(Write(eps_note), Write(eps_note2), run_time=1)198 self.wait(1)199 200 # 点沿 C_eps 顺时针运动展示方向201 # 顺时针 = 反向参数化202 tracer_eps = Dot(color=YELLOW, radius=0.07)203 eps_path_cw = eps_circle.copy().reverse_direction()204 tracer_eps.move_to(eps_path_cw.point_from_proportion(0))205 trail_eps = TracedPath(tracer_eps.get_center, stroke_color=YELLOW, stroke_width=3)206 self.add(trail_eps)207 self.play(208 MoveAlongPath(tracer_eps, eps_path_cw),209 run_time=2, rate_func=linear,210 )211 self.play(FadeOut(tracer_eps), FadeOut(trail_eps), run_time=0.3)212 213 # 顺时针箭头214 eps_arrows = VGroup()215 for prop in [0.1, 0.4, 0.7]:216 pos = eps_circle.point_from_proportion(prop)217 prev_pos = eps_circle.point_from_proportion(prop - 0.025)218 tangent = prev_pos - pos219 tangent = tangent / np.linalg.norm(tangent) * 0.18220 arr = Arrow(pos - tangent * 0.5, pos + tangent * 0.5,221 buff=0, stroke_width=2.5, color=YELLOW, tip_length=0.08)222 eps_arrows.add(arr)223 self.play(LaggedStartMap(Create, eps_arrows, lag_ratio=0.15), run_time=0.8)224 225 # 标注新区域 D'226 d_prime_label = MathTex(r"D'", color=GREEN).scale(0.65)227 d_prime_label.move_to(center + UP * 1.2 + LEFT * 0.7)228 self.play(Transform(d_label, d_prime_label), run_time=0.8)229 230 # 右侧注释231 note_dprime = Text("D' 无奇点 → 可用格林公式", font="SimSun", color=GREEN).scale(0.3)232 note_dprime.next_to(eps_note2, DOWN, buff=0.15)233 self.play(Write(note_dprime), run_time=1)234 self.wait(1.5)235 236 # ========== 场景3.5:强调复连通区域边界正方向 ==========237 self.play(238 FadeOut(step3_label), FadeOut(note_dprime),239 FadeOut(eps_desc), FadeOut(eps_note), FadeOut(eps_note2),240 run_time=0.5,241 )242 243 dir_title = Text('复连通区域的边界正方向', font="SimSun", color=TEAL).scale(0.38)244 dir_title.to_edge(RIGHT, buff=0.6).shift(UP * 2.2)245 self.play(Write(dir_title), run_time=0.8)246 247 # 规则文字248 rule_text = Text('规则:沿正方向行走时', font="SimSun", color=WHITE).scale(0.3)249 rule_text.next_to(dir_title, DOWN, buff=0.15)250 rule_text2 = Text('区域 D\' 始终在左手边', font="SimSun", color=YELLOW).scale(0.3)251 rule_text2.next_to(rule_text, DOWN, buff=0.05)252 self.play(Write(rule_text), run_time=0.8)253 self.play(Write(rule_text2), run_time=0.8)254 255 # 高亮外边界 —— 逆时针256 outer_dir_label = Text('外边界 L:逆时针', font="SimSun", color=BLUE).scale(0.28)257 outer_dir_label.next_to(rule_text2, DOWN, buff=0.25)258 self.play(Write(outer_dir_label), run_time=0.6)259 260 # 动画:小人沿外边界逆时针走 + 左手指向内侧261 walker_L = Dot(color=BLUE, radius=0.1)262 walker_L.move_to(outer_curve.point_from_proportion(0))263 # 左手指示箭头(指向区域内侧)264 left_hand_L = always_redraw(265 lambda: Arrow(266 walker_L.get_center(),267 walker_L.get_center() + self._inward_normal(outer_curve, walker_L.get_center(), center) * 0.5,268 buff=0, color=GREEN, stroke_width=3, tip_length=0.1,269 )270 )271 left_label_L = always_redraw(272 lambda: Text('D\'', font="SimSun", color=GREEN).scale(0.22).move_to(273 walker_L.get_center() + self._inward_normal(outer_curve, walker_L.get_center(), center) * 0.7274 )275 )276 277 self.add(left_hand_L, left_label_L)278 self.play(279 MoveAlongPath(walker_L, outer_curve),280 run_time=4, rate_func=linear,281 )282 self.play(FadeOut(walker_L), FadeOut(left_hand_L), FadeOut(left_label_L), run_time=0.3)283 284 # 高亮内边界 —— 顺时针285 inner_dir_label = Text('内边界 C_ε:顺时针', font="SimSun", color=YELLOW).scale(0.28)286 inner_dir_label.next_to(outer_dir_label, DOWN, buff=0.12)287 self.play(Write(inner_dir_label), run_time=0.6)288 289 # 动画:小人沿内边界顺时针走 + 左手指向外侧(即区域D')290 walker_C = Dot(color=YELLOW, radius=0.1)291 eps_path_cw2 = eps_circle.copy().reverse_direction()292 walker_C.move_to(eps_path_cw2.point_from_proportion(0))293 left_hand_C = always_redraw(294 lambda: Arrow(295 walker_C.get_center(),296 walker_C.get_center() + self._outward_normal(eps_circle, walker_C.get_center(), center) * 0.5,297 buff=0, color=GREEN, stroke_width=3, tip_length=0.1,298 )299 )300 left_label_C = always_redraw(301 lambda: Text('D\'', font="SimSun", color=GREEN).scale(0.22).move_to(302 walker_C.get_center() + self._outward_normal(eps_circle, walker_C.get_center(), center) * 0.7303 )304 )305 306 self.add(left_hand_C, left_label_C)307 self.play(308 MoveAlongPath(walker_C, eps_path_cw2),309 run_time=3, rate_func=linear,310 )311 self.play(FadeOut(walker_C), FadeOut(left_hand_C), FadeOut(left_label_C), run_time=0.3)312 313 # 总结图示314 summary_dir = VGroup(315 MathTex(r"L:", color=BLUE).scale(0.4),316 Text('逆时针(区域在左)', font="SimSun", color=BLUE).scale(0.26),317 ).arrange(RIGHT, buff=0.1)318 summary_dir2 = VGroup(319 MathTex(r"C_\varepsilon:", color=YELLOW).scale(0.4),320 Text('顺时针(区域在左)', font="SimSun", color=YELLOW).scale(0.26),321 ).arrange(RIGHT, buff=0.1)322 summary_group = VGroup(summary_dir, summary_dir2).arrange(DOWN, buff=0.1, aligned_edge=LEFT)323 summary_group.next_to(inner_dir_label, DOWN, buff=0.2)324 self.play(FadeIn(summary_group), run_time=0.8)325 self.wait(2)326 327 # 清理328 dir_objs = VGroup(dir_title, rule_text, rule_text2, outer_dir_label, inner_dir_label, summary_group)329 self.play(FadeOut(dir_objs), run_time=0.6)330 331 # ========== 场景4:在D'上格林公式(图+公式配合) ==========332 333 # 高亮两条边界334 self.play(335 outer_curve.animate.set_color(BLUE).set_stroke(width=5),336 eps_circle.animate.set_color(YELLOW).set_stroke(width=5),337 run_time=0.8,338 )339 self.play(340 outer_curve.animate.set_stroke(width=3.5),341 eps_circle.animate.set_stroke(width=3),342 run_time=0.5,343 )344 345 # 右侧公式346 step4_eq1 = MathTex(347 r"\partial D' = ", r"L", r"\ +\ ", r"C_\varepsilon^-",348 color=WHITE,349 ).scale(0.4)350 step4_eq1[1].set_color(BLUE)351 step4_eq1[3].set_color(YELLOW)352 step4_eq1.to_edge(RIGHT, buff=0.8).shift(UP * 1.5)353 self.play(Write(step4_eq1), run_time=1)354 355 # 闪烁对应边界356 self.play(outer_curve.animate.set_color(WHITE), run_time=0.3)357 self.play(outer_curve.animate.set_color(BLUE), run_time=0.3)358 self.play(eps_circle.animate.set_color(WHITE), run_time=0.3)359 self.play(eps_circle.animate.set_color(YELLOW), run_time=0.3)360 361 # 格林公式362 step4_eq2 = MathTex(363 r"\oint_L + \oint_{C_\varepsilon^-} = \iint_{D'} 0\,d\sigma",364 color=WHITE,365 ).scale(0.38)366 step4_eq2.next_to(step4_eq1, DOWN, buff=0.2)367 self.play(Write(step4_eq2), run_time=1.5)368 369 # 闪烁D'区域(绿色)表示积分为0370 annular_fill = Annulus(371 inner_radius=0.5, outer_radius=2.2,372 color=GREEN, fill_opacity=0.2, stroke_width=0,373 ).move_to(center)374 self.play(FadeIn(annular_fill), run_time=0.8)375 self.play(FadeOut(annular_fill), run_time=0.8)376 377 # = 0378 step4_eq3 = MathTex(r"= 0", color=TEAL).scale(0.5)379 step4_eq3.next_to(step4_eq2, DOWN, buff=0.1)380 self.play(Write(step4_eq3), run_time=0.6)381 382 # 关键结论383 step4_result = MathTex(384 r"\therefore\ \oint_L = -\oint_{C_\varepsilon^-} = \oint_{C_\varepsilon^+}",385 color=YELLOW,386 ).scale(0.42)387 step4_result.next_to(step4_eq3, DOWN, buff=0.2)388 result_box = SurroundingRectangle(step4_result, color=YELLOW, buff=0.06, corner_radius=0.05)389 self.play(Write(step4_result), Create(result_box), run_time=1.5)390 self.wait(2)391 392 # ========== 场景5:转化为小圆积分(可视化计算) ==========393 # 清理场景4公式(保留黄框结论)394 scene4_cleanup = VGroup(step4_eq1, step4_eq2, step4_eq3)395 self.play(FadeOut(scene4_cleanup), run_time=0.6)396 397 # 将黄框结论移到问题框下方保留398 self.play(399 step4_result.animate.scale(0.9).next_to(problem_box, DOWN, buff=0.15),400 result_box.animate.scale(0.9).move_to(401 problem_box.get_bottom() + DOWN * 0.35402 ),403 run_time=1,404 )405 # 重新包围406 new_result_box = SurroundingRectangle(step4_result, color=YELLOW, buff=0.06, corner_radius=0.05)407 self.play(Transform(result_box, new_result_box), run_time=0.3)408 409 # 移除外边界相关,放大小圆410 self.play(411 FadeOut(outer_curve), FadeOut(outer_fill), FadeOut(l_arrows),412 FadeOut(l_label), FadeOut(d_label),413 FadeOut(vector_field),414 eps_circle.animate.scale(3).move_to(LEFT * 2.5 + DOWN * 0.3),415 hole_fill.animate.scale(3).move_to(LEFT * 2.5 + DOWN * 0.3),416 eps_arrows.animate.scale(3).move_to(LEFT * 2.5 + DOWN * 0.3),417 eps_label.animate.scale(1.3).next_to(LEFT * 2.5 + DOWN * 0.3, DOWN, buff=0.6),418 run_time=1.5,419 )420 421 # 放大后重新定位422 big_center = LEFT * 2.5 + DOWN * 0.3423 big_circle = eps_circle424 425 # 参数化标注:在圆上标点426 angle_tracker = ValueTracker(0)427 radius = 1.5 # 0.5 * 3428 429 moving_dot = always_redraw(430 lambda: Dot(431 big_center + radius * np.array([432 np.cos(angle_tracker.get_value()),433 np.sin(angle_tracker.get_value()), 0434 ]),435 color=ORANGE, radius=0.08,436 )437 )438 # 半径线439 radius_line = always_redraw(440 lambda: DashedLine(441 big_center,442 big_center + radius * np.array([443 np.cos(angle_tracker.get_value()),444 np.sin(angle_tracker.get_value()), 0445 ]),446 color=ORANGE, stroke_width=2,447 )448 )449 # 角度标注450 angle_label = always_redraw(451 lambda: MathTex("t", color=ORANGE).scale(0.45).move_to(452 big_center + 0.4 * np.array([453 np.cos(angle_tracker.get_value() / 2),454 np.sin(angle_tracker.get_value() / 2), 0455 ])456 )457 )458 459 self.play(FadeIn(moving_dot), Create(radius_line), Write(angle_label), run_time=0.8)460 461 # 右侧参数化公式462 param_eq = MathTex(463 r"x = \varepsilon\cos t,\ y = \varepsilon\sin t",464 color=ORANGE,465 ).scale(0.4)466 param_eq.to_edge(RIGHT, buff=1.2).shift(UP * 1.5)467 param_range = MathTex(r"t: 0 \to 2\pi", color=ORANGE).scale(0.4)468 param_range.next_to(param_eq, DOWN, buff=0.1)469 self.play(Write(param_eq), Write(param_range), run_time=1)470 471 # 动画:点沿圆逆时针转一圈472 self.play(angle_tracker.animate.set_value(2 * PI), run_time=3, rate_func=linear)473 self.wait(0.5)474 475 # 代入计算476 calc1 = MathTex(477 r"\oint_{C_\varepsilon^+} Pdx + Qdy",478 color=WHITE,479 ).scale(0.4)480 calc1.next_to(param_range, DOWN, buff=0.3)481 self.play(Write(calc1), run_time=0.8)482 483 calc2 = MathTex(484 r"= \int_0^{2\pi} \frac{\varepsilon^2}{\varepsilon^2}\,dt",485 color=WHITE,486 ).scale(0.4)487 calc2.next_to(calc1, DOWN, buff=0.1)488 self.play(Write(calc2), run_time=1)489 490 calc3 = MathTex(491 r"= \int_0^{2\pi} 1\,dt = 2\pi",492 color=YELLOW,493 ).scale(0.5)494 calc3.next_to(calc2, DOWN, buff=0.1)495 self.play(Write(calc3), run_time=1.2)496 497 calc_box = SurroundingRectangle(calc3, color=YELLOW, buff=0.06, corner_radius=0.05)498 self.play(Create(calc_box), run_time=0.5)499 self.wait(1)500 501 # 最终结论:更新问题框的答案502 answer_eq = MathTex(503 r"\text{求:}\ \oint_L Pdx + Qdy = 2\pi",504 color=YELLOW,505 ).scale(0.42)506 answer_eq.move_to(problem_eq)507 answer_group = VGroup(problem_desc.copy(), answer_eq)508 answer_box = SurroundingRectangle(answer_group, color=GREEN, buff=0.08, corner_radius=0.05)509 self.play(510 Transform(problem_eq, answer_eq),511 Transform(problem_box, answer_box),512 run_time=1.5,513 )514 515 summary = Text('挖洞法:绕开奇点,转化为小圆上的简单积分!', font="SimSun", color=GREEN).scale(0.32)516 summary.to_edge(DOWN, buff=0.4)517 self.play(Write(summary), run_time=1.2)518 self.wait(3)519 520 521def main():522 import os523 os.system("manim -pql greens_theorem_hole.py GreensTheoremHole")524 525 526if __name__ == "__main__":527 main() 讲解
这个视频处理格林公式里最容易被忽略的一种情况:区域内部有奇点。向量场
在原点无定义。虽然在原点之外有
但如果闭曲线 包围原点,就不能直接写成
问题不在公式本身,而在使用条件:格林公式要求向量场在区域内满足相应光滑性。原点这个奇点落在 内部时, 不是可以直接套用格林公式的区域。
挖去奇点
开场画面先定义绕原点旋转的向量场,并在靠近奇点时避免绘制。目标积分提示把向量场、奇点和待求边界积分放在同一张图上:
错误尝试画面展示“直接用格林公式”的问题:外边界 围成的区域 内有奇点,所以不能把右侧二重积分简单写成 。
挖洞法从这一处开始。动画在奇点周围挖去一个小圆
把原区域改成没有奇点的环形区域 。这样一来,格林公式可以在 上使用。
边界方向
对带洞区域,边界不只是一条曲线。边界方向说明专门解释复连通区域的正方向:
外边界 取逆时针方向;内边界 取顺时针方向。这样沿边界行走时,区域 始终在左手边。
在 上使用格林公式时,可以得到
于是
也就是说,原来沿外边界 的积分,可以转化成沿小圆正向 的积分。
小圆积分
小圆积分部分把内边界参数化为
代入向量场后,
因此
最终得到
这个结论也解释了为什么“旋度为零”不等于“闭合曲线积分一定为零”:如果区域里有洞或奇点,格林公式的使用条件必须先处理清楚。